From fdaf30ea45da2470776182fdcb641121591e37ed Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 25 Jan 2024 00:23:01 +0100 Subject: [PATCH 01/28] Add serverless adapter :wip --- build.gradle.kts | 2 + gradle.properties | 4 ++ serverless/serverless_http/README.md | 15 +++++++ serverless/serverless_http/build.gradle.kts | 16 +++++++ .../serverless/http/ServerlessHttp.kt | 11 +++++ .../serverless/http/ServerlessHttpTest.kt | 5 +++ serverless/serverless_http_google/README.md | 9 ++++ .../serverless_http_google/build.gradle.kts | 45 +++++++++++++++++++ .../google/GoogleServerlessHttpAdapter.kt | 12 +++++ .../google/GoogleServerlessHttpAdapterTest.kt | 13 ++++++ settings.gradle.kts | 1 + 11 files changed, 133 insertions(+) create mode 100644 serverless/serverless_http/README.md create mode 100644 serverless/serverless_http/build.gradle.kts create mode 100644 serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt create mode 100644 serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt create mode 100644 serverless/serverless_http_google/README.md create mode 100644 serverless/serverless_http_google/build.gradle.kts create mode 100644 serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt create mode 100644 serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index ae1e46ad15..d49c8a99cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -159,6 +159,8 @@ apiValidation { // Experimental modules "rest", "rest_tools", + "serverless_http", + "serverless_http_google", "web", "templates_jte", ) diff --git a/gradle.properties b/gradle.properties index 451842e10f..9d9f1ed094 100644 --- a/gradle.properties +++ b/gradle.properties @@ -64,6 +64,10 @@ logbackVersion=1.4.14 jacksonVersion=2.16.1 dslJsonVersion=2.0.2 +# serverless_http_google +functionsVersion=1.1.0 +invokerVersion=1.3.1 + # templates_freemarker freemarkerVersion=2.3.32 diff --git a/serverless/serverless_http/README.md b/serverless/serverless_http/README.md new file mode 100644 index 0000000000..6bd2e0d679 --- /dev/null +++ b/serverless/serverless_http/README.md @@ -0,0 +1,15 @@ + +# Module serverless_http +This port's purpose is to develop HTTP serverless functions. It uses the [http_handlers] module to +process HTTP events. + +[http_handlers]: /http_handlers + +## Install the Dependency +This module is not meant to be used directly. You should include and Adapter implementing this +feature (as [serverless_http_google]) in order to create an HTTP serverless function. + +[serverless_http_google]: /serverless_http_google + +# Package com.hexagonkt.serverless.serverless.http +TODO diff --git a/serverless/serverless_http/build.gradle.kts b/serverless/serverless_http/build.gradle.kts new file mode 100644 index 0000000000..274b2fdf74 --- /dev/null +++ b/serverless/serverless_http/build.gradle.kts @@ -0,0 +1,16 @@ + +plugins { + id("java-library") +} + +apply(from = "$rootDir/gradle/kotlin.gradle") +apply(from = "$rootDir/gradle/publish.gradle") +apply(from = "$rootDir/gradle/dokka.gradle") +apply(from = "$rootDir/gradle/native.gradle") +apply(from = "$rootDir/gradle/detekt.gradle") + +description = "Serverless HTTP functions. Requires an adapter to be used." + +dependencies { + "api"(project(":http:http_handlers")) +} diff --git a/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt b/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt new file mode 100644 index 0000000000..b9238d49d3 --- /dev/null +++ b/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt @@ -0,0 +1,11 @@ +package com.hexagonkt.serverless.http + +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.model.HttpRequestPort +import com.hexagonkt.http.model.HttpResponsePort + +interface ServerlessHttp { + val handler: HttpHandler + fun request(): HttpRequestPort + fun response(): HttpResponsePort +} diff --git a/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt b/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt new file mode 100644 index 0000000000..afc9ded12d --- /dev/null +++ b/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt @@ -0,0 +1,5 @@ +package com.hexagonkt.serverless.http + +internal class ServerlessHttpTest { + +} diff --git a/serverless/serverless_http_google/README.md b/serverless/serverless_http_google/README.md new file mode 100644 index 0000000000..8d20407471 --- /dev/null +++ b/serverless/serverless_http_google/README.md @@ -0,0 +1,9 @@ + +# Module serverless_http_google +TODO + +## Install the Dependency +TODO + +# Package com.hexagonkt.serverless.http.google +TODO diff --git a/serverless/serverless_http_google/build.gradle.kts b/serverless/serverless_http_google/build.gradle.kts new file mode 100644 index 0000000000..a58d9e082b --- /dev/null +++ b/serverless/serverless_http_google/build.gradle.kts @@ -0,0 +1,45 @@ + +plugins { + id("java-library") +} + +apply(from = "$rootDir/gradle/kotlin.gradle") +apply(from = "$rootDir/gradle/publish.gradle") +apply(from = "$rootDir/gradle/dokka.gradle") +apply(from = "$rootDir/gradle/native.gradle") +apply(from = "$rootDir/gradle/detekt.gradle") + +description = "Google Functions Serverless adapter." + +private val target = "com.hexagonkt.serverless.http.google.GoogleServerlessHttpAdapter" +private val invoker by configurations.creating + +dependencies { + val functionsVersion = properties["functionsVersion"] + val invokerVersion = properties["invokerVersion"] + + "api"(project(":serverless:serverless_http")) + "compileOnly"("com.google.cloud.functions:functions-framework-api:$functionsVersion") + + "testImplementation"("com.google.cloud.functions:functions-framework-api:$functionsVersion") + "testImplementation"("com.google.cloud.functions.invoker:java-function-invoker:$invokerVersion") + + invoker("com.google.cloud.functions.invoker:java-function-invoker:1.3.1") +} + +tasks.register("runFunction") { + val classpath = files(configurations.runtimeClasspath, sourceSets["main"].output) + + classpath(invoker) + mainClass = "com.google.cloud.functions.invoker.runner.Invoker" + inputs.files(classpath) + + args( + "--target", properties["run.target"] ?: target, + "--port", properties["run.port"] ?: 8080 + ) + + doFirst { + args("--classpath", classpath.asPath) + } +} diff --git a/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt b/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt new file mode 100644 index 0000000000..c93e17e6eb --- /dev/null +++ b/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt @@ -0,0 +1,12 @@ +package com.hexagonkt.serverless.http.google + +import com.google.cloud.functions.HttpFunction +import com.google.cloud.functions.HttpRequest +import com.google.cloud.functions.HttpResponse + +class GoogleServerlessHttpAdapter: HttpFunction { + + override fun service(request: HttpRequest, response: HttpResponse) { + response.writer.write("Hello World!") + } +} diff --git a/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt new file mode 100644 index 0000000000..a2e9bfdab5 --- /dev/null +++ b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt @@ -0,0 +1,13 @@ +package com.hexagonkt.serverless.http.google + +import com.google.cloud.functions.invoker.runner.Invoker +import kotlin.test.Test + +internal class GoogleServerlessHttpAdapterTest { + + @Test fun `Google functions work ok`() { +// Invoker.main( +// +// ) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7cad87df8c..94b927da6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,7 @@ includeNestedModules( "http", "logging", "serialization", + "serverless", "templates" ) From 46f0ae42105bb10f175aab4f86df990fd6710cf4 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 25 Jan 2024 22:21:11 +0100 Subject: [PATCH 02/28] Remove logging adapters --- .sdkmanrc | 2 +- core/api/core.api | 52 +-------- .../com/hexagonkt/core/logging/Logger.kt | 89 +++------------ .../com/hexagonkt/core/logging/LoggerPort.kt | 24 ---- .../hexagonkt/core/logging/LoggingLevel.kt | 20 ---- .../hexagonkt/core/logging/LoggingManager.kt | 76 +------------ .../com/hexagonkt/core/logging/LoggingPort.kt | 31 ----- .../hexagonkt/core/logging/SystemLogger.kt | 28 ----- .../core/logging/SystemLoggingAdapter.kt | 34 ------ .../core/ClasspathHandlerProviderTest.kt | 3 - .../com/hexagonkt/core/logging/LoggerTest.kt | 52 +-------- .../core/logging/LoggingManagerTest.kt | 46 +------- .../com/hexagonkt/core/logging/LoggingTest.kt | 106 ------------------ .../core/logging/SystemLoggerTest.kt | 46 -------- gradle.properties | 6 +- .../hexagonkt/http/handlers/HttpPredicate.kt | 14 +-- http/http_server/api/http_server.api | 4 +- .../http/server/callbacks/LoggingCallback.kt | 4 +- .../http/server/servlet/ServletServerTest.kt | 5 - .../com/hexagonkt/http/test/BaseTest.kt | 5 - .../http/test/examples/SamplesTest.kt | 16 --- http/rest_tools/build.gradle.kts | 9 +- .../com/hexagonkt/web/examples/TodoTest.kt | 3 - logging/logging_jul/README.md | 24 ---- logging/logging_jul/api/logging_jul.api | 17 --- logging/logging_jul/build.gradle.kts | 16 --- .../logging/jul/JulLoggingAdapter.kt | 78 ------------- .../hexagonkt/logging/jul/PatternFormat.kt | 85 -------------- .../logging/jul/SystemStreamHandler.kt | 29 ----- .../src/main/kotlin/module-info.java | 12 -- .../hexagonkt/logging/jul/JulLoggerTest.kt | 95 ---------------- .../logging/jul/PatternFormatTest.kt | 63 ----------- .../logging_jul/native-image.properties | 3 - logging/logging_logback/README.md | 65 ----------- .../logging_logback/api/logging_logback.api | 7 -- logging/logging_logback/build.gradle.kts | 23 ---- .../logging/logback/LogbackLoggingAdapter.kt | 72 ------------ .../src/main/kotlin/module-info.java | 14 --- .../logging/logback/LogbackLoggerTest.kt | 105 ----------------- .../logging_logback/native-image.properties | 3 - .../src/test/resources/logback-test.xml | 30 ----- logging/logging_slf4j_jul/README.md | 59 ---------- .../api/logging_slf4j_jul.api | 9 -- logging/logging_slf4j_jul/build.gradle.kts | 21 ---- .../slf4j/jul/Slf4jJulLoggingAdapter.kt | 63 ----------- .../src/main/kotlin/module-info.java | 13 --- .../logging/slf4j/jul/Slf4jJulLoggerTest.kt | 95 ---------------- .../logging_slf4j_jul/native-image.properties | 3 - settings.gradle.kts | 1 - site/pages/index.md | 2 +- 50 files changed, 33 insertions(+), 1649 deletions(-) delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt delete mode 100644 core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt delete mode 100644 logging/logging_jul/README.md delete mode 100644 logging/logging_jul/api/logging_jul.api delete mode 100644 logging/logging_jul/build.gradle.kts delete mode 100644 logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt delete mode 100644 logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt delete mode 100644 logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt delete mode 100644 logging/logging_jul/src/main/kotlin/module-info.java delete mode 100644 logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt delete mode 100644 logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt delete mode 100644 logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties delete mode 100644 logging/logging_logback/README.md delete mode 100644 logging/logging_logback/api/logging_logback.api delete mode 100644 logging/logging_logback/build.gradle.kts delete mode 100644 logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt delete mode 100644 logging/logging_logback/src/main/kotlin/module-info.java delete mode 100644 logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt delete mode 100644 logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties delete mode 100644 logging/logging_logback/src/test/resources/logback-test.xml delete mode 100644 logging/logging_slf4j_jul/README.md delete mode 100644 logging/logging_slf4j_jul/api/logging_slf4j_jul.api delete mode 100644 logging/logging_slf4j_jul/build.gradle.kts delete mode 100644 logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt delete mode 100644 logging/logging_slf4j_jul/src/main/kotlin/module-info.java delete mode 100644 logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt delete mode 100644 logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties diff --git a/.sdkmanrc b/.sdkmanrc index 9584f14f9c..62631dfc04 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=21.0.1-graalce +java=21.0.2-graalce diff --git a/core/api/core.api b/core/api/core.api index f707a8e429..ebf79b97f4 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -228,26 +228,14 @@ public final class com/hexagonkt/core/logging/Logger { public static synthetic fun error$default (Lcom/hexagonkt/core/logging/Logger;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun getName ()Ljava/lang/String; public final fun info (Lkotlin/jvm/functions/Function0;)V - public final fun isDebugEnabled ()Z - public final fun isErrorEnabled ()Z - public final fun isInfoEnabled ()Z - public final fun isLoggerLevelEnabled (Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isTraceEnabled ()Z - public final fun isWarnEnabled ()Z - public final fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V - public final fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Lkotlin/jvm/functions/Function0;)V - public final fun setLoggerLevel (Lcom/hexagonkt/core/logging/LoggingLevel;)V + public final fun log (Ljava/lang/System$Logger$Level;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V + public final fun log (Ljava/lang/System$Logger$Level;Lkotlin/jvm/functions/Function0;)V public final fun trace (Lkotlin/jvm/functions/Function0;)V public final fun warn (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V public final fun warn (Lkotlin/jvm/functions/Function0;)V public static synthetic fun warn$default (Lcom/hexagonkt/core/logging/Logger;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } -public abstract interface class com/hexagonkt/core/logging/LoggerPort { - public abstract fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V - public abstract fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Lkotlin/jvm/functions/Function0;)V -} - public final class com/hexagonkt/core/logging/LoggingKt { public static final fun debug (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; public static synthetic fun debug$default (Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; @@ -258,50 +246,14 @@ public final class com/hexagonkt/core/logging/LoggingKt { public static synthetic fun trace$default (Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; } -public final class com/hexagonkt/core/logging/LoggingLevel : java/lang/Enum { - public static final field DEBUG Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field ERROR Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field INFO Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field OFF Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field TRACE Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field WARN Lcom/hexagonkt/core/logging/LoggingLevel; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggingLevel; - public static fun values ()[Lcom/hexagonkt/core/logging/LoggingLevel; -} - public final class com/hexagonkt/core/logging/LoggingManager { public static final field INSTANCE Lcom/hexagonkt/core/logging/LoggingManager; - public final fun getAdapter ()Lcom/hexagonkt/core/logging/LoggingPort; public final fun getDefaultLoggerName ()Ljava/lang/String; public final fun getUseColor ()Z - public final fun isLoggerLevelEnabled (Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Ljava/lang/Object;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Lkotlin/reflect/KClass;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun setAdapter (Lcom/hexagonkt/core/logging/LoggingPort;)V public final fun setDefaultLoggerName (Ljava/lang/String;)V - public final fun setLoggerLevel (Lcom/hexagonkt/core/logging/LoggingLevel;)V - public final fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V - public final fun setLoggerLevel (Lkotlin/reflect/KClass;Lcom/hexagonkt/core/logging/LoggingLevel;)V public final fun setUseColor (Z)V } -public abstract interface class com/hexagonkt/core/logging/LoggingPort { - public abstract fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public abstract fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public abstract fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - -public final class com/hexagonkt/core/logging/SystemLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (Lcom/hexagonkt/core/logging/LoggingLevel;)V - public synthetic fun (Lcom/hexagonkt/core/logging/LoggingLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - public final class com/hexagonkt/core/media/MediaType { public static final field Companion Lcom/hexagonkt/core/media/MediaType$Companion; public fun (Lcom/hexagonkt/core/media/MediaTypeGroup;Ljava/lang/String;)V diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt index fd3f20c960..62583c06c1 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt @@ -1,7 +1,8 @@ package com.hexagonkt.core.logging +import com.hexagonkt.core.text.stripAnsi +import java.lang.System.Logger.Level.* import kotlin.reflect.KClass -import com.hexagonkt.core.logging.LoggingLevel.* /** * Logger class with Kotlin improvements like lazy evaluation. It is backed by a logging port. @@ -11,23 +12,21 @@ import com.hexagonkt.core.logging.LoggingLevel.* */ class Logger(val name: String) { - internal val log: LoggerPort = LoggingManager.adapter.createLogger(name) + internal val log: System.Logger = System.getLogger(name) /** * Log a message, with associated exception information. - * - * @see LoggerPort.log */ - fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) { - log.log(level, exception, message) + fun log(level: System.Logger.Level, exception: E, message: (E) -> Any?) { + if (LoggingManager.useColor) message(exception) + else message(exception)?.toString()?.stripAnsi() + log.log(level, { message(exception)?.toString() }, exception) } /** * Log a message. - * - * @see LoggerPort.log */ - fun log(level: LoggingLevel, message: () -> Any?) { + fun log(level: System.Logger.Level, message: () -> Any?) { log.log(level, message) } @@ -67,12 +66,12 @@ class Logger(val name: String) { } /** - * Log a message using [WARN] level. + * Log a message using [WARNING] level. * * @param message The required message to log. */ fun warn(message: () -> Any?) { - log.log(WARN, message) + log.log(WARNING, message) } /** @@ -85,14 +84,14 @@ class Logger(val name: String) { } /** - * Log a message using [WARN] level with associated exception information. + * Log a message using [WARNING] level with associated exception information. * * @param exception The exception associated with log message. * @param message The message to log (optional). If not supplied it will be empty. */ fun warn(exception: E?, message: (E?) -> Any? = { "" }) { - if (exception == null) log.log(WARN) { message(null) } - else log.log(WARN, exception, message) + if (exception == null) log(WARNING) { message(null) } + else log(WARNING, exception, message) } /** @@ -102,65 +101,7 @@ class Logger(val name: String) { * @param message The message to log (optional). If not supplied it will be empty. */ fun error(exception: E?, message: (E?) -> Any? = { "" }) { - if (exception == null) log.log(ERROR) { message(null) } - else log.log(ERROR, exception, message) - } - - /** - * Set a logging level for this logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(level: LoggingLevel) { - LoggingManager.setLoggerLevel(name, level) + if (exception == null) log(ERROR) { message(null) } + else log(ERROR, exception, message) } - - /** - * Check if a logging level is enabled for this logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for this logger. - */ - fun isLoggerLevelEnabled(level: LoggingLevel): Boolean = - LoggingManager.isLoggerLevelEnabled(name, level) - - /** - * Check if the [TRACE] logging level is enabled for this logger. - * - * @return True if the [TRACE] level is enabled for this logger. - */ - fun isTraceEnabled(): Boolean = - isLoggerLevelEnabled(TRACE) - - /** - * Check if the [DEBUG] logging level is enabled for this logger. - * - * @return True if the [DEBUG] level is enabled for this logger. - */ - fun isDebugEnabled(): Boolean = - isLoggerLevelEnabled(DEBUG) - - /** - * Check if the [INFO] logging level is enabled for this logger. - * - * @return True if the [INFO] level is enabled for this logger. - */ - fun isInfoEnabled(): Boolean = - isLoggerLevelEnabled(INFO) - - /** - * Check if the [WARN] logging level is enabled for this logger. - * - * @return True if the [WARN] level is enabled for this logger. - */ - fun isWarnEnabled(): Boolean = - isLoggerLevelEnabled(WARN) - - /** - * Check if the [ERROR] logging level is enabled for this logger. - * - * @return True if the [ERROR] level is enabled for this logger. - */ - fun isErrorEnabled(): Boolean = - isLoggerLevelEnabled(ERROR) } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt deleted file mode 100644 index 453b8479fc..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * A Logger is used to log messages for a specific system or application component. - */ -interface LoggerPort { - - /** - * Log a message, with associated exception information. - * - * @param level One of the message level identifiers, e.g., TRACE. - * @param exception The exception associated with log message. - * @param message The required message to log. - */ - fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) - - /** - * Log a message. - * - * @param level One of the message level identifiers, e.g., TRACE. - * @param message The required message to log. - */ - fun log(level: LoggingLevel, message: () -> Any?) -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt deleted file mode 100644 index e04bd8a164..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * Logger logging level values. - * - * @property TRACE Used for low level details that are logged very often. - * @property DEBUG Useful information to diagnose problems or failures. - * @property INFO Only used for really useful information that is not written very often. - * @property WARN To notify that something failed and was ignored, but it could be an issue later. - * @property ERROR Error that stopped the correct processing of the process. - * @property OFF Disable all logging levels. - */ -enum class LoggingLevel { - TRACE, - DEBUG, - INFO, - WARN, - ERROR, - OFF, -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt index 9532e6b604..9e06d23065 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt @@ -1,87 +1,13 @@ package com.hexagonkt.core.logging -import kotlin.reflect.KClass - /** - * Manages Logs using [SystemLoggingAdapter] + * Manages Logs. */ object LoggingManager { var useColor: Boolean = true - var adapter: LoggingPort = SystemLoggingAdapter() var defaultLoggerName: String = "com.hexagonkt.core.logging" set(value) { require(value.isNotEmpty()) { "Default logger name cannot be empty string" } field = value } - - /** - * Set a logger logging level by name. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(name: String, level: LoggingLevel) { - adapter.setLoggerLevel(name, level) - } - - /** - * Set a logging level for a logger with a class name. - * - * @param type Class type. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(type: KClass<*>, level: LoggingLevel) { - setLoggerLevel(qualifiedName(type), level) - } - - /** - * Set a logger logging level for a logger with a default name. - * - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(level: LoggingLevel) { - setLoggerLevel("", level) - } - - /** - * Check if a logging level is enabled for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - adapter.isLoggerLevelEnabled(name, level) - - /** - * Check if a logging level is enabled for a logger with an instance. - * - * @param instance class instance. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(instance: Any, level: LoggingLevel): Boolean = - isLoggerLevelEnabled(instance::class, level) - - /** - * Check if a logging level is enabled for a logger with a class name. - * - * @param type Class type. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(type: KClass<*>, level: LoggingLevel): Boolean = - isLoggerLevelEnabled(qualifiedName(type), level) - - /** - * Check if a logging level is enabled for the root logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(level: LoggingLevel): Boolean = - isLoggerLevelEnabled("", level) - - private fun qualifiedName(type: KClass<*>): String = - type.qualifiedName ?: error("Cannot get qualified name of type") } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt deleted file mode 100644 index 177a246c70..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * Logging Contract for integrating different logging libraries. - */ -interface LoggingPort { - - /** - * Create [Logger][LoggerPort] with name. - * - * @param name Logger name. - */ - fun createLogger(name: String): LoggerPort - - /** - * Set logging level for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(name: String, level: LoggingLevel) - - /** - * Check if a logging level is enabled for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt deleted file mode 100644 index 90eabed085..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.* - -internal data class SystemLogger(val name: String) : LoggerPort { - - private val logger: System.Logger = System.getLogger(name) - - override fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) { - if (LoggingManager.isLoggerLevelEnabled(name, level)) - logger.log(level(level), message(exception).toString(), exception) - } - - override fun log(level: LoggingLevel, message: () -> Any?) { - if (LoggingManager.isLoggerLevelEnabled(name, level)) - logger.log(level(level), message()) - } - - private fun level(level: LoggingLevel): System.Logger.Level = - when (level) { - TRACE -> System.Logger.Level.TRACE - DEBUG -> System.Logger.Level.DEBUG - INFO -> System.Logger.Level.INFO - WARN -> System.Logger.Level.WARNING - ERROR -> System.Logger.Level.ERROR - OFF -> error("OFF Level not allowed for a logging message") - } -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt deleted file mode 100644 index ab9ea645ae..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.require - -class SystemLoggingAdapter(defaultLevel: LoggingLevel = INFO) : LoggingPort { - - private val loggerLevels: MutableMap = mutableMapOf("" to defaultLevel) - - override fun createLogger(name: String): LoggerPort = - SystemLogger(name) - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - loggerLevels[name] = level - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - findLoggingLevel(name).ordinal <= level.ordinal - - private fun findLoggingLevel(name: String): LoggingLevel { - var path = name - - do { - val loggingLevel = loggerLevels[path] - if (loggingLevel != null) - return loggingLevel - - path = path.substringBeforeLast('.') - } - while (path.contains('.')) - - return loggerLevels.require("") - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt index 7a96c368e4..c9ab44a238 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt @@ -1,7 +1,5 @@ package com.hexagonkt.core -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingManager import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -13,7 +11,6 @@ import kotlin.test.assertFailsWith internal class ClasspathHandlerProviderTest { @BeforeAll fun registerHandler() { - LoggingManager.setLoggerLevel("com.hexagonkt", LoggingLevel.TRACE) ClasspathHandler.registerHandler() } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt index b1ba8d65a3..ffa37dc4f1 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt @@ -1,10 +1,11 @@ package com.hexagonkt.core.logging -import com.hexagonkt.core.logging.LoggingLevel.* import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.condition.DisabledInNativeImage import org.junit.jupiter.api.Test +import java.lang.System.Logger.Level.ERROR +import java.lang.System.Logger.Level.TRACE import kotlin.IllegalStateException import kotlin.reflect.KClass import kotlin.test.* @@ -36,11 +37,6 @@ internal class LoggerTest { classLogger.warn(exception) classLogger.error(exception) classLogger.error { "Error without an exception" } - - // Logging level can be changed programmatically - LoggingManager.setLoggerLevel(ERROR) - LoggingManager.setLoggerLevel(classLogger::class, DEBUG) - LoggingManager.setLoggerLevel("com.hexagonkt", INFO) // logger } @@ -72,50 +68,6 @@ internal class LoggerTest { assert(Logger("name"::class).name == "kotlin.String") } - @Test fun `A logger level can be changed`() { - val l = Logger("l") - - l.setLoggerLevel(TRACE) - assert(l.name == "l") - assert(l.isTraceEnabled()) - assert(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(DEBUG) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assert(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(INFO) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(WARN) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assertFalse(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(ERROR) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assertFalse(l.isInfoEnabled()) - assertFalse(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - } - @Test @DisabledInNativeImage fun `Invalid class name raises error`() { diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt index e789514ee4..cdf87d63e2 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt @@ -1,6 +1,5 @@ package com.hexagonkt.core.logging -import com.hexagonkt.core.logging.LoggingLevel.* import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.condition.DisabledInNativeImage @@ -9,49 +8,9 @@ import kotlin.reflect.KClass import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue internal class LoggingManagerTest { - // TODO Repeat this test on other logging adapters - @Test fun `Loggers are enabled and disabled at runtime`() { - - LoggingManager.adapter = SystemLoggingAdapter() - - val ch = Logger("com.hx") - val chc = Logger("com.hx.core") - val chl = Logger("com.hx.logging") - - LoggingManager.setLoggerLevel("com.hx", TRACE) - assertTrue(Logger("z").isLoggerLevelEnabled(INFO)) - assertFalse(Logger("z").isLoggerLevelEnabled(DEBUG)) - assertTrue( - entries.all { - ch.isLoggerLevelEnabled(it) - && chc.isLoggerLevelEnabled(it) - && chl.isLoggerLevelEnabled(it) - } - ) - - LoggingManager.setLoggerLevel("com.hx.core", WARN) - assertTrue(chc.isLoggerLevelEnabled(ERROR) && chc.isErrorEnabled()) - assertTrue(chc.isLoggerLevelEnabled(WARN) && chc.isWarnEnabled()) - assertFalse(chc.isLoggerLevelEnabled(INFO) || chc.isInfoEnabled()) - assertFalse(chc.isLoggerLevelEnabled(DEBUG) || chc.isDebugEnabled()) - assertFalse(chc.isLoggerLevelEnabled(TRACE) || chc.isTraceEnabled()) - assertTrue(entries.all { ch.isLoggerLevelEnabled(it) && chl.isLoggerLevelEnabled(it) }) - - // TODO Check if parent level changes gets reflected on created loggers (com.hx -> TRACE) - LoggingManager.setLoggerLevel("com.hx.core", TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled("com.hx.core", INFO)) - assertTrue(chc.isLoggerLevelEnabled(ERROR) && chc.isErrorEnabled()) - assertTrue(chc.isLoggerLevelEnabled(WARN) && chc.isWarnEnabled()) - assertTrue(chc.isLoggerLevelEnabled(INFO) && chc.isInfoEnabled()) - assertTrue(chc.isLoggerLevelEnabled(DEBUG) && chc.isDebugEnabled()) - assertTrue(chc.isLoggerLevelEnabled(TRACE) && chc.isTraceEnabled()) - } - @Test fun `'defaultLoggerName' can be changed`() { val dln = LoggingManager.defaultLoggerName @@ -75,10 +34,7 @@ internal class LoggingManagerTest { val kc = mockk>() every { kc.qualifiedName } returns null - assertFailsWith { LoggingManager.isLoggerLevelEnabled(kc, INFO) } - .apply { assertEquals("Cannot get qualified name of type", message) } - - assertFailsWith { LoggingManager.setLoggerLevel(kc, INFO) } + assertFailsWith { Logger(kc) } .apply { assertEquals("Cannot get qualified name of type", message) } } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt index ff03e9a6c3..1b6e3d52b6 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt @@ -1,11 +1,7 @@ package com.hexagonkt.core.logging -import com.hexagonkt.core.logging.LoggingLevel.* import org.junit.jupiter.api.Test -import java.time.LocalDate import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue internal class LoggingTest { @@ -45,106 +41,4 @@ internal class LoggingTest { assertEquals(null, null.info()) assertEquals("text", "text".info()) } - - @Test fun `Manager can check if the root logger is enabled for a given level`() { - LoggingManager.setLoggerLevel(TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(DEBUG) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(INFO) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(WARN) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(ERROR) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(OFF) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(WARN) - } - - @Test fun `Manager can check if loggers are enabled for a given level`() { - val date = LocalDate.now() - - LoggingManager.setLoggerLevel(OFF) - - LoggingManager.setLoggerLevel(date::class, TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, DEBUG) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, INFO) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, WARN) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, ERROR) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, ERROR)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, OFF) - assertFalse(LoggingManager.isLoggerLevelEnabled(LocalDate::class, ERROR)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(WARN) - } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt deleted file mode 100644 index d0f165d46c..0000000000 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.* -import org.junit.jupiter.api.Test -import java.lang.RuntimeException -import kotlin.test.assertFailsWith - -// TODO Add string appender and assert messages -internal class SystemLoggerTest { - - @Test fun `System loggers work as expected`() { - LoggingManager.adapter = SystemLoggingAdapter(TRACE) - val l = Logger("a") - - l.log(TRACE) { "trace" } - l.log(DEBUG) { "debug" } - l.log(INFO) { "info" } - l.log(WARN) { "warn" } - l.log(ERROR) { "error" } - - l.log(TRACE, RuntimeException()) { "trace $it" } - l.log(DEBUG, RuntimeException()) { "debug $it" } - l.log(INFO, RuntimeException()) { "info $it" } - l.log(WARN, RuntimeException()) { "warn $it" } - l.log(ERROR, RuntimeException()) { "error $it" } - - LoggingManager.adapter = SystemLoggingAdapter() - } - - @Test fun `Disabled logs are not issued`() { - LoggingManager.adapter = SystemLoggingAdapter(DEBUG) - val l = Logger("a") - - l.log(TRACE) { "trace" } - l.log(TRACE, RuntimeException()) { "trace $it" } - - LoggingManager.adapter = SystemLoggingAdapter() - } - - @Test fun `Invalid log level throws an error`() { - assertFailsWith { logger.log(OFF) { "error" } } - assertFailsWith { - logger.log(OFF, RuntimeException()) { "error $it" } - } - } -} diff --git a/gradle.properties b/gradle.properties index 451842e10f..76cb9ef794 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.warning.mode=all org.gradle.console=plain # Gradle -version=3.4.7 +version=3.4.8 group=com.hexagonkt description=The atoms of your platform @@ -56,10 +56,6 @@ jettyVersion=12.0.5 swaggerRequestValidatorVersion=2.40.0 vertxVersion=4.5.1 -# logging -slf4jVersion=2.0.11 -logbackVersion=1.4.14 - # serialization jacksonVersion=2.16.1 dslJsonVersion=2.0.2 diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt index 83ed2934f9..03b7c861cd 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt @@ -45,16 +45,10 @@ data class HttpPredicate( private fun log( predicate: (Context) -> Boolean - ): (Context) -> Boolean { - return if (logger.isDebugEnabled()) { - { - val allowed = predicate(it) - logger.debug { "${describe()} -> ${if (allowed) "ALLOWED" else "DENIED"}" } - allowed - } - } - else - predicate + ): (Context) -> Boolean = { + val allowed = predicate(it) + logger.debug { "${describe()} -> ${if (allowed) "ALLOWED" else "DENIED"}" } + allowed } private fun filterMethod(context: Context): Boolean = diff --git a/http/http_server/api/http_server.api b/http/http_server/api/http_server.api index 095b8878fc..4ddbc452cf 100644 --- a/http/http_server/api/http_server.api +++ b/http/http_server/api/http_server.api @@ -110,8 +110,8 @@ public final class com/hexagonkt/http/server/callbacks/FileCallback : kotlin/jvm public final class com/hexagonkt/http/server/callbacks/LoggingCallback : kotlin/jvm/functions/Function1 { public fun ()V - public fun (Lcom/hexagonkt/core/logging/LoggingLevel;Lcom/hexagonkt/core/logging/Logger;ZZ)V - public synthetic fun (Lcom/hexagonkt/core/logging/LoggingLevel;Lcom/hexagonkt/core/logging/Logger;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/System$Logger$Level;Lcom/hexagonkt/core/logging/Logger;ZZ)V + public synthetic fun (Ljava/lang/System$Logger$Level;Lcom/hexagonkt/core/logging/Logger;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun invoke (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public synthetic fun invoke (Ljava/lang/Object;)Ljava/lang/Object; } diff --git a/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt b/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt index 4ca8ada3ab..cdcbd02be1 100644 --- a/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt +++ b/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt @@ -1,16 +1,16 @@ package com.hexagonkt.http.server.callbacks import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel import com.hexagonkt.http.model.* import com.hexagonkt.http.handlers.HttpContext +import java.lang.System.Logger.Level import kotlin.system.measureNanoTime /** * Callback that logs server requests and responses. */ class LoggingCallback( - private val level: LoggingLevel = LoggingLevel.INFO, + private val level: Level = Level.INFO, private val logger: Logger = Logger(LoggingCallback::class), private val includeHeaders: Boolean = false, private val includeBody: Boolean = true, diff --git a/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt b/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt index c84308c31c..3eb37a9260 100644 --- a/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt +++ b/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.server.servlet -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.urlOf import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientSettings @@ -41,7 +38,6 @@ internal class ServletServerTest { private val jettyServer = JettyServer(InetSocketAddress("127.0.0.1", 9897)) @BeforeAll fun `Run server`() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) val context = WebAppContext() context.contextPath = "/" context.war = "." @@ -58,7 +54,6 @@ internal class ServletServerTest { @AfterAll fun shutdown() { jettyServer.stopAtShutdown = true jettyServer.stop() - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) } @Test fun `Servlet server starts`() { diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt index 2347d38343..9664240415 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.test -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.urlOf import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientPort @@ -38,7 +35,6 @@ abstract class BaseTest { } @BeforeAll fun startUp() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) server.start() client.start() } @@ -46,7 +42,6 @@ abstract class BaseTest { @AfterAll fun shutDown() { client.stop() server.stop() - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) } protected fun assertResponseContains( diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt index 9d7d90111c..61d106b69c 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.test.examples -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.media.APPLICATION_JSON import com.hexagonkt.core.media.APPLICATION_XML import com.hexagonkt.core.media.TEXT_CSS @@ -26,29 +23,16 @@ import com.hexagonkt.http.server.HttpServerSettings import com.hexagonkt.http.server.callbacks.UrlCallback import com.hexagonkt.http.handlers.* import com.hexagonkt.http.server.serve -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import java.net.InetAddress import kotlin.test.assertEquals -@TestInstance(PER_CLASS) abstract class SamplesTest( val clientAdapter: () -> HttpClientPort, val serverAdapter: () -> HttpServerPort, val serverSettings: HttpServerSettings = HttpServerSettings(), ) { - @BeforeAll fun startUp() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) - } - - @AfterAll fun shutDown() { - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) - } - @Test fun serverCreation() { // serverCreation /* diff --git a/http/rest_tools/build.gradle.kts b/http/rest_tools/build.gradle.kts index 167bda92f6..39aa3babcf 100644 --- a/http/rest_tools/build.gradle.kts +++ b/http/rest_tools/build.gradle.kts @@ -12,8 +12,6 @@ apply(from = "$rootDir/gradle/detekt.gradle") description = "Tools to test and document REST services." dependencies { - val guavaVersion = "33.0.0-jre" - val slf4jVersion = properties["slf4jVersion"] val swaggerRequestValidatorVersion = properties["swaggerRequestValidatorVersion"] val vertxVersion = properties["vertxVersion"] @@ -21,12 +19,7 @@ dependencies { "api"(project(":http:http_server")) "api"(project(":http:http_client")) "api"("io.vertx:vertx-openapi:$vertxVersion") - "api"("org.slf4j:slf4j-api:$slf4jVersion") - "api"("com.google.guava:guava:$guavaVersion") - "api"("com.atlassian.oai:swagger-request-validator-core:$swaggerRequestValidatorVersion") { - exclude(group = "org.slf4j") - exclude(group = "com.google.guava") - } + "api"("com.atlassian.oai:swagger-request-validator-core:$swaggerRequestValidatorVersion") "testImplementation"(project(":http:http_client_jetty")) "testImplementation"(project(":http:http_server_jetty")) diff --git a/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt b/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt index a356bbf11d..24f459ca23 100644 --- a/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt +++ b/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt @@ -2,8 +2,6 @@ package com.hexagonkt.web.examples import com.hexagonkt.core.* import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.media.APPLICATION_JSON import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientSettings @@ -150,7 +148,6 @@ abstract class TodoTest(adapter: HttpServerPort) { @BeforeAll fun initialize() { SerializationManager.formats = linkedSetOf(Json) - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) server.start() client.start() } diff --git a/logging/logging_jul/README.md b/logging/logging_jul/README.md deleted file mode 100644 index 5eb1ad40c6..0000000000 --- a/logging/logging_jul/README.md +++ /dev/null @@ -1,24 +0,0 @@ - -# Module logging_logback -Contains the logger adapter for the Java Util Logging package. - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_jul:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - com.hexagonkt - logging_jul - $hexagonVersion - - ``` - -# Package com.hexagonkt.logging.jul -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_jul/api/logging_jul.api b/logging/logging_jul/api/logging_jul.api deleted file mode 100644 index fa096bb5a1..0000000000 --- a/logging/logging_jul/api/logging_jul.api +++ /dev/null @@ -1,17 +0,0 @@ -public final class com/hexagonkt/logging/jul/JulLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (ZLjava/io/PrintStream;)V - public synthetic fun (ZLjava/io/PrintStream;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - -public final class com/hexagonkt/logging/jul/PatternFormat : java/util/logging/Formatter { - public static final field COLOR_PATTERN Ljava/lang/String; - public static final field PATTERN Ljava/lang/String; - public fun (ZZ)V - public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun format (Ljava/util/logging/LogRecord;)Ljava/lang/String; -} - diff --git a/logging/logging_jul/build.gradle.kts b/logging/logging_jul/build.gradle.kts deleted file mode 100644 index cb85d6cdb7..0000000000 --- a/logging/logging_jul/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ - -plugins { - id("java-library") -} - -apply(from = "$rootDir/gradle/kotlin.gradle") -apply(from = "$rootDir/gradle/publish.gradle") -apply(from = "$rootDir/gradle/dokka.gradle") -apply(from = "$rootDir/gradle/native.gradle") -apply(from = "$rootDir/gradle/detekt.gradle") - -description = "Hexagon Java Util Logging adapter." - -dependencies { - "api"(project(":core")) -} diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt deleted file mode 100644 index d31ff247a9..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.ERROR -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.logging.LoggingLevel.TRACE -import com.hexagonkt.core.logging.LoggingLevel.WARN -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager.useColor -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.core.text.stripAnsi -import java.io.PrintStream -import java.util.logging.Level -import java.util.logging.Logger as JulLogger - -/** - * Implements [LoggingPort] using [Logger][JulLogger]. - */ -class JulLoggingAdapter( - messageOnly: Boolean = false, - stream: PrintStream = System.out -) : LoggingPort { - - init { - val root = JulLogger.getLogger("") - - for (hnd in root.handlers) - root.removeHandler(hnd) - - val handlerFormatter = PatternFormat(useColor, messageOnly) - val systemStreamHandler = SystemStreamHandler(handlerFormatter, stream) - root.addHandler(systemStreamHandler) - root.level = Level.INFO - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - JulLogger.getLogger(name).level = mapLevel(level) - } - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: JulLogger = JulLogger.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - val julLevel = mapLevel(level) - if (log.isLoggable(julLevel)) - log.log(julLevel, color(message().toString())) - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - val julLevel = mapLevel(level) - if (log.isLoggable(julLevel)) - log.log(julLevel, color(message(exception).toString()), exception) - } - - private fun color(message: String): String = - if (useColor) message - else message.stripAnsi() - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - JulLogger.getLogger(name).isLoggable(mapLevel(level)) - - internal fun mapLevel(level: LoggingLevel): Level = when (level) { - TRACE -> Level.FINER - DEBUG -> Level.FINE - INFO -> Level.INFO - WARN -> Level.WARNING - ERROR -> Level.SEVERE - OFF -> Level.OFF - } -} diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt deleted file mode 100644 index 7f156a79df..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.text.AnsiColor.DEFAULT -import com.hexagonkt.core.text.AnsiColor.YELLOW -import com.hexagonkt.core.text.AnsiColor.BLUE -import com.hexagonkt.core.text.AnsiColor.BRIGHT_BLACK -import com.hexagonkt.core.text.AnsiColor.CYAN -import com.hexagonkt.core.text.AnsiColor.MAGENTA -import com.hexagonkt.core.text.AnsiColor.RED -import com.hexagonkt.core.text.AnsiEffect.BOLD -import com.hexagonkt.core.text.Ansi.RESET -import com.hexagonkt.core.text.eol -import com.hexagonkt.core.fail -import com.hexagonkt.core.toText -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.logging.Formatter -import java.util.logging.Level -import java.util.logging.LogRecord - -/** - * A Formatter implements [Formatter] provides support for formatting Logs. - * - * @property useColor Use colors in log messages. - */ -class PatternFormat( - private val useColor: Boolean, - private val messageOnly: Boolean = false, -) : Formatter() { - - internal companion object { - private const val TIMESTAMP = "%tH:% = mapOf( - Level.FINER to DEFAULT, - Level.FINE to DEFAULT, - Level.INFO to BLUE, - Level.WARNING to YELLOW, - Level.SEVERE to RED + BOLD - ) - - private val levelNames: Map = mapOf( - Level.FINER to "TRACE", - Level.FINE to "DEBUG", - Level.INFO to "INFO", - Level.WARNING to "WARN", - Level.SEVERE to "ERROR" - ) - - override fun format(record: LogRecord): String { - if (messageOnly) - return record.message + "\n" - - val instant = Instant.ofEpochMilli(record.millis) - val dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) - val thrown = record.thrown - val trace = when { - thrown == null -> "" - useColor -> "$eol${thrown.toText()}".replace("\n", "\n$RED") - else -> "$eol${thrown.toText()}" - } - val level = record.level - val levelName = levelNames[level] ?: fail - val levelColor = levelColors[level] ?: BLUE - val message = record.message - val loggerName = record.loggerName - val threadName = Thread.currentThread().name - - return if (useColor) - pattern.format(dateTime, levelColor, levelName, threadName, loggerName, message + trace) - else - pattern.format(dateTime, levelName, threadName, loggerName, message + trace) - } -} diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt deleted file mode 100644 index 6823701447..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.hexagonkt.logging.jul - -import java.io.PrintStream -import java.util.logging.Formatter -import java.util.logging.Level -import java.util.logging.LogRecord -import java.util.logging.StreamHandler - -/** - * Create a StreamHandler with a given [Formatter]. - * - * @param handlerFormatter Formatter used by the log handler. - */ -internal class SystemStreamHandler( - handlerFormatter: Formatter, - stream: PrintStream = System.out -) : StreamHandler() { - - override fun publish(record: LogRecord) { - super.publish(record) - flush() - } - - init { - setOutputStream(stream) - formatter = handlerFormatter - level = Level.ALL - } -} diff --git a/logging/logging_jul/src/main/kotlin/module-info.java b/logging/logging_jul/src/main/kotlin/module-info.java deleted file mode 100644 index 9e0617a4e3..0000000000 --- a/logging/logging_jul/src/main/kotlin/module-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_jul { - - requires transitive java.logging; - requires transitive com.hexagonkt.core; - - exports com.hexagonkt.logging.jul; -} diff --git a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt b/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt deleted file mode 100644 index e21db29129..0000000000 --- a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class JulLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using Logback`() { - - LoggingManager.adapter = JulLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt b/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt deleted file mode 100644 index b3b4d38900..0000000000 --- a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.fail -import com.hexagonkt.core.text.AnsiColor -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import java.lang.RuntimeException -import java.util.logging.Level.INFO -import java.util.logging.Level.SEVERE -import java.util.logging.LogRecord - -internal class PatternFormatTest { - - @Test fun `Formatting messages with 'printf' special characters works correctly`() { - val message = "Message with '%'" - - val colorFormat = PatternFormat(true) - val colorMessage = colorFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertTrue(colorMessage.contains(AnsiColor.BLUE)) - - val plainFormat = PatternFormat(false) - val plainMessage = plainFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE)) - } - - @Test fun `Formatting error messages render stack traces`() { - val message = "Message with '%'" - val record = LogRecord(SEVERE, message) - record.thrown = RuntimeException("Tested failure") - - val colorMessage = PatternFormat(true).format(record) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertTrue(colorMessage.contains(AnsiColor.RED)) - Assertions.assertTrue(colorMessage.contains("Tested failure")) - Assertions.assertTrue(colorMessage.contains(RuntimeException::class.qualifiedName ?: fail)) - Assertions.assertTrue(colorMessage.contains(this::class.qualifiedName ?: fail)) - - val plainMessage = PatternFormat(false).format(record) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(plainMessage.contains(AnsiColor.RED)) - Assertions.assertTrue(plainMessage.contains("Tested failure")) - Assertions.assertTrue(plainMessage.contains(RuntimeException::class.qualifiedName ?: fail)) - Assertions.assertTrue(plainMessage.contains(this::class.qualifiedName ?: fail)) - } - - @Test fun `Formatting messages without logging fields works correctly`() { - val message = "Message with '%'" - - val colorFormat = PatternFormat(useColor = true, messageOnly = true) - val colorMessage = colorFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertFalse(colorMessage.contains("INFO")) - Assertions.assertFalse(colorMessage.contains(AnsiColor.BLUE)) - - val plainFormat = PatternFormat(useColor = false, messageOnly = true) - val plainMessage = plainFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(colorMessage.contains("INFO")) - Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE)) - } -} diff --git a/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties b/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/logging/logging_logback/README.md b/logging/logging_logback/README.md deleted file mode 100644 index 8bdbb5715c..0000000000 --- a/logging/logging_logback/README.md +++ /dev/null @@ -1,65 +0,0 @@ - -# Module logging_logback -Contains the logger adapter for the [Logback] logging library. - -[Logback]: http://logback.qos.ch - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_logback:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - - - com.hexagonkt - logging_logback - $hexagonVersion - - ``` - -> ℹ️ **Info** -> -> The above adapter bridge other logging libraries that may be used by other third party -> libraries you use (if you want to disable this behaviour, you need to explicitly exclude bridging -> libraries). - -=== "build.gradle" - - ```groovy - // Bridges - runtimeOnly("org.slf4j:jcl-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:log4j-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:jul-to-slf4j:1.7.30") - ``` - -=== "pom.xml" - - ```xml - - org.slf4j - jcl-over-slf4j - 1.7.30 - - - org.slf4j - log4j-over-slf4j - 1.7.30 - - - org.slf4j - jul-to-slf4j - 1.7.30 - - ``` - -# Package com.hexagonkt.logging.logback -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_logback/api/logging_logback.api b/logging/logging_logback/api/logging_logback.api deleted file mode 100644 index 99b2cf2073..0000000000 --- a/logging/logging_logback/api/logging_logback.api +++ /dev/null @@ -1,7 +0,0 @@ -public final class com/hexagonkt/logging/logback/LogbackLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - diff --git a/logging/logging_logback/build.gradle.kts b/logging/logging_logback/build.gradle.kts deleted file mode 100644 index 6ffa6e7b33..0000000000 --- a/logging/logging_logback/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ - -plugins { - id("java-library") -} - -apply(from = "$rootDir/gradle/kotlin.gradle") -apply(from = "$rootDir/gradle/publish.gradle") -apply(from = "$rootDir/gradle/dokka.gradle") -apply(from = "$rootDir/gradle/native.gradle") -apply(from = "$rootDir/gradle/detekt.gradle") - -description = "Hexagon Logback logging adapter." - -dependencies { - val slf4jVersion = properties["slf4jVersion"] - val logbackVersion = properties["logbackVersion"] - - "api"(project(":core")) - "api"("ch.qos.logback:logback-classic:$logbackVersion") { exclude("org.slf4j") } - "api"("org.slf4j:jul-to-slf4j:$slf4jVersion") - "api"("org.slf4j:jcl-over-slf4j:$slf4jVersion") - "api"("org.slf4j:log4j-over-slf4j:$slf4jVersion") -} diff --git a/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt b/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt deleted file mode 100644 index bd728a9ea9..0000000000 --- a/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.hexagonkt.logging.logback - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.ERROR -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.logging.LoggingLevel.TRACE -import com.hexagonkt.core.logging.LoggingLevel.WARN -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.core.text.stripAnsi -import org.slf4j.LoggerFactory - -class LogbackLoggingAdapter : LoggingPort { - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: org.slf4j.Logger = LoggerFactory.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - val processedMessage = color(message().toString()) - when (level) { - TRACE -> if (log.isTraceEnabled) log.trace(processedMessage) - DEBUG -> if (log.isDebugEnabled) log.debug(processedMessage) - INFO -> if (log.isInfoEnabled) log.info(processedMessage) - WARN -> if (log.isWarnEnabled) log.warn(processedMessage) - ERROR -> if (log.isErrorEnabled) log.error(processedMessage) - OFF -> {} - } - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - val processedMessage = color(message(exception).toString()) - when (level) { - WARN -> if (log.isWarnEnabled) log.warn(processedMessage, exception) - ERROR -> if (log.isErrorEnabled) log.error(processedMessage, exception) - else -> {} - } - } - - private fun color(message: String): String = - if (LoggingManager.useColor) message - else message.stripAnsi() - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - val loggerName = name.ifEmpty { Logger.ROOT_LOGGER_NAME } - (LoggerFactory.getLogger(loggerName) as Logger).level = mapLevel(level) - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean { - val loggerName = name.ifEmpty { Logger.ROOT_LOGGER_NAME } - return (LoggerFactory.getLogger(loggerName) as Logger).isEnabledFor(mapLevel(level)) - } - - private fun mapLevel(level: LoggingLevel): Level = when (level) { - TRACE -> Level.TRACE - DEBUG -> Level.DEBUG - INFO -> Level.INFO - WARN -> Level.WARN - ERROR -> Level.ERROR - OFF -> Level.OFF - } -} diff --git a/logging/logging_logback/src/main/kotlin/module-info.java b/logging/logging_logback/src/main/kotlin/module-info.java deleted file mode 100644 index 6fde7d91d9..0000000000 --- a/logging/logging_logback/src/main/kotlin/module-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_logback { - - requires transitive com.hexagonkt.core; - requires transitive org.slf4j; - requires transitive ch.qos.logback.core; - requires transitive ch.qos.logback.classic; - - exports com.hexagonkt.logging.logback; -} diff --git a/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt b/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt deleted file mode 100644 index f8cfb4c7cd..0000000000 --- a/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.hexagonkt.logging.logback - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class LogbackLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using Logback`() { - - LoggingManager.useColor = true - LoggingManager.adapter = LogbackLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - - LoggingManager.useColor = false - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties b/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/logging/logging_logback/src/test/resources/logback-test.xml b/logging/logging_logback/src/test/resources/logback-test.xml deleted file mode 100644 index 0d90cfe15d..0000000000 --- a/logging/logging_logback/src/test/resources/logback-test.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - ${pattern} - - - - - - log/%d{yyyy-MM-dd}.log - 5 - - - ${pattern} - - - - - - - - - diff --git a/logging/logging_slf4j_jul/README.md b/logging/logging_slf4j_jul/README.md deleted file mode 100644 index faae8ee532..0000000000 --- a/logging/logging_slf4j_jul/README.md +++ /dev/null @@ -1,59 +0,0 @@ - -# Module logging_slf4j_jul -Contains the logger adapter for the [SLF4J JUL] logging library. - -[SLF4J JUL]: http://www.slf4j.org - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_slf4j_jul:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - - - com.hexagonkt - logging_slf4j_jul - $hexagonVersion - - ``` - -> ℹ️ **Info** -> -> The above adapter bridge other logging libraries that may be used by other third party -> libraries you use (if you want to disable this behaviour, you need to explicitly exclude bridging -> libraries). - -=== "build.gradle" - - ```groovy - // Bridges - runtimeOnly("org.slf4j:jcl-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:log4j-over-slf4j:1.7.30") - ``` - -=== "pom.xml" - - ```xml - - org.slf4j - jcl-over-slf4j - 1.7.30 - - - org.slf4j - log4j-over-slf4j - 1.7.30 - - ``` - -# Package com.hexagonkt.logging.slf4j.jul -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_slf4j_jul/api/logging_slf4j_jul.api b/logging/logging_slf4j_jul/api/logging_slf4j_jul.api deleted file mode 100644 index 2c5d35cc98..0000000000 --- a/logging/logging_slf4j_jul/api/logging_slf4j_jul.api +++ /dev/null @@ -1,9 +0,0 @@ -public final class com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (ZLjava/io/PrintStream;)V - public synthetic fun (ZLjava/io/PrintStream;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - diff --git a/logging/logging_slf4j_jul/build.gradle.kts b/logging/logging_slf4j_jul/build.gradle.kts deleted file mode 100644 index 4429e768aa..0000000000 --- a/logging/logging_slf4j_jul/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ - -plugins { - id("java-library") -} - -apply(from = "$rootDir/gradle/kotlin.gradle") -apply(from = "$rootDir/gradle/publish.gradle") -apply(from = "$rootDir/gradle/dokka.gradle") -apply(from = "$rootDir/gradle/native.gradle") -apply(from = "$rootDir/gradle/detekt.gradle") - -description = "Hexagon SLF4J logging adapter (using JUL as SLF4J engine)." - -dependencies { - val slf4jVersion = properties["slf4jVersion"] - - "api"(project(":logging:logging_jul")) - "api"("org.slf4j:slf4j-jdk14:$slf4jVersion") - "api"("org.slf4j:jcl-over-slf4j:$slf4jVersion") - "api"("org.slf4j:log4j-over-slf4j:$slf4jVersion") -} diff --git a/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt b/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt deleted file mode 100644 index eb35ddf5c7..0000000000 --- a/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.hexagonkt.logging.slf4j.jul - -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.logging.jul.JulLoggingAdapter -import org.slf4j.Logger.ROOT_LOGGER_NAME -import org.slf4j.LoggerFactory -import java.io.PrintStream -import org.slf4j.Logger as Slf4jLogger - -class Slf4jJulLoggingAdapter( - messageOnly: Boolean = false, - stream: PrintStream = System.out -) : LoggingPort { - - private val julLoggingAdapter = JulLoggingAdapter(messageOnly, stream) - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: Slf4jLogger = LoggerFactory.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - when (level) { - TRACE -> if (log.isTraceEnabled) log.trace(message().toString()) - DEBUG -> if (log.isDebugEnabled) log.debug(message().toString()) - INFO -> if (log.isInfoEnabled) log.info(message().toString()) - WARN -> if (log.isWarnEnabled) log.warn(message().toString()) - ERROR -> if (log.isErrorEnabled) log.error(message().toString()) - OFF -> { /* Ignored */ } - } - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - when (level) { - TRACE -> - if (log.isTraceEnabled) log.trace(message(exception).toString(), exception) - DEBUG -> - if (log.isDebugEnabled) log.debug(message(exception).toString(), exception) - INFO -> - if (log.isInfoEnabled) log.info(message(exception).toString(), exception) - WARN -> - if (log.isWarnEnabled) log.warn(message(exception).toString(), exception) - ERROR -> - if (log.isErrorEnabled) log.error(message(exception).toString(), exception) - OFF -> { /* Ignored */ } - } - } - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - val loggerName = name.ifEmpty { ROOT_LOGGER_NAME } - julLoggingAdapter.setLoggerLevel(loggerName, level) - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - julLoggingAdapter.isLoggerLevelEnabled(name.ifEmpty { ROOT_LOGGER_NAME }, level) -} diff --git a/logging/logging_slf4j_jul/src/main/kotlin/module-info.java b/logging/logging_slf4j_jul/src/main/kotlin/module-info.java deleted file mode 100644 index 356775dc43..0000000000 --- a/logging/logging_slf4j_jul/src/main/kotlin/module-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_slf4j_jul { - - requires transitive kotlin.stdlib; - requires transitive com.hexagonkt.logging_jul; - requires transitive org.slf4j; - - exports com.hexagonkt.logging.slf4j.jul; -} diff --git a/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt b/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt deleted file mode 100644 index c49cf5eca9..0000000000 --- a/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.hexagonkt.logging.slf4j.jul - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class Slf4jJulLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using SLF4J JUL`() { - - LoggingManager.adapter = Slf4jJulLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties b/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/settings.gradle.kts b/settings.gradle.kts index 7cad87df8c..2b8bc1b283 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,7 +10,6 @@ include( includeNestedModules( "http", - "logging", "serialization", "templates" ) diff --git a/site/pages/index.md b/site/pages/index.md index 5feba69bbe..34c8364cd5 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -104,7 +104,7 @@ Module that provide functionality that does not depend on different implementati [handlers]. ## Manager -Singleton object to manage a cross toolkit aspect. I.e., Serialization, Logging or Templates. +Singleton object to manage a cross toolkit aspect. I.e., Serialization or Templates. [core]: /core [handlers]: /handlers From bc2a6c156800de5e4dd80ad576828925d50291c8 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sat, 27 Jan 2024 12:09:20 +0100 Subject: [PATCH 03/28] Add system setting fetching with default and use system loggers --- core/api/core.api | 16 ++---- .../src/main/kotlin/com/hexagonkt/core/Jvm.kt | 26 +++++----- .../com/hexagonkt/core/MultipleException.kt | 2 +- .../com/hexagonkt/core/logging/Logger.kt | 52 +++++++++++++------ .../com/hexagonkt/core/logging/Logging.kt | 49 +++++++++-------- .../hexagonkt/core/logging/LoggingManager.kt | 13 ----- .../test/kotlin/com/hexagonkt/core/JvmTest.kt | 17 ++---- .../kotlin/com/hexagonkt/core/NetworkTest.kt | 8 +-- .../com/hexagonkt/core/logging/LoggerTest.kt | 19 +++++++ .../core/logging/LoggingManagerTest.kt | 40 -------------- .../com/hexagonkt/core/logging/LoggingTest.kt | 20 +------ .../com/hexagonkt/core/text/StringsTest.kt | 2 +- gradle.properties | 4 +- 13 files changed, 113 insertions(+), 155 deletions(-) delete mode 100644 core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt delete mode 100644 core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt diff --git a/core/api/core.api b/core/api/core.api index ebf79b97f4..1529b11899 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -157,10 +157,10 @@ public final class com/hexagonkt/core/Jvm { public final fun getVersion ()Ljava/lang/String; public final fun getZoneId ()Ljava/time/ZoneId; public final fun isConsole ()Z - public final fun loadSystemSettings (Ljava/util/Map;Z)V - public static synthetic fun loadSystemSettings$default (Lcom/hexagonkt/core/Jvm;Ljava/util/Map;ZILjava/lang/Object;)V + public final fun loadSystemSettings (Ljava/util/Map;)V public final fun systemFlag (Ljava/lang/String;)Z public final fun systemSetting (Lkotlin/reflect/KClass;Ljava/lang/String;)Ljava/lang/Object; + public final fun systemSetting (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; public final fun systemSettingOrNull (Lkotlin/reflect/KClass;Ljava/lang/String;)Ljava/lang/Object; public final fun totalMemory ()Ljava/lang/String; public final fun usedMemory ()Ljava/lang/String; @@ -220,7 +220,8 @@ public final class com/hexagonkt/core/UuidsKt { } public final class com/hexagonkt/core/logging/Logger { - public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/System$Logger;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/System$Logger;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlin/reflect/KClass;)V public final fun debug (Lkotlin/jvm/functions/Function0;)V public final fun error (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V @@ -228,6 +229,7 @@ public final class com/hexagonkt/core/logging/Logger { public static synthetic fun error$default (Lcom/hexagonkt/core/logging/Logger;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun getName ()Ljava/lang/String; public final fun info (Lkotlin/jvm/functions/Function0;)V + public final fun isLoggable (Ljava/lang/System$Logger$Level;)Z public final fun log (Ljava/lang/System$Logger$Level;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V public final fun log (Ljava/lang/System$Logger$Level;Lkotlin/jvm/functions/Function0;)V public final fun trace (Lkotlin/jvm/functions/Function0;)V @@ -246,14 +248,6 @@ public final class com/hexagonkt/core/logging/LoggingKt { public static synthetic fun trace$default (Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; } -public final class com/hexagonkt/core/logging/LoggingManager { - public static final field INSTANCE Lcom/hexagonkt/core/logging/LoggingManager; - public final fun getDefaultLoggerName ()Ljava/lang/String; - public final fun getUseColor ()Z - public final fun setDefaultLoggerName (Ljava/lang/String;)V - public final fun setUseColor (Z)V -} - public final class com/hexagonkt/core/media/MediaType { public static final field Companion Lcom/hexagonkt/core/media/MediaType$Companion; public fun (Lcom/hexagonkt/core/media/MediaTypeGroup;Ljava/lang/String;)V diff --git a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt index 1ba4aeb608..35a60af29c 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt @@ -85,24 +85,16 @@ object Jvm { (runtime.totalMemory() - runtime.freeMemory()).let { "%,d".format(it / 1024) } /** - * Add a map to system properties, optionally overriding them. + * Add a map to system properties, overriding entries if already set. * * @param settings Data to be added to system properties. - * @param overwrite If true, overwrite existing entries with supplied data. */ - fun loadSystemSettings(settings: Map, overwrite: Boolean = false) { - settings.keys.forEach { - check(it.matches(systemSettingPattern)) { - "Property name must match $systemSettingPattern ($it)" - } + fun loadSystemSettings(settings: Map) { + settings.entries.forEach { (k, v) -> + val matchPattern = k.matches(systemSettingPattern) + check(matchPattern) { "Property name must match $systemSettingPattern ($k)" } + System.setProperty(k, v) } - - val systemProperties = System.getProperties() - val properties = - if (overwrite) settings.entries - else settings.entries.filter { !systemProperties.containsKey(it.key) } - - properties.forEach { (k, v) -> System.setProperty(k, v) } } /** @@ -122,6 +114,9 @@ object Jvm { systemSettingOrNull(type, name) ?: error("Required '${type.simpleName}' system setting '$name' not found") + fun systemSetting(type: KClass, name: String, defaultValue: T): T = + systemSettingOrNull(type, name) ?: defaultValue + /** * Retrieve a flag (boolean parameter) by name by looking in OS environment variables first and * in the JVM system properties if not found. @@ -147,6 +142,9 @@ object Jvm { inline fun systemSetting(name: String): T = systemSetting(T::class, name) + inline fun systemSetting(name: String, defaultValue: T): T = + systemSetting(T::class, name, defaultValue) + private fun systemSettingRaw(name: String): String? { val correctName = name.matches(systemSettingPattern) require(correctName) { "Setting name must match $systemSettingPattern" } diff --git a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt index 9ab99a6f06..a710700532 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt @@ -1,7 +1,7 @@ package com.hexagonkt.core /** - * Exception with a list of causes. Cause is `null` as it can't be tell which one of the list is the + * Exception with a list of causes. Cause is `null` as it can't be told which one of the list is the * cause. * * A coded multiple exception should be created this way: diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt index 62583c06c1..55d82cef7e 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt @@ -1,33 +1,50 @@ package com.hexagonkt.core.logging import com.hexagonkt.core.text.stripAnsi +import java.lang.System.Logger.Level import java.lang.System.Logger.Level.* import kotlin.reflect.KClass /** - * Logger class with Kotlin improvements like lazy evaluation. It is backed by a logging port. + * Logger class with Kotlin usability improvements. It is backed by a [System.Logger] instance. * * @param name Logger name. It is shown in the logs messages and used for log filtering. * @sample com.hexagonkt.core.logging.LoggerTest.loggerUsage */ -class Logger(val name: String) { - - internal val log: System.Logger = System.getLogger(name) +class Logger( + val name: String, + internal val logger: System.Logger = System.getLogger(name) +) { + /** + * Check if this logger is enabled for a given log level. + * + * @param level Level to check. + * @return True if this logger is enabled for the supplied level. + */ + fun isLoggable(level: Level): Boolean = + logger.isLoggable(level) /** * Log a message, with associated exception information. + * + * @param level Level used in the log statement. + * @param exception The exception associated with log message. + * @param message The message supplier to use in the log statement. */ - fun log(level: System.Logger.Level, exception: E, message: (E) -> Any?) { - if (LoggingManager.useColor) message(exception) - else message(exception)?.toString()?.stripAnsi() - log.log(level, { message(exception)?.toString() }, exception) + fun log(level: Level, exception: E, message: (E) -> Any?) { + val messageSupplier = { stripAnsi(message(exception), useColor) } + logger.log(level, messageSupplier, exception) } /** * Log a message. + * + * @param level Level used in the log statement. + * @param message The message supplier to use in the log statement. */ - fun log(level: System.Logger.Level, message: () -> Any?) { - log.log(level, message) + fun log(level: Level, message: () -> Any?) { + val messageSupplier = { stripAnsi(message(), useColor) } + logger.log(level, messageSupplier) } /** @@ -44,7 +61,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun trace(message: () -> Any?) { - log.log(TRACE, message) + logger.log(TRACE, message) } /** @@ -53,7 +70,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun debug(message: () -> Any?) { - log.log(DEBUG, message) + logger.log(DEBUG, message) } /** @@ -62,7 +79,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun info(message: () -> Any?) { - log.log(INFO, message) + logger.log(INFO, message) } /** @@ -71,7 +88,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun warn(message: () -> Any?) { - log.log(WARNING, message) + logger.log(WARNING, message) } /** @@ -80,7 +97,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun error(message: () -> Any?) { - log.log(ERROR, message) + logger.log(ERROR, message) } /** @@ -98,10 +115,13 @@ class Logger(val name: String) { * Log a message using [ERROR] level with associated exception information. * * @param exception The exception associated with log message. - * @param message The message to log (optional). If not supplied it will be empty. + * @param message The message to log (function to optional). If not supplied it will be empty. */ fun error(exception: E?, message: (E?) -> Any? = { "" }) { if (exception == null) log(ERROR) { message(null) } else log(ERROR, exception, message) } + + internal fun stripAnsi(receiver: T?, apply: Boolean): String? = + receiver?.toString()?.let { if (apply) it.stripAnsi() else it } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt index 4c26959c84..eb5876a1c9 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt @@ -1,46 +1,51 @@ package com.hexagonkt.core.logging +import java.lang.System.Logger.Level.* +import com.hexagonkt.core.Jvm + +internal val useColor: Boolean by lazy { Jvm.systemSetting("hexagonkt_logging_color", true) } +internal val defaultLoggerName: String by lazy { + Jvm.systemSetting("hexagonkt_logging_logger_name", "com.hexagonkt.core.logging") +} + /** Default logger for when you feel too lazy to declare one. */ -val logger: Logger by lazy { Logger(LoggingManager.defaultLoggerName) } +val logger: Logger by lazy { Logger(defaultLoggerName) } /** - * Uses this [T] to log a message with a prefix using [TRACE][LoggingLevel.TRACE] level. - * - * com.hexagonkt.core.logging.Logger must have TRACE level + * Use this [T] to log a message with a prefix using [TRACE] level. * - * TODO Add use case and example in documentation. + * [com.hexagonkt.core.logging.logger] must have the [TRACE] level enabled. * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.trace(prefix: String = ""): T = apply { logger.trace { "$prefix$this" } } /** - * Uses this [T] to log a message with a prefix using [DEBUG][LoggingLevel.DEBUG] level. + * Use this [T] to log a message with a prefix using [DEBUG] level. * - * com.hexagonkt.core.logging.Logger must have DEBUG level + * [com.hexagonkt.core.logging.logger] must have the [DEBUG] level enabled. * - * TODO Add use case and example in documentation. - * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.debug(prefix: String = ""): T = apply { logger.debug { "$prefix$this" } } /** - * Uses this [T] to log a message with a prefix using [INFO][LoggingLevel.INFO] level. - * - * com.hexagonkt.core.logging.Logger must have INFO level + * Use this [T] to log a message with a prefix using [INFO] level. * - * TODO Add use case and example in documentation. + * [com.hexagonkt.core.logging.logger] must have the [INFO] level enabled. * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.info(prefix: String = ""): T = apply { logger.info { "$prefix$this" } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt deleted file mode 100644 index 9e06d23065..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * Manages Logs. - */ -object LoggingManager { - var useColor: Boolean = true - var defaultLoggerName: String = "com.hexagonkt.core.logging" - set(value) { - require(value.isNotEmpty()) { "Default logger name cannot be empty string" } - field = value - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt index 8c03346695..64c83493e6 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt @@ -23,23 +23,14 @@ internal class JvmTest { mapOf("s1" to "v1", "s2" to "v2").forEach { (k, v) -> System.setProperty(k, v) } Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2")) - assertEquals("v1", System.getProperty("s1")) - assertEquals("v2", System.getProperty("s2")) + assertEquals("x1", System.getProperty("s1")) + assertEquals("x2", System.getProperty("s2")) - Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2", "s3" to "x3")) + Jvm.loadSystemSettings(mapOf("s1" to "v1", "s2" to "v2", "s3" to "x3")) assertEquals("v1", System.getProperty("s1")) assertEquals("v2", System.getProperty("s2")) assertEquals("x3", System.getProperty("s3")) - Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2"), true) - assertEquals("x1", System.getProperty("s1")) - assertEquals("x2", System.getProperty("s2")) - - Jvm.loadSystemSettings(mapOf("s1" to "z1", "s2" to "z2", "s3" to "z3"), true) - assertEquals("z1", System.getProperty("s1")) - assertEquals("z2", System.getProperty("s2")) - assertEquals("z3", System.getProperty("s3")) - val e = assertFailsWith { Jvm.loadSystemSettings(mapOf("1" to "v")) } assertEquals("Property name must match [_A-Za-z]+[_A-Za-z0-9]* (1)", e.message) } @@ -214,7 +205,9 @@ internal class JvmTest { assert(Jvm.systemSetting("PATH").isNotEmpty()) assert(Jvm.systemSetting("path").isNotEmpty()) + assertNotEquals("default", Jvm.systemSetting("path", "default")) assertNull(Jvm.systemSettingOrNull("_not_defined_")) + assertEquals("default", Jvm.systemSetting("_not_defined_", "default")) System.setProperty("PATH", "path override") assert(Jvm.systemSetting("PATH") != "path override") diff --git a/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt b/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt index 73a63c3545..3a58cab177 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt @@ -76,9 +76,9 @@ internal class NetworkTest { } @Test fun `URL check works properly`() { - assertTrue { urlOf("http://example.com").responseSuccessful() } - assertFalse { urlOf("http://invalid-domain.z").responseSuccessful() } - assertTrue { urlOf("http://example.com").responseFound() } - assertFalse { urlOf("http://example.com/nothing").responseFound() } + assertTrue { urlOf("https://example.com").responseSuccessful() } + assertFalse { urlOf("https://invalid-domain.z").responseSuccessful() } + assertTrue { urlOf("https://example.com").responseFound() } + assertFalse { urlOf("https://example.com/nothing").responseFound() } } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt index ffa37dc4f1..377ea3f6ef 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt @@ -1,5 +1,9 @@ package com.hexagonkt.core.logging +import com.hexagonkt.core.text.Ansi.RESET +import com.hexagonkt.core.text.AnsiColor.BRIGHT_WHITE +import com.hexagonkt.core.text.AnsiColor.RED_BG +import com.hexagonkt.core.text.AnsiEffect.UNDERLINE import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.condition.DisabledInNativeImage @@ -68,6 +72,21 @@ internal class LoggerTest { assert(Logger("name"::class).name == "kotlin.String") } + @Test fun `A logger can be queried for its enabled state on a given level`() { + assert(Logger("name").isLoggable(ERROR)) + assertFalse(Logger("name").isLoggable(TRACE)) + } + + @Test fun `ANSI testing`() { + val l = Logger("name") + val message = "$RED_BG$BRIGHT_WHITE${UNDERLINE}ANSI$RESET normal" + val noAnsiMessage = l.stripAnsi(message, true) + val ansiMessage = l.stripAnsi(message, false) + assertEquals(message, ansiMessage) + assertNotEquals(message, noAnsiMessage) + assertContentEquals(noAnsiMessage?.toByteArray(), "ANSI normal".toByteArray()) + } + @Test @DisabledInNativeImage fun `Invalid class name raises error`() { diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt deleted file mode 100644 index cdf87d63e2..0000000000 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.hexagonkt.core.logging - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.condition.DisabledInNativeImage -import kotlin.IllegalStateException -import kotlin.reflect.KClass -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -internal class LoggingManagerTest { - - @Test fun `'defaultLoggerName' can be changed`() { - val dln = LoggingManager.defaultLoggerName - - LoggingManager.defaultLoggerName = "com.example" - assertEquals("com.example", LoggingManager.defaultLoggerName) - - LoggingManager.defaultLoggerName = dln - } - - @Test fun `'defaultLoggerName' cannot be set to empty string`() { - val e = assertFailsWith { - LoggingManager.defaultLoggerName = "" - } - - assertEquals("Default logger name cannot be empty string", e.message) - } - - @Test - @DisabledInNativeImage - fun `Problem reading class name raises error`() { - val kc = mockk>() - every { kc.qualifiedName } returns null - - assertFailsWith { Logger(kc) } - .apply { assertEquals("Cannot get qualified name of type", message) } - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt index 1b6e3d52b6..381596c91f 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt @@ -6,25 +6,7 @@ import kotlin.test.assertEquals internal class LoggingTest { @Test fun `Log helpers`() { - LoggingManager.useColor = false - assertEquals(LoggingManager.defaultLoggerName, logger.name) - - assertEquals("foo", "foo".trace(">>> ")) - assertEquals("foo", "foo".trace()) - assertEquals(null, null.trace()) - assertEquals("text", "text".trace()) - - assertEquals("foo", "foo".debug(">>> ")) - assertEquals("foo", "foo".debug()) - assertEquals(null, null.debug()) - assertEquals("text", "text".debug()) - - assertEquals("foo", "foo".info(">>> ")) - assertEquals("foo", "foo".info()) - assertEquals(null, null.info()) - assertEquals("text", "text".info()) - - LoggingManager.useColor = true + assertEquals(defaultLoggerName, logger.name) assertEquals("foo", "foo".trace(">>> ")) assertEquals("foo", "foo".trace()) diff --git a/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt b/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt index 3fff5af84f..5fe16c8a68 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt @@ -131,7 +131,7 @@ internal class StringsTest { Double::class to "4.3", String::class to "text", InetAddress::class to "127.0.0.1", - URL::class to "http://example.com", + URL::class to "https://example.com", URI::class to "schema://host:0/file", File::class to "/absolute/file.txt", LocalDate::class to "2020-12-31", diff --git a/gradle.properties b/gradle.properties index 76cb9ef794..9128d0f3cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -46,7 +46,7 @@ nettyVersion=4.1.106.Final nettyTcNativeVersion=2.0.62.Final # http_server_helidon -helidonVersion=4.0.3 +helidonVersion=4.0.4 # http_server_servlet servletVersion=6.0.0 @@ -64,7 +64,7 @@ dslJsonVersion=2.0.2 freemarkerVersion=2.3.32 # templates_jte -jteVersion=3.1.6 +jteVersion=3.1.8 # templates_pebble pebbleVersion=3.2.2 From b5713337e24dcea88a7a0990919b230f962cf376 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sat, 27 Jan 2024 17:05:52 +0100 Subject: [PATCH 04/28] Delete unused callback --- gradle.properties | 1 - http/rest_tools/build.gradle.kts | 2 - .../rest/tools/openapi/OpenApiHandler.kt | 252 -------------- .../rest/tools/openapi/VerifySpecCallback.kt | 166 +++++++++- .../rest/tools/openapi/OpenApiHandlerTest.kt | 311 ------------------ .../tools/openapi/SpecExampleCallbackTest.kt | 283 ++++++++++++++++ .../tools/openapi/VerifySpecCallbackTest.kt | 2 +- 7 files changed, 447 insertions(+), 570 deletions(-) delete mode 100644 http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt delete mode 100644 http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt create mode 100644 http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt diff --git a/gradle.properties b/gradle.properties index 9128d0f3cd..a25af47181 100644 --- a/gradle.properties +++ b/gradle.properties @@ -54,7 +54,6 @@ jettyVersion=12.0.5 # rest_tools swaggerRequestValidatorVersion=2.40.0 -vertxVersion=4.5.1 # serialization jacksonVersion=2.16.1 diff --git a/http/rest_tools/build.gradle.kts b/http/rest_tools/build.gradle.kts index 39aa3babcf..eecbd14b3a 100644 --- a/http/rest_tools/build.gradle.kts +++ b/http/rest_tools/build.gradle.kts @@ -13,12 +13,10 @@ description = "Tools to test and document REST services." dependencies { val swaggerRequestValidatorVersion = properties["swaggerRequestValidatorVersion"] - val vertxVersion = properties["vertxVersion"] "api"(project(":http:rest")) "api"(project(":http:http_server")) "api"(project(":http:http_client")) - "api"("io.vertx:vertx-openapi:$vertxVersion") "api"("com.atlassian.oai:swagger-request-validator-core:$swaggerRequestValidatorVersion") "testImplementation"(project(":http:http_client_jetty")) diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt deleted file mode 100644 index 45768a25a1..0000000000 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt +++ /dev/null @@ -1,252 +0,0 @@ -package com.hexagonkt.rest.tools.openapi - -import com.hexagonkt.core.fail -import com.hexagonkt.core.require -import com.hexagonkt.http.model.BAD_REQUEST_400 -import com.hexagonkt.http.model.HttpMethod -import com.hexagonkt.http.model.HttpMethod.* -import com.hexagonkt.http.model.UNAUTHORIZED_401 -import com.hexagonkt.http.handlers.* - -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.PathItem -import io.swagger.v3.oas.models.media.MediaType -import io.swagger.v3.oas.models.parameters.Parameter -import io.swagger.v3.oas.models.responses.ApiResponse -import io.swagger.v3.oas.models.security.SecurityRequirement -import io.swagger.v3.oas.models.security.SecurityScheme -import io.swagger.v3.oas.models.security.SecurityScheme.Type -import io.swagger.v3.parser.OpenAPIV3Parser - -internal class OpenApiHandler(pathToSpec: String) { - - private val openAPIParser = OpenAPIV3Parser() - private val openAPISpec: OpenAPI = openAPIParser.read(pathToSpec) - ?: error("OpenAPI Spec could not be read. Please check the file's path and its format") - - fun createServer(): HttpHandler = - PathHandler( - "", - openAPISpec.paths.map { (path: String, pathItem: PathItem) -> - when { - pathItem.get != null -> createHandler(GET, path, pathItem.get) - pathItem.head != null -> createHandler(HEAD, path, pathItem.head) - pathItem.post != null -> createHandler(POST, path, pathItem.post) - pathItem.put != null -> createHandler(PUT, path, pathItem.put) - pathItem.delete != null -> createHandler(DELETE, path, pathItem.delete) - pathItem.trace != null -> createHandler(TRACE, path, pathItem.trace) - pathItem.options != null -> createHandler(OPTIONS, path, pathItem.options) - pathItem.patch != null -> createHandler(PATCH, path, pathItem.patch) - else -> error("Unsupported method") - } - } - ) - - private fun createHandler(method: HttpMethod, path: String, operation: Operation): HttpHandler = - OnHandler(method, path, handleRequest(operation)) - - private fun handleRequest(operation: Operation): HttpCallbackType = - { - verifyAuth(operation, this) - ?: verifyParams(operation, this) - ?: verifyBody(operation, this) - ?: ok( - getResponseContentForStatus( - operation, - status = 200, - exampleName = request.headers["x-mock-response-example"]?.string() - ) - ) - } - - private fun getResponseContentForStatus( - operation: Operation, status: Int, exampleName: String? = null - ): String { - - val responsesForStatus: ApiResponse = operation.responses[status.toString()] - ?: error("The OpenAPI Spec contains no responses for this operation") - val jsonResponses: MediaType = responsesForStatus.content["application/json"] - ?: error("The OpenAPI Spec contains no JSON responses for this operation") - - return if (exampleName != null) - jsonResponses.examples[exampleName]?.value.toString() - else - (getExampleFromSchema(jsonResponses) ?: getExampleFromMediaType(jsonResponses)) - ?.toString() - ?: error("The OpenAPI Spec contains no response examples for this operation") - } - - private fun getExampleFromSchema(mediaType: MediaType) = - mediaType.schema?.example - - private fun getExampleFromMediaType(mediaType: MediaType): Any? = - if (mediaType.example != null) mediaType.example - else mediaType.examples?.toList()?.get(0)?.second?.value - - private fun verifyAuth(operation: Operation, call: HttpContext): HttpContext? { - if (operation.security == null || operation.security.size == 0 - || containsEmptySecurityRequirement(operation)) return null - - // Any one of the security mechanisms need to be satisfied - return if (!operation.security.any { securityRequirement -> - verifySecurityRequirement(securityRequirement, call) - }) { - call.send(status = UNAUTHORIZED_401, body = getResponseContentForStatus(operation, 401)) - } - else null - } - - private fun containsEmptySecurityRequirement(operation: Operation): Boolean = - operation.security.any { it.size == 0 } - - private fun verifySecurityRequirement( - securityRequirement: SecurityRequirement, call: HttpContext): Boolean = - securityRequirement.keys.all { verifySecurityScheme(it, call) } - - private fun verifySecurityScheme(schemeName: String, call: HttpContext): Boolean { - val securityScheme = openAPISpec.components.securitySchemes[schemeName] - ?: error("The OpenAPI Spec contains no security scheme component for $schemeName") - - return when (securityScheme.type) { - Type.APIKEY -> validateApiKey(securityScheme, call) - Type.HTTP -> validateHttpAuth(securityScheme, call) - else -> error("Mock Server only supports HTTP and API Key authentication") - } - } - - private fun validateApiKey(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.`in`) { - SecurityScheme.In.QUERY -> - call.request.queryParameters[securityScheme.name]?.string()?.isNotBlank() ?: fail - SecurityScheme.In.HEADER -> - call.request.headers[securityScheme.name]?.string().isNullOrBlank() - SecurityScheme.In.COOKIE -> - call.request.cookiesMap()[securityScheme.name] != null - else -> - error("Unknown `in` value found in OpenAPI Spec for security scheme") - } - - private fun validateHttpAuth(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.scheme.lowercase()) { - "basic" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Basic") - } ?: false - } - "bearer" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Bearer") - } ?: false - } - else -> - error("Mock Server only supports Basic and Bearer HTTP Authentication") - } - - private fun verifyParams(operation: Operation, call: HttpContext): HttpContext? { - operation.parameters?.forEach { parameter -> - when (parameter.`in`) { - "path" -> { - if (!verifyPathParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "query" -> { - if (!verifyQueryParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "header" -> { - if (!verifyHeaderParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "cookie" -> { - if (!verifyCookieParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - } - } - - return null - } - - private fun verifyPathParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.pathParameters[parameter.name].isNullOrBlank()) return false - parameter.schema.enum?.let { - if (call.pathParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyQueryParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.queryParameters.require(parameter.name).string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.queryParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyHeaderParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.headers[parameter.name]?.string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.headers[parameter.name] !in it) return false - } - return true - } - - private fun verifyCookieParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.cookiesMap()[parameter.name] == null) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.cookiesMap()[parameter.name]?.value !in it) return false - } - return true - } - - private fun verifyBody(operation: Operation, call: HttpContext): HttpContext? { - operation.requestBody?.let { requestBody -> - if (requestBody.required && call.request.bodyString().isBlank()) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus(operation, 400) - ) - } - } - return null - } -} diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt index 4defd44908..1e8b40f338 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt @@ -8,24 +8,39 @@ import com.atlassian.oai.validator.model.Request.Method import com.atlassian.oai.validator.model.SimpleRequest import com.atlassian.oai.validator.model.SimpleResponse import com.atlassian.oai.validator.report.ValidationReport +import com.hexagonkt.core.fail +import com.hexagonkt.core.require import com.hexagonkt.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext +import com.hexagonkt.http.model.BAD_REQUEST_400 import com.hexagonkt.http.model.ContentType import com.hexagonkt.http.model.HttpMethod import com.hexagonkt.http.model.HttpMethod.* +import com.hexagonkt.http.model.UNAUTHORIZED_401 +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.parameters.Parameter +import io.swagger.v3.oas.models.security.SecurityRequirement +import io.swagger.v3.oas.models.security.SecurityScheme +import io.swagger.v3.oas.models.security.SecurityScheme.Type +import io.swagger.v3.parser.OpenAPIV3Parser import java.net.URL import kotlin.jvm.optionals.getOrNull /** * Callback that verifies server calls comply with a given OpenAPI spec. - * - * TODO Use https://vertx.io/docs/vertx-openapi/java */ class VerifySpecCallback(spec: URL) : HttpCallback { private val messagePrefix: String = "\n- " + private val specText: String = spec.readText() + private val validator: OpenApiInteractionValidator = - createForInlineApiSpecification(spec.readText()).build() + createForInlineApiSpecification(specText).build() + + private val openAPIParser = OpenAPIV3Parser() + private val openAPISpec: OpenAPI = openAPIParser.read(spec.path) + ?: error("OpenAPI Spec could not be read. Please check the file's path and its format") override fun invoke(context: HttpContext): HttpContext { val requestReport = validator.validateRequest(request(context)) @@ -110,4 +125,149 @@ class VerifySpecCallback(spec: URL) : HttpCallback { OPTIONS -> Method.OPTIONS PATCH -> Method.PATCH } + + private fun verifyAuth(operation: Operation, call: HttpContext): HttpContext? { + if (operation.security == null + || operation.security.size == 0 + || containsEmptySecurityRequirement(operation)) + return null + + // Any one of the security mechanisms need to be satisfied + return if (!operation.security.any { securityRequirement -> verifySecurityRequirement(securityRequirement, call) }) { + call.send(status = UNAUTHORIZED_401) + } + else null + } + + private fun verifySecurityRequirement( + securityRequirement: SecurityRequirement, call: HttpContext): Boolean = + securityRequirement.keys.all { verifySecurityScheme(it, call) } + + private fun verifySecurityScheme(schemeName: String, call: HttpContext): Boolean { + val securityScheme = openAPISpec.components.securitySchemes[schemeName] + ?: error("The OpenAPI Spec contains no security scheme component for $schemeName") + + return when (securityScheme.type) { + Type.APIKEY -> validateApiKey(securityScheme, call) + Type.HTTP -> validateHttpAuth(securityScheme, call) + else -> error("Mock Server only supports HTTP and API Key authentication") + } + } + + private fun validateApiKey(securityScheme: SecurityScheme, call: HttpContext): Boolean = + when (securityScheme.`in`) { + SecurityScheme.In.QUERY -> + call.request.queryParameters[securityScheme.name]?.string()?.isNotBlank() ?: fail + SecurityScheme.In.HEADER -> + call.request.headers[securityScheme.name]?.string().isNullOrBlank() + SecurityScheme.In.COOKIE -> + call.request.cookiesMap()[securityScheme.name] != null + else -> + error("Unknown `in` value found in OpenAPI Spec for security scheme") + } + + private fun validateHttpAuth(securityScheme: SecurityScheme, call: HttpContext): Boolean = + when (securityScheme.scheme.lowercase()) { + "basic" -> { + call.request.headers["authorization"]?.string()?.let { authString -> + authString.isNotBlank() && authString.startsWith("Basic") + } ?: false + } + "bearer" -> { + call.request.headers["authorization"]?.string()?.let { authString -> + authString.isNotBlank() && authString.startsWith("Bearer") + } ?: false + } + else -> + error("Mock Server only supports Basic and Bearer HTTP Authentication") + } + + private fun verifyParams(operation: Operation, call: HttpContext): HttpContext? { + operation.parameters?.forEach { parameter -> + when (parameter.`in`) { + "path" -> { + if (!verifyPathParam(parameter, call)) { + call.send( + status = BAD_REQUEST_400, + ) + } + } + "query" -> { + if (!verifyQueryParam(parameter, call)) { + call.send( + status = BAD_REQUEST_400, + ) + } + } + "header" -> { + if (!verifyHeaderParam(parameter, call)) { + call.send( + status = BAD_REQUEST_400, + ) + } + } + "cookie" -> { + if (!verifyCookieParam(parameter, call)) { + call.send( + status = BAD_REQUEST_400, + ) + } + } + } + } + + return null + } + + private fun verifyPathParam(parameter: Parameter, call: HttpContext): Boolean { + if (call.pathParameters[parameter.name].isNullOrBlank()) return false + parameter.schema.enum?.let { + if (call.pathParameters[parameter.name] !in it) return false + } + return true + } + + private fun verifyQueryParam(parameter: Parameter, call: HttpContext): Boolean { + if (call.request.queryParameters.require(parameter.name).string().isNullOrBlank()) { + return !parameter.required + } + parameter.schema.enum?.let { + if (call.request.queryParameters[parameter.name] !in it) return false + } + return true + } + + private fun verifyHeaderParam(parameter: Parameter, call: HttpContext): Boolean { + if (call.request.headers[parameter.name]?.string().isNullOrBlank()) { + return !parameter.required + } + parameter.schema.enum?.let { + if (call.request.headers[parameter.name] !in it) return false + } + return true + } + + private fun verifyCookieParam(parameter: Parameter, call: HttpContext): Boolean { + if (call.request.cookiesMap()[parameter.name] == null) { + return !parameter.required + } + parameter.schema.enum?.let { + if (call.request.cookiesMap()[parameter.name]?.value !in it) return false + } + return true + } + + private fun verifyBody(operation: Operation, call: HttpContext): HttpContext? { + operation.requestBody?.let { requestBody -> + if (requestBody.required && call.request.bodyString().isBlank()) { + call.send( + status = BAD_REQUEST_400, + ) + } + } + return null + } + + private fun containsEmptySecurityRequirement(operation: Operation): Boolean = + operation.security.any { it.size == 0 } } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt deleted file mode 100644 index be90ade816..0000000000 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt +++ /dev/null @@ -1,311 +0,0 @@ -package com.hexagonkt.rest.tools.openapi - -import com.hexagonkt.core.urlOf -import com.hexagonkt.http.client.HttpClient -import com.hexagonkt.http.client.HttpClientSettings -import com.hexagonkt.http.client.jetty.JettyClientAdapter -import com.hexagonkt.http.model.* -import com.hexagonkt.http.server.HttpServer -import com.hexagonkt.http.server.jetty.JettyServletAdapter -import org.junit.jupiter.api.* -import kotlin.test.assertEquals - -@Disabled // TODO Fix this functionality -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class OpenApiHandlerTest { - - private val server by lazy { - HttpServer(JettyServletAdapter(), OpenApiHandler("openapi_test.json").createServer()) - } - - private val client by lazy { - val settings = HttpClientSettings(urlOf("http://localhost:${server.runtimePort}")) - HttpClient(JettyClientAdapter(), settings) - } - - @BeforeAll fun setUp() { - server.start() - client.start() - assert(server.started()) - } - - @AfterAll fun tearDown() { - client.stop() - server.stop() - } - - @Test fun `Basic routes are created correctly`() { - val response = client.get("/ping") - assertEquals(OK_200, response.status) - assertEquals("pong", response.body) - } - - @Test fun `Examples are fetched from media-type schema correctly`() { - val response = client.get("/get-example-from-schema") - assertEquals(OK_200, response.status) - assertEquals("response", response.body) - } - - @Test fun `Examples are fetched from media type correctly`() { - val response = client.get("/get-example-from-mediatype") - assertEquals(OK_200, response.status) - assertEquals("response", response.body) - } - - @Test fun `Examples are fetched from multiple examples correctly`() { - val response = client.get("/get-from-multiple-examples") - assertEquals(OK_200, response.status) - assert(response.body in listOf("foo", "bar")) - } - - @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { - val headers = Headers(Header("x-mock-response-example", "example2")) - val response = client.get("/get-from-multiple-examples", headers = headers) - assertEquals(OK_200, response.status) - assertEquals("bar", response.body) - } - - @Test fun `Empty string is returned if no examples specified`() { - val response = client.get("/get-from-no-examples") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - } - - @Test fun `Paths not present in OpenAPI spec return 404`() { - val response = client.get("/unknown-path") - assertEquals(NOT_FOUND_404, response.status) - } - - @Test fun `Required query params are verified correctly`() { - val response1 = client.get("/check-query-param") - assertEquals(BAD_REQUEST_400, response1.status) - assertEquals("invalid or missing query param", response1.body) - - val response2 = client.get("/check-query-param?queryParam=aValidValue") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val response3 = client.get("/check-query-param?queryParam=anInvalidValue") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing query param", response3.body) - } - - @Test fun `Optional query params are verified correctly`() { - val response1 = client.get("/check-optional-query-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing query param", response3.body) - } - - @Test fun `Path params are verified correctly`() { - val response1 = client.get("/check-path-param/aValidValue") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-path-param/anInvalidValue") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing path param", response2.body) - } - - @Test fun `Required header params are verified correctly`() { - val response1 = client.get("/check-header-param") - assertEquals(BAD_REQUEST_400, response1.status) - assertEquals("invalid or missing header param", response1.body) - - val validHeaders = Headers(Header("header-param", "aValidValue")) - val response2 = client.get("/check-header-param", headers = validHeaders) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) - val response3 = client.get("/check-header-param", headers = invalidHeaders) - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing header param", response3.body) - } - - @Test fun `Optional header params are verified correctly`() { - val response1 = client.get("/check-optional-header-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val validHeaders = Headers(Header("header-param", "aValidValue")) - val response2 = client.get("/check-optional-header-param", headers = validHeaders) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) - val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing header param", response3.body) - } - - @Test fun `Required cookies are verified correctly`() { - client.cookies += Cookie("cookieParam", "aValidValue") - val response1 = client.get("/check-cookie-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - client.cookies = emptyList() - - client.cookies += Cookie("cookieParam", "anInvalidValue") - val response2 = client.get("/check-cookie-param") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing cookie param", response2.body) - client.cookies = emptyList() - - val response3 = client.get("/check-cookie-param") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing cookie param", response3.body) - } - - @Test fun `Optional cookies are verified correctly`() { - client.cookies += Cookie("cookieParam", "aValidValue") - val response1 = client.get("/check-optional-cookie-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - client.cookies = emptyList() - - client.cookies += Cookie("cookieParam", "anInvalidValue") - val response2 = client.get("/check-optional-cookie-param") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing cookie param", response2.body) - client.cookies = emptyList() - - val response3 = client.get("/check-optional-cookie-param") - assertEquals(OK_200, response3.status) - assertEquals("success", response3.body) - } - - @Test fun `Body is verified correctly`() { - val response1 = client.get("/check-body", body = "Some body content") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-body") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing request body", response2.body) - } - - @Test fun `If Authorization is optional, it is skipped`() { - val response1 = client.get("/check-optional-auth") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response2 = client.get("/check-optional-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Basic HTTP Authentication is verified correctly`() { - val response1 = client.get("/check-basic-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response2 = client.get("/check-basic-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Bearer HTTP Authentication is verified correctly`() { - val response1 = client.get("/check-bearer-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) - val response2 = client.get("/check-bearer-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `HTTP Authentication with unknown scheme throws error`() { - val response = client.get("/check-unknown-auth") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) - } - - @Test fun `Query param API Key Authentication is verified correctly`() { - val response1 = client.get("/check-query-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val response2 = client.get("/check-query-api-auth?api-key=abcdefg") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Header API Key Authentication is verified correctly`() { - val response1 = client.get("/check-header-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("api-key", "abcdefg")) - val response2 = client.get("/check-header-api-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Cookie API Key Authentication is verified correctly`() { - val response1 = client.get("/check-cookie-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-cookie-api-auth") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - client.cookies = emptyList() - } - - @Test fun `Unknown location API Key Authentication throws error`() { - val response = client.get("/check-unknown-api-auth") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) - } - - @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { - val response1 = client.get("/check-multiple-mechanisms") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-multiple-mechanisms") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - client.cookies = emptyList() - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response3 = client.get("/check-multiple-mechanisms", headers = headers) - assertEquals(OK_200, response3.status) - assertEquals("success", response3.body) - } - - @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { - val response1 = client.get("/check-multiple-mechanisms") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-multiple-schemes") - assertEquals(UNAUTHORIZED_401, response2.status) - assertEquals("Invalid authorization credentials", response2.body) - client.cookies = emptyList() - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response3 = client.get("/check-multiple-schemes", headers = headers) - assertEquals(UNAUTHORIZED_401, response3.status) - assertEquals("Invalid authorization credentials", response3.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response4 = client.get("/check-multiple-schemes", headers = headers) - assertEquals(OK_200, response4.status) - assertEquals("success", response4.body) - client.cookies = emptyList() - } -} diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt new file mode 100644 index 0000000000..688d3b6213 --- /dev/null +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt @@ -0,0 +1,283 @@ +package com.hexagonkt.rest.tools.openapi + +import org.junit.jupiter.api.* + +@Disabled // TODO Fix this functionality +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class SpecExampleCallbackTest { + +// @Test fun `Basic routes are created correctly`() { +// val response = SpecExampleCallback("").process(HttpRequest(GET, path = "/ping")) +// assertEquals(OK_200, response.status) +// assertEquals("pong", response.response.body) +// } +// +// @Test fun `Examples are fetched from media-type schema correctly`() { +// val response = client.get("/get-example-from-schema") +// assertEquals(OK_200, response.status) +// assertEquals("response", response.body) +// } +// +// @Test fun `Examples are fetched from media type correctly`() { +// val response = client.get("/get-example-from-mediatype") +// assertEquals(OK_200, response.status) +// assertEquals("response", response.body) +// } +// +// @Test fun `Examples are fetched from multiple examples correctly`() { +// val response = client.get("/get-from-multiple-examples") +// assertEquals(OK_200, response.status) +// assert(response.body in listOf("foo", "bar")) +// } +// +// @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { +// val headers = Headers(Header("x-mock-response-example", "example2")) +// val response = client.get("/get-from-multiple-examples", headers = headers) +// assertEquals(OK_200, response.status) +// assertEquals("bar", response.body) +// } +// +// @Test fun `Empty string is returned if no examples specified`() { +// val response = client.get("/get-from-no-examples") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// } +// +// @Test fun `Paths not present in OpenAPI spec return 404`() { +// val response = client.get("/unknown-path") +// assertEquals(NOT_FOUND_404, response.status) +// } +// +// @Test fun `Required query params are verified correctly`() { +// val response1 = client.get("/check-query-param") +// assertEquals(BAD_REQUEST_400, response1.status) +// assertEquals("invalid or missing query param", response1.body) +// +// val response2 = client.get("/check-query-param?queryParam=aValidValue") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val response3 = client.get("/check-query-param?queryParam=anInvalidValue") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing query param", response3.body) +// } +// +// @Test fun `Optional query params are verified correctly`() { +// val response1 = client.get("/check-optional-query-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing query param", response3.body) +// } +// +// @Test fun `Path params are verified correctly`() { +// val response1 = client.get("/check-path-param/aValidValue") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-path-param/anInvalidValue") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing path param", response2.body) +// } +// +// @Test fun `Required header params are verified correctly`() { +// val response1 = client.get("/check-header-param") +// assertEquals(BAD_REQUEST_400, response1.status) +// assertEquals("invalid or missing header param", response1.body) +// +// val validHeaders = Headers(Header("header-param", "aValidValue")) +// val response2 = client.get("/check-header-param", headers = validHeaders) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) +// val response3 = client.get("/check-header-param", headers = invalidHeaders) +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing header param", response3.body) +// } +// +// @Test fun `Optional header params are verified correctly`() { +// val response1 = client.get("/check-optional-header-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val validHeaders = Headers(Header("header-param", "aValidValue")) +// val response2 = client.get("/check-optional-header-param", headers = validHeaders) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) +// val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing header param", response3.body) +// } +// +// @Test fun `Required cookies are verified correctly`() { +// client.cookies += Cookie("cookieParam", "aValidValue") +// val response1 = client.get("/check-cookie-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// client.cookies = emptyList() +// +// client.cookies += Cookie("cookieParam", "anInvalidValue") +// val response2 = client.get("/check-cookie-param") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing cookie param", response2.body) +// client.cookies = emptyList() +// +// val response3 = client.get("/check-cookie-param") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing cookie param", response3.body) +// } +// +// @Test fun `Optional cookies are verified correctly`() { +// client.cookies += Cookie("cookieParam", "aValidValue") +// val response1 = client.get("/check-optional-cookie-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// client.cookies = emptyList() +// +// client.cookies += Cookie("cookieParam", "anInvalidValue") +// val response2 = client.get("/check-optional-cookie-param") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing cookie param", response2.body) +// client.cookies = emptyList() +// +// val response3 = client.get("/check-optional-cookie-param") +// assertEquals(OK_200, response3.status) +// assertEquals("success", response3.body) +// } +// +// @Test fun `Body is verified correctly`() { +// val response1 = client.get("/check-body", body = "Some body content") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-body") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing request body", response2.body) +// } +// +// @Test fun `If Authorization is optional, it is skipped`() { +// val response1 = client.get("/check-optional-auth") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response2 = client.get("/check-optional-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Basic HTTP Authentication is verified correctly`() { +// val response1 = client.get("/check-basic-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response2 = client.get("/check-basic-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Bearer HTTP Authentication is verified correctly`() { +// val response1 = client.get("/check-bearer-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) +// val response2 = client.get("/check-bearer-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `HTTP Authentication with unknown scheme throws error`() { +// val response = client.get("/check-unknown-auth") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) +// } +// +// @Test fun `Query param API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-query-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val response2 = client.get("/check-query-api-auth?api-key=abcdefg") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Header API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-header-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("api-key", "abcdefg")) +// val response2 = client.get("/check-header-api-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Cookie API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-cookie-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-cookie-api-auth") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// client.cookies = emptyList() +// } +// +// @Test fun `Unknown location API Key Authentication throws error`() { +// val response = client.get("/check-unknown-api-auth") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) +// } +// +// @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { +// val response1 = client.get("/check-multiple-mechanisms") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-multiple-mechanisms") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// client.cookies = emptyList() +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response3 = client.get("/check-multiple-mechanisms", headers = headers) +// assertEquals(OK_200, response3.status) +// assertEquals("success", response3.body) +// } +// +// @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { +// val response1 = client.get("/check-multiple-mechanisms") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-multiple-schemes") +// assertEquals(UNAUTHORIZED_401, response2.status) +// assertEquals("Invalid authorization credentials", response2.body) +// client.cookies = emptyList() +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response3 = client.get("/check-multiple-schemes", headers = headers) +// assertEquals(UNAUTHORIZED_401, response3.status) +// assertEquals("Invalid authorization credentials", response3.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response4 = client.get("/check-multiple-schemes", headers = headers) +// assertEquals(OK_200, response4.status) +// assertEquals("success", response4.body) +// client.cookies = emptyList() +// } +} diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt index aa20a408b0..9d16579cb6 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt @@ -24,7 +24,7 @@ internal class VerifySpecCallbackTest { // TODO Check commented code (it should throw validation errors) @Test fun `Requests not complying with spec return an error`() { verify(errors = listOf("ERROR: validation.request.path.missing [ ] No API path found that matches request ''. [] []")) - // 'status' query parameter with invalid value + // TODO 'status' query parameter with invalid value // verify( // HttpRequest( // path = "/pet/findByStatus", From 1f75d6bbd844a60fb43ca7040a16f35825587a75 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Mon, 29 Jan 2024 20:09:44 +0100 Subject: [PATCH 05/28] Update dependencies --- build.gradle.kts | 2 +- gradle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ae1e46ad15..22e17eca01 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ plugins { id("project-report") id("org.jetbrains.dokka") version("1.9.10") id("com.github.jk1.dependency-license-report") version("2.5") - id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.13.2") + id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.14.0") id("org.graalvm.buildtools.native") version("0.9.28") apply(false) id("io.gitlab.arturbosch.detekt") version("1.23.4") apply(false) id("me.champeau.jmh") version("0.7.2") apply(false) diff --git a/gradle.properties b/gradle.properties index a25af47181..4d99dd8637 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,7 +37,7 @@ mockkVersion=1.13.9 junitVersion=5.10.1 gatlingVersion=3.10.3 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.5 +mkdocsMaterialVersion=9.5.6 mermaidDokkaVersion=0.4.4 nativeToolsVersion=0.9.28 @@ -63,7 +63,7 @@ dslJsonVersion=2.0.2 freemarkerVersion=2.3.32 # templates_jte -jteVersion=3.1.8 +jteVersion=3.1.9 # templates_pebble pebbleVersion=3.2.2 From 2c22454d722ef48df24ba21f88ac35017c12f3ca Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 1 Feb 2024 20:57:51 +0100 Subject: [PATCH 06/28] Update dependencies --- build.gradle.kts | 2 +- gradle.properties | 2 +- .../http/client/jetty/JettyClientAdapter.kt | 4 +- .../src/main/kotlin/module-info.java_ | 1 + .../tools/openapi/SpecExampleCallbackTest.kt | 283 ------------------ .../tools/openapi/VerifySpecCallbackTest.kt | 275 +++++++++++++++++ 6 files changed, 281 insertions(+), 286 deletions(-) delete mode 100644 http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 22e17eca01..90608d905b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,7 @@ plugins { id("com.github.jk1.dependency-license-report") version("2.5") id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.14.0") id("org.graalvm.buildtools.native") version("0.9.28") apply(false) - id("io.gitlab.arturbosch.detekt") version("1.23.4") apply(false) + id("io.gitlab.arturbosch.detekt") version("1.23.5") apply(false) id("me.champeau.jmh") version("0.7.2") apply(false) } diff --git a/gradle.properties b/gradle.properties index 4d99dd8637..565534f1d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ helidonVersion=4.0.4 # http_server_servlet servletVersion=6.0.0 -jettyVersion=12.0.5 +jettyVersion=12.0.6 # rest_tools swaggerRequestValidatorVersion=2.40.0 diff --git a/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt b/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt index f82ccb0018..894a971997 100644 --- a/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt +++ b/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt @@ -200,7 +200,9 @@ open class JettyClientAdapter : HttpClientPort { it.put("content-type", contentType.text) if (authorization != null) it.put("authorization", authorization.text) - request.headers.values.forEach { (k, v) -> it.put(k, v.map(Any::toString)) } + request.headers.values.forEach { (k, v) -> + v.map(Any::toString).forEach { s -> it.add(k, s)} + } } .body(createBody(request)) .accept(*request.accept.map { it.text }.toTypedArray()) diff --git a/http/rest_tools/src/main/kotlin/module-info.java_ b/http/rest_tools/src/main/kotlin/module-info.java_ index b1190932bb..4f62b48674 100644 --- a/http/rest_tools/src/main/kotlin/module-info.java_ +++ b/http/rest_tools/src/main/kotlin/module-info.java_ @@ -11,6 +11,7 @@ module com.hexagonkt.rest_tools { requires io.swagger.v3.oas.models; requires swagger.parser.v3; + requires swagger.request.validator.core; exports com.hexagonkt.rest.tools; } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt deleted file mode 100644 index 688d3b6213..0000000000 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/SpecExampleCallbackTest.kt +++ /dev/null @@ -1,283 +0,0 @@ -package com.hexagonkt.rest.tools.openapi - -import org.junit.jupiter.api.* - -@Disabled // TODO Fix this functionality -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class SpecExampleCallbackTest { - -// @Test fun `Basic routes are created correctly`() { -// val response = SpecExampleCallback("").process(HttpRequest(GET, path = "/ping")) -// assertEquals(OK_200, response.status) -// assertEquals("pong", response.response.body) -// } -// -// @Test fun `Examples are fetched from media-type schema correctly`() { -// val response = client.get("/get-example-from-schema") -// assertEquals(OK_200, response.status) -// assertEquals("response", response.body) -// } -// -// @Test fun `Examples are fetched from media type correctly`() { -// val response = client.get("/get-example-from-mediatype") -// assertEquals(OK_200, response.status) -// assertEquals("response", response.body) -// } -// -// @Test fun `Examples are fetched from multiple examples correctly`() { -// val response = client.get("/get-from-multiple-examples") -// assertEquals(OK_200, response.status) -// assert(response.body in listOf("foo", "bar")) -// } -// -// @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { -// val headers = Headers(Header("x-mock-response-example", "example2")) -// val response = client.get("/get-from-multiple-examples", headers = headers) -// assertEquals(OK_200, response.status) -// assertEquals("bar", response.body) -// } -// -// @Test fun `Empty string is returned if no examples specified`() { -// val response = client.get("/get-from-no-examples") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// } -// -// @Test fun `Paths not present in OpenAPI spec return 404`() { -// val response = client.get("/unknown-path") -// assertEquals(NOT_FOUND_404, response.status) -// } -// -// @Test fun `Required query params are verified correctly`() { -// val response1 = client.get("/check-query-param") -// assertEquals(BAD_REQUEST_400, response1.status) -// assertEquals("invalid or missing query param", response1.body) -// -// val response2 = client.get("/check-query-param?queryParam=aValidValue") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val response3 = client.get("/check-query-param?queryParam=anInvalidValue") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing query param", response3.body) -// } -// -// @Test fun `Optional query params are verified correctly`() { -// val response1 = client.get("/check-optional-query-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing query param", response3.body) -// } -// -// @Test fun `Path params are verified correctly`() { -// val response1 = client.get("/check-path-param/aValidValue") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-path-param/anInvalidValue") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing path param", response2.body) -// } -// -// @Test fun `Required header params are verified correctly`() { -// val response1 = client.get("/check-header-param") -// assertEquals(BAD_REQUEST_400, response1.status) -// assertEquals("invalid or missing header param", response1.body) -// -// val validHeaders = Headers(Header("header-param", "aValidValue")) -// val response2 = client.get("/check-header-param", headers = validHeaders) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) -// val response3 = client.get("/check-header-param", headers = invalidHeaders) -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing header param", response3.body) -// } -// -// @Test fun `Optional header params are verified correctly`() { -// val response1 = client.get("/check-optional-header-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val validHeaders = Headers(Header("header-param", "aValidValue")) -// val response2 = client.get("/check-optional-header-param", headers = validHeaders) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) -// val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing header param", response3.body) -// } -// -// @Test fun `Required cookies are verified correctly`() { -// client.cookies += Cookie("cookieParam", "aValidValue") -// val response1 = client.get("/check-cookie-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// client.cookies = emptyList() -// -// client.cookies += Cookie("cookieParam", "anInvalidValue") -// val response2 = client.get("/check-cookie-param") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing cookie param", response2.body) -// client.cookies = emptyList() -// -// val response3 = client.get("/check-cookie-param") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing cookie param", response3.body) -// } -// -// @Test fun `Optional cookies are verified correctly`() { -// client.cookies += Cookie("cookieParam", "aValidValue") -// val response1 = client.get("/check-optional-cookie-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// client.cookies = emptyList() -// -// client.cookies += Cookie("cookieParam", "anInvalidValue") -// val response2 = client.get("/check-optional-cookie-param") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing cookie param", response2.body) -// client.cookies = emptyList() -// -// val response3 = client.get("/check-optional-cookie-param") -// assertEquals(OK_200, response3.status) -// assertEquals("success", response3.body) -// } -// -// @Test fun `Body is verified correctly`() { -// val response1 = client.get("/check-body", body = "Some body content") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-body") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing request body", response2.body) -// } -// -// @Test fun `If Authorization is optional, it is skipped`() { -// val response1 = client.get("/check-optional-auth") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response2 = client.get("/check-optional-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Basic HTTP Authentication is verified correctly`() { -// val response1 = client.get("/check-basic-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response2 = client.get("/check-basic-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Bearer HTTP Authentication is verified correctly`() { -// val response1 = client.get("/check-bearer-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) -// val response2 = client.get("/check-bearer-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `HTTP Authentication with unknown scheme throws error`() { -// val response = client.get("/check-unknown-auth") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) -// } -// -// @Test fun `Query param API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-query-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val response2 = client.get("/check-query-api-auth?api-key=abcdefg") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Header API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-header-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("api-key", "abcdefg")) -// val response2 = client.get("/check-header-api-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Cookie API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-cookie-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-cookie-api-auth") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// client.cookies = emptyList() -// } -// -// @Test fun `Unknown location API Key Authentication throws error`() { -// val response = client.get("/check-unknown-api-auth") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) -// } -// -// @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { -// val response1 = client.get("/check-multiple-mechanisms") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-multiple-mechanisms") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// client.cookies = emptyList() -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response3 = client.get("/check-multiple-mechanisms", headers = headers) -// assertEquals(OK_200, response3.status) -// assertEquals("success", response3.body) -// } -// -// @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { -// val response1 = client.get("/check-multiple-mechanisms") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-multiple-schemes") -// assertEquals(UNAUTHORIZED_401, response2.status) -// assertEquals("Invalid authorization credentials", response2.body) -// client.cookies = emptyList() -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response3 = client.get("/check-multiple-schemes", headers = headers) -// assertEquals(UNAUTHORIZED_401, response3.status) -// assertEquals("Invalid authorization credentials", response3.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response4 = client.get("/check-multiple-schemes", headers = headers) -// assertEquals(OK_200, response4.status) -// assertEquals("success", response4.body) -// client.cookies = emptyList() -// } -} diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt index 9d16579cb6..37905ff07d 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt @@ -231,4 +231,279 @@ internal class VerifySpecCallbackTest { assertEquals(expectedStatus, result.status) assertEquals(errors, actualErrors) } + +// @Test fun `Basic routes are created correctly`() { +// val response = SpecExampleCallback("").process(HttpRequest(GET, path = "/ping")) +// assertEquals(OK_200, response.status) +// assertEquals("pong", response.response.body) +// } +// +// @Test fun `Examples are fetched from media-type schema correctly`() { +// val response = client.get("/get-example-from-schema") +// assertEquals(OK_200, response.status) +// assertEquals("response", response.body) +// } +// +// @Test fun `Examples are fetched from media type correctly`() { +// val response = client.get("/get-example-from-mediatype") +// assertEquals(OK_200, response.status) +// assertEquals("response", response.body) +// } +// +// @Test fun `Examples are fetched from multiple examples correctly`() { +// val response = client.get("/get-from-multiple-examples") +// assertEquals(OK_200, response.status) +// assert(response.body in listOf("foo", "bar")) +// } +// +// @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { +// val headers = Headers(Header("x-mock-response-example", "example2")) +// val response = client.get("/get-from-multiple-examples", headers = headers) +// assertEquals(OK_200, response.status) +// assertEquals("bar", response.body) +// } +// +// @Test fun `Empty string is returned if no examples specified`() { +// val response = client.get("/get-from-no-examples") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// } +// +// @Test fun `Paths not present in OpenAPI spec return 404`() { +// val response = client.get("/unknown-path") +// assertEquals(NOT_FOUND_404, response.status) +// } +// +// @Test fun `Required query params are verified correctly`() { +// val response1 = client.get("/check-query-param") +// assertEquals(BAD_REQUEST_400, response1.status) +// assertEquals("invalid or missing query param", response1.body) +// +// val response2 = client.get("/check-query-param?queryParam=aValidValue") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val response3 = client.get("/check-query-param?queryParam=anInvalidValue") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing query param", response3.body) +// } +// +// @Test fun `Optional query params are verified correctly`() { +// val response1 = client.get("/check-optional-query-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing query param", response3.body) +// } +// +// @Test fun `Path params are verified correctly`() { +// val response1 = client.get("/check-path-param/aValidValue") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-path-param/anInvalidValue") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing path param", response2.body) +// } +// +// @Test fun `Required header params are verified correctly`() { +// val response1 = client.get("/check-header-param") +// assertEquals(BAD_REQUEST_400, response1.status) +// assertEquals("invalid or missing header param", response1.body) +// +// val validHeaders = Headers(Header("header-param", "aValidValue")) +// val response2 = client.get("/check-header-param", headers = validHeaders) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) +// val response3 = client.get("/check-header-param", headers = invalidHeaders) +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing header param", response3.body) +// } +// +// @Test fun `Optional header params are verified correctly`() { +// val response1 = client.get("/check-optional-header-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val validHeaders = Headers(Header("header-param", "aValidValue")) +// val response2 = client.get("/check-optional-header-param", headers = validHeaders) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// +// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) +// val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing header param", response3.body) +// } +// +// @Test fun `Required cookies are verified correctly`() { +// client.cookies += Cookie("cookieParam", "aValidValue") +// val response1 = client.get("/check-cookie-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// client.cookies = emptyList() +// +// client.cookies += Cookie("cookieParam", "anInvalidValue") +// val response2 = client.get("/check-cookie-param") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing cookie param", response2.body) +// client.cookies = emptyList() +// +// val response3 = client.get("/check-cookie-param") +// assertEquals(BAD_REQUEST_400, response3.status) +// assertEquals("invalid or missing cookie param", response3.body) +// } +// +// @Test fun `Optional cookies are verified correctly`() { +// client.cookies += Cookie("cookieParam", "aValidValue") +// val response1 = client.get("/check-optional-cookie-param") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// client.cookies = emptyList() +// +// client.cookies += Cookie("cookieParam", "anInvalidValue") +// val response2 = client.get("/check-optional-cookie-param") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing cookie param", response2.body) +// client.cookies = emptyList() +// +// val response3 = client.get("/check-optional-cookie-param") +// assertEquals(OK_200, response3.status) +// assertEquals("success", response3.body) +// } +// +// @Test fun `Body is verified correctly`() { +// val response1 = client.get("/check-body", body = "Some body content") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val response2 = client.get("/check-body") +// assertEquals(BAD_REQUEST_400, response2.status) +// assertEquals("invalid or missing request body", response2.body) +// } +// +// @Test fun `If Authorization is optional, it is skipped`() { +// val response1 = client.get("/check-optional-auth") +// assertEquals(OK_200, response1.status) +// assertEquals("success", response1.body) +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response2 = client.get("/check-optional-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Basic HTTP Authentication is verified correctly`() { +// val response1 = client.get("/check-basic-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response2 = client.get("/check-basic-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Bearer HTTP Authentication is verified correctly`() { +// val response1 = client.get("/check-bearer-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) +// val response2 = client.get("/check-bearer-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `HTTP Authentication with unknown scheme throws error`() { +// val response = client.get("/check-unknown-auth") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) +// } +// +// @Test fun `Query param API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-query-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val response2 = client.get("/check-query-api-auth?api-key=abcdefg") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Header API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-header-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// val headers = Headers(Header("api-key", "abcdefg")) +// val response2 = client.get("/check-header-api-auth", headers = headers) +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// } +// +// @Test fun `Cookie API Key Authentication is verified correctly`() { +// val response1 = client.get("/check-cookie-api-auth") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-cookie-api-auth") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// client.cookies = emptyList() +// } +// +// @Test fun `Unknown location API Key Authentication throws error`() { +// val response = client.get("/check-unknown-api-auth") +// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) +// assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) +// } +// +// @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { +// val response1 = client.get("/check-multiple-mechanisms") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-multiple-mechanisms") +// assertEquals(OK_200, response2.status) +// assertEquals("success", response2.body) +// client.cookies = emptyList() +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response3 = client.get("/check-multiple-mechanisms", headers = headers) +// assertEquals(OK_200, response3.status) +// assertEquals("success", response3.body) +// } +// +// @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { +// val response1 = client.get("/check-multiple-mechanisms") +// assertEquals(UNAUTHORIZED_401, response1.status) +// assertEquals("Invalid authorization credentials", response1.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response2 = client.get("/check-multiple-schemes") +// assertEquals(UNAUTHORIZED_401, response2.status) +// assertEquals("Invalid authorization credentials", response2.body) +// client.cookies = emptyList() +// +// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) +// val response3 = client.get("/check-multiple-schemes", headers = headers) +// assertEquals(UNAUTHORIZED_401, response3.status) +// assertEquals("Invalid authorization credentials", response3.body) +// +// client.cookies += Cookie("api-key", "abcdefg") +// val response4 = client.get("/check-multiple-schemes", headers = headers) +// assertEquals(OK_200, response4.status) +// assertEquals("success", response4.body) +// client.cookies = emptyList() +// } } From ba4abbddaddfcecd2a866fb26da78aebfa6c14c2 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 1 Feb 2024 23:03:48 +0100 Subject: [PATCH 07/28] Set up test logging --- gradle.properties | 1 + http/http_client_jetty/build.gradle.kts | 3 +++ .../src/test/resources/logging.properties | 11 +++++++++++ http/http_client_jetty_ws/build.gradle.kts | 3 +++ http/http_server_jetty/build.gradle.kts | 2 ++ http/http_test/build.gradle.kts | 2 ++ http/rest/build.gradle.kts | 3 +++ http/rest_tools/build.gradle.kts | 2 ++ http/web/build.gradle.kts | 3 +++ 9 files changed, 30 insertions(+) create mode 100644 http/http_client_jetty/src/test/resources/logging.properties diff --git a/gradle.properties b/gradle.properties index 565534f1d2..48c291e233 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,6 +36,7 @@ dokkaVersion=1.9.10 mockkVersion=1.13.9 junitVersion=5.10.1 gatlingVersion=3.10.3 +slf4jVersion=2.0.11 jmhVersion=1.37 mkdocsMaterialVersion=9.5.6 mermaidDokkaVersion=0.4.4 diff --git a/http/http_client_jetty/build.gradle.kts b/http/http_client_jetty/build.gradle.kts index 2e54f10670..2d11784305 100644 --- a/http/http_client_jetty/build.gradle.kts +++ b/http/http_client_jetty/build.gradle.kts @@ -13,7 +13,10 @@ description = "HTTP client adapter for Jetty (without WebSockets support)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_client")) "api"("org.eclipse.jetty.http2:jetty-http2-client-transport:$jettyVersion") + + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_client_jetty/src/test/resources/logging.properties b/http/http_client_jetty/src/test/resources/logging.properties new file mode 100644 index 0000000000..1ab53cda83 --- /dev/null +++ b/http/http_client_jetty/src/test/resources/logging.properties @@ -0,0 +1,11 @@ + +handlers=java.util.logging.ConsoleHandler + +.level=SEVERE + +java.util.logging.ConsoleHandler.level=TRACE +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + +java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n + +com.hexagonkt.level=WARNING diff --git a/http/http_client_jetty_ws/build.gradle.kts b/http/http_client_jetty_ws/build.gradle.kts index 2af866cd63..b7992f4eca 100644 --- a/http/http_client_jetty_ws/build.gradle.kts +++ b/http/http_client_jetty_ws/build.gradle.kts @@ -13,7 +13,10 @@ description = "HTTP client adapter for Jetty (with WebSockets support)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_client_jetty")) "api"("org.eclipse.jetty.websocket:jetty-websocket-jetty-client:$jettyVersion") + + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_server_jetty/build.gradle.kts b/http/http_server_jetty/build.gradle.kts index 159c6860dc..10bd006fd2 100644 --- a/http/http_server_jetty/build.gradle.kts +++ b/http/http_server_jetty/build.gradle.kts @@ -13,6 +13,7 @@ description = "HTTP server adapter for Jetty (using Servlets under the hood)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server_servlet")) "api"("org.eclipse.jetty.ee10:jetty-ee10-servlet:$jettyVersion") @@ -23,6 +24,7 @@ dependencies { "testImplementation"(project(":http:http_client_jetty_ws")) "testImplementation"(project(":serialization:serialization_jackson_json")) "testImplementation"(project(":serialization:serialization_jackson_yaml")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") "testImplementation"( "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server:$jettyVersion" ) diff --git a/http/http_test/build.gradle.kts b/http/http_test/build.gradle.kts index 948be2ba20..6e88d45c89 100644 --- a/http/http_test/build.gradle.kts +++ b/http/http_test/build.gradle.kts @@ -13,6 +13,7 @@ description = "Test cases for HTTP client and server adapters." dependencies { val junitVersion = properties["junitVersion"] val gatlingVersion = properties["gatlingVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":serialization:serialization")) "api"(project(":http:http_client")) @@ -20,4 +21,5 @@ dependencies { "api"("org.jetbrains.kotlin:kotlin-test") "api"("org.junit.jupiter:junit-jupiter:$junitVersion") "api"("io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion") + "api"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/rest/build.gradle.kts b/http/rest/build.gradle.kts index ccd8079c24..42a96cb7a4 100644 --- a/http/rest/build.gradle.kts +++ b/http/rest/build.gradle.kts @@ -12,10 +12,13 @@ apply(from = "$rootDir/gradle/detekt.gradle") description = "HTTP server extensions to ease the development of REST APIs." dependencies { + val slf4jVersion = properties["slf4jVersion"] + "api"(project(":http:http_handlers")) "api"(project(":serialization:serialization")) "testImplementation"(project(":http:http_client_jetty")) "testImplementation"(project(":http:http_server_jetty")) "testImplementation"(project(":serialization:serialization_jackson_json")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/rest_tools/build.gradle.kts b/http/rest_tools/build.gradle.kts index eecbd14b3a..fae64822c4 100644 --- a/http/rest_tools/build.gradle.kts +++ b/http/rest_tools/build.gradle.kts @@ -13,6 +13,7 @@ description = "Tools to test and document REST services." dependencies { val swaggerRequestValidatorVersion = properties["swaggerRequestValidatorVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:rest")) "api"(project(":http:http_server")) @@ -23,4 +24,5 @@ dependencies { "testImplementation"(project(":http:http_server_jetty")) "testImplementation"(project(":serialization:serialization_jackson_json")) "testImplementation"(project(":serialization:serialization_jackson_yaml")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/web/build.gradle.kts b/http/web/build.gradle.kts index 2e581b7312..8e0d4618a8 100644 --- a/http/web/build.gradle.kts +++ b/http/web/build.gradle.kts @@ -12,6 +12,8 @@ apply(from = "$rootDir/gradle/detekt.gradle") description = "HTTP server extensions to ease the development of dynamic Web applications." dependencies { + val slf4jVersion = properties["slf4jVersion"] + "api"(project(":http:http_server")) "api"(project(":templates:templates")) @@ -19,4 +21,5 @@ dependencies { "testImplementation"(project(":http:http_server_jetty")) "testImplementation"(project(":templates:templates_pebble")) "testImplementation"(project(":serialization:serialization_jackson_json")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } From 9784dedce83b7ba0da0213d38a70ec0b617c8194 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 4 Feb 2024 13:37:36 +0100 Subject: [PATCH 08/28] Update dependencies --- build.gradle.kts | 4 ++-- gradle.properties | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 90608d905b..48b158487a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ plugins { id("org.jetbrains.dokka") version("1.9.10") id("com.github.jk1.dependency-license-report") version("2.5") id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.14.0") - id("org.graalvm.buildtools.native") version("0.9.28") apply(false) + id("org.graalvm.buildtools.native") version("0.10.0") apply(false) id("io.gitlab.arturbosch.detekt") version("1.23.5") apply(false) id("me.champeau.jmh") version("0.7.2") apply(false) } @@ -140,7 +140,7 @@ gradle.taskGraph.whenReady(closureOf { }) tasks.wrapper { - gradleVersion = "8.5" + gradleVersion = "8.6" distributionType = ALL } diff --git a/gradle.properties b/gradle.properties index 48c291e233..e69ddee035 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,13 +34,13 @@ iconsDirectory=content kotlinVersion=1.9.22 dokkaVersion=1.9.10 mockkVersion=1.13.9 -junitVersion=5.10.1 +junitVersion=5.10.2 gatlingVersion=3.10.3 slf4jVersion=2.0.11 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.6 +mkdocsMaterialVersion=9.5.7 mermaidDokkaVersion=0.4.4 -nativeToolsVersion=0.9.28 +nativeToolsVersion=0.10.0 # http_server_netty nettyVersion=4.1.106.Final diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6aba2515d..2ea3535dc0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f13..25da30dbde 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 5a07cc72a8b3304b1591b6a3d710b711e349af95 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 4 Feb 2024 13:37:53 +0100 Subject: [PATCH 09/28] Fix test in Windows --- .../kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt index 47f4f98a6c..642d8a0c92 100644 --- a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt +++ b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt @@ -25,7 +25,7 @@ internal class SerializeResponseCallbackTest { @Test fun `Serialize response callback creates the proper response`() { SerializationManager.formats = setOf(Json) val body = mapOf("key" to "value") - val jsonBody = "{\n \"key\" : \"value\"\n}" + val jsonBody = "{\n \"key\" : \"value\"\n}".replace("\n", System.lineSeparator()) val yaml = ContentType(APPLICATION_YAML) val yamlContext = HttpContext().send(body = body, contentType = yaml) From ad18bff8d64569e0f713390ee70be4990defdcd6 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 4 Feb 2024 16:49:16 +0100 Subject: [PATCH 10/28] Improve OpenAPI verification --- ...{DynamicServer.kt => DynamicHttpServer.kt} | 0 .../tools/{Http.kt => StateHttpClient.kt} | 0 .../rest/tools/openapi/VerifySpecCallback.kt | 216 ++--------- .../tools/openapi/VerifySpecCallbackTest.kt | 342 ++---------------- 4 files changed, 62 insertions(+), 496 deletions(-) rename http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/{DynamicServer.kt => DynamicHttpServer.kt} (100%) rename http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/{Http.kt => StateHttpClient.kt} (100%) diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicServer.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicHttpServer.kt similarity index 100% rename from http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicServer.kt rename to http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicHttpServer.kt diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/Http.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt similarity index 100% rename from http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/Http.kt rename to http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt index 1e8b40f338..43cfa781ac 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt @@ -3,27 +3,17 @@ package com.hexagonkt.rest.tools.openapi import com.atlassian.oai.validator.OpenApiInteractionValidator import com.atlassian.oai.validator.OpenApiInteractionValidator.createForInlineApiSpecification import com.atlassian.oai.validator.model.Request -import com.atlassian.oai.validator.model.Response import com.atlassian.oai.validator.model.Request.Method +import com.atlassian.oai.validator.model.Response import com.atlassian.oai.validator.model.SimpleRequest import com.atlassian.oai.validator.model.SimpleResponse import com.atlassian.oai.validator.report.ValidationReport -import com.hexagonkt.core.fail -import com.hexagonkt.core.require +import com.atlassian.oai.validator.report.ValidationReport.Message import com.hexagonkt.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext -import com.hexagonkt.http.model.BAD_REQUEST_400 import com.hexagonkt.http.model.ContentType import com.hexagonkt.http.model.HttpMethod import com.hexagonkt.http.model.HttpMethod.* -import com.hexagonkt.http.model.UNAUTHORIZED_401 -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.parameters.Parameter -import io.swagger.v3.oas.models.security.SecurityRequirement -import io.swagger.v3.oas.models.security.SecurityScheme -import io.swagger.v3.oas.models.security.SecurityScheme.Type -import io.swagger.v3.parser.OpenAPIV3Parser import java.net.URL import kotlin.jvm.optionals.getOrNull @@ -34,14 +24,9 @@ class VerifySpecCallback(spec: URL) : HttpCallback { private val messagePrefix: String = "\n- " private val specText: String = spec.readText() - private val validator: OpenApiInteractionValidator = createForInlineApiSpecification(specText).build() - private val openAPIParser = OpenAPIV3Parser() - private val openAPISpec: OpenAPI = openAPIParser.read(spec.path) - ?: error("OpenAPI Spec could not be read. Please check the file's path and its format") - override fun invoke(context: HttpContext): HttpContext { val requestReport = validator.validateRequest(request(context)) @@ -50,38 +35,42 @@ class VerifySpecCallback(spec: URL) : HttpCallback { val resultMethod = method(result.method) val responseReport = validator.validateResponse(result.path, resultMethod, response(result)) - responseReport.merge(requestReport) + val callReport = responseReport.merge(requestReport) - return if (responseReport.hasErrors()) result.badRequest(message(responseReport)) + return if (callReport.hasErrors()) result.badRequest(message(callReport)) else result } private fun message(report: ValidationReport): String { - return report.messages.joinToString(messagePrefix, "Invalid request:$messagePrefix") { - val level = it.level - val key = it.key - val context = it.context - .map { c -> - val op = c.apiOperation - .getOrNull() - ?.let { ao -> - val method = ao.method - val apiPath = ao.apiPath - "$method ${apiPath.normalised()}" - } - ?: "" + val messages = report.messages.map(::messageToText).distinct() + return messages.joinToString(messagePrefix, "Invalid call:$messagePrefix") + } - val loc = c.location.getOrNull()?.name ?: "" + private fun messageToText(it: Message): String { + val level = it.level + val key = it.key + val context = it.context + .map { c -> + val op = c.apiOperation + .getOrNull() + ?.let { ao -> + val method = ao.method + val apiPath = ao.apiPath + "$method ${apiPath.normalised()}" + } + ?: "" - "$op $loc" - } - .orElse("") - val message = it.message - val additionalInfo = it.additionalInfo - val nestedMessages = it.nestedMessages + val loc = c.location.getOrNull()?.name ?: "" - "$level: $key [$context] $message $additionalInfo $nestedMessages" - } + "$op $loc" + } + .orElse("") + + val message = it.message + val additionalInfo = it.additionalInfo + val nestedMessages = it.nestedMessages + + return "$level: $key [$context] $message $additionalInfo $nestedMessages" } private fun request(context: HttpContext): Request { @@ -125,149 +114,4 @@ class VerifySpecCallback(spec: URL) : HttpCallback { OPTIONS -> Method.OPTIONS PATCH -> Method.PATCH } - - private fun verifyAuth(operation: Operation, call: HttpContext): HttpContext? { - if (operation.security == null - || operation.security.size == 0 - || containsEmptySecurityRequirement(operation)) - return null - - // Any one of the security mechanisms need to be satisfied - return if (!operation.security.any { securityRequirement -> verifySecurityRequirement(securityRequirement, call) }) { - call.send(status = UNAUTHORIZED_401) - } - else null - } - - private fun verifySecurityRequirement( - securityRequirement: SecurityRequirement, call: HttpContext): Boolean = - securityRequirement.keys.all { verifySecurityScheme(it, call) } - - private fun verifySecurityScheme(schemeName: String, call: HttpContext): Boolean { - val securityScheme = openAPISpec.components.securitySchemes[schemeName] - ?: error("The OpenAPI Spec contains no security scheme component for $schemeName") - - return when (securityScheme.type) { - Type.APIKEY -> validateApiKey(securityScheme, call) - Type.HTTP -> validateHttpAuth(securityScheme, call) - else -> error("Mock Server only supports HTTP and API Key authentication") - } - } - - private fun validateApiKey(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.`in`) { - SecurityScheme.In.QUERY -> - call.request.queryParameters[securityScheme.name]?.string()?.isNotBlank() ?: fail - SecurityScheme.In.HEADER -> - call.request.headers[securityScheme.name]?.string().isNullOrBlank() - SecurityScheme.In.COOKIE -> - call.request.cookiesMap()[securityScheme.name] != null - else -> - error("Unknown `in` value found in OpenAPI Spec for security scheme") - } - - private fun validateHttpAuth(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.scheme.lowercase()) { - "basic" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Basic") - } ?: false - } - "bearer" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Bearer") - } ?: false - } - else -> - error("Mock Server only supports Basic and Bearer HTTP Authentication") - } - - private fun verifyParams(operation: Operation, call: HttpContext): HttpContext? { - operation.parameters?.forEach { parameter -> - when (parameter.`in`) { - "path" -> { - if (!verifyPathParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - ) - } - } - "query" -> { - if (!verifyQueryParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - ) - } - } - "header" -> { - if (!verifyHeaderParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - ) - } - } - "cookie" -> { - if (!verifyCookieParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - ) - } - } - } - } - - return null - } - - private fun verifyPathParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.pathParameters[parameter.name].isNullOrBlank()) return false - parameter.schema.enum?.let { - if (call.pathParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyQueryParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.queryParameters.require(parameter.name).string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.queryParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyHeaderParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.headers[parameter.name]?.string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.headers[parameter.name] !in it) return false - } - return true - } - - private fun verifyCookieParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.cookiesMap()[parameter.name] == null) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.cookiesMap()[parameter.name]?.value !in it) return false - } - return true - } - - private fun verifyBody(operation: Operation, call: HttpContext): HttpContext? { - operation.requestBody?.let { requestBody -> - if (requestBody.required && call.request.bodyString().isBlank()) { - call.send( - status = BAD_REQUEST_400, - ) - } - } - return null - } - - private fun containsEmptySecurityRequirement(operation: Operation): Boolean = - operation.security.any { it.size == 0 } } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt index 37905ff07d..7ababfb4a8 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt @@ -21,27 +21,25 @@ internal class VerifySpecCallbackTest { SerializationManager.formats = setOf(Json, Yaml) } - // TODO Check commented code (it should throw validation errors) @Test fun `Requests not complying with spec return an error`() { verify(errors = listOf("ERROR: validation.request.path.missing [ ] No API path found that matches request ''. [] []")) - // TODO 'status' query parameter with invalid value -// verify( -// HttpRequest( -// path = "/pet/findByStatus", -// queryParameters = QueryParameters(QueryParameter("status", "invalid")) -// ), -// HttpResponse( -// status = OK_200, -// contentType = ContentType(APPLICATION_JSON), -// body = listOf( -// mapOf( -// "name" to "Keka", -// "photoUrls" to listOf("https://example.com") -// ) -// ), -// ), -// listOf("1") -// ) + verify( + HttpRequest( + path = "/pet/findByStatus", + queryParameters = QueryParameters(QueryParameter("status", "invalid")) + ), + HttpResponse( + status = OK_200, + contentType = ContentType(APPLICATION_JSON), + body = listOf( + mapOf( + "name" to "Keka", + "photoUrls" to listOf("https://example.com") + ) + ), + ), + listOf("ERROR: validation.request.parameter.schema.enum [GET /pet/findByStatus REQUEST] Instance value (\"invalid\") not found in enum (possible values: [\"available\",\"pending\",\"sold\"]) [] []") + ) verify( HttpRequest( method = HEAD, @@ -80,22 +78,21 @@ internal class VerifySpecCallbackTest { ), listOf("ERROR: validation.response.body.schema.type [POST /pet RESPONSE] Instance type (array) does not match any allowed primitive type (allowed: [\"object\"]) [] []") ) - // TODO Request body required (should fail) -// verify( -// HttpRequest( -// method = POST, -// path = "/pet", -// ), -// HttpResponse( -// status = OK_200, -// contentType = ContentType(APPLICATION_JSON), -// body = mapOf( -// "name" to "Keka", -// "photoUrls" to listOf("http://example.com") -// ), -// ), -// listOf("1") -// ) + verify( + HttpRequest( + method = POST, + path = "/pet", + ), + HttpResponse( + status = OK_200, + contentType = ContentType(APPLICATION_JSON), + body = mapOf( + "name" to "Keka", + "photoUrls" to listOf("https://example.com") + ), + ), + listOf("ERROR: validation.request.body.missing [POST /pet REQUEST] A request body is required but none found. [] []") + ) verify( HttpRequest(method = DELETE, path = "/pet/1"), HttpResponse(status = OK_200), @@ -231,279 +228,4 @@ internal class VerifySpecCallbackTest { assertEquals(expectedStatus, result.status) assertEquals(errors, actualErrors) } - -// @Test fun `Basic routes are created correctly`() { -// val response = SpecExampleCallback("").process(HttpRequest(GET, path = "/ping")) -// assertEquals(OK_200, response.status) -// assertEquals("pong", response.response.body) -// } -// -// @Test fun `Examples are fetched from media-type schema correctly`() { -// val response = client.get("/get-example-from-schema") -// assertEquals(OK_200, response.status) -// assertEquals("response", response.body) -// } -// -// @Test fun `Examples are fetched from media type correctly`() { -// val response = client.get("/get-example-from-mediatype") -// assertEquals(OK_200, response.status) -// assertEquals("response", response.body) -// } -// -// @Test fun `Examples are fetched from multiple examples correctly`() { -// val response = client.get("/get-from-multiple-examples") -// assertEquals(OK_200, response.status) -// assert(response.body in listOf("foo", "bar")) -// } -// -// @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { -// val headers = Headers(Header("x-mock-response-example", "example2")) -// val response = client.get("/get-from-multiple-examples", headers = headers) -// assertEquals(OK_200, response.status) -// assertEquals("bar", response.body) -// } -// -// @Test fun `Empty string is returned if no examples specified`() { -// val response = client.get("/get-from-no-examples") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// } -// -// @Test fun `Paths not present in OpenAPI spec return 404`() { -// val response = client.get("/unknown-path") -// assertEquals(NOT_FOUND_404, response.status) -// } -// -// @Test fun `Required query params are verified correctly`() { -// val response1 = client.get("/check-query-param") -// assertEquals(BAD_REQUEST_400, response1.status) -// assertEquals("invalid or missing query param", response1.body) -// -// val response2 = client.get("/check-query-param?queryParam=aValidValue") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val response3 = client.get("/check-query-param?queryParam=anInvalidValue") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing query param", response3.body) -// } -// -// @Test fun `Optional query params are verified correctly`() { -// val response1 = client.get("/check-optional-query-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing query param", response3.body) -// } -// -// @Test fun `Path params are verified correctly`() { -// val response1 = client.get("/check-path-param/aValidValue") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-path-param/anInvalidValue") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing path param", response2.body) -// } -// -// @Test fun `Required header params are verified correctly`() { -// val response1 = client.get("/check-header-param") -// assertEquals(BAD_REQUEST_400, response1.status) -// assertEquals("invalid or missing header param", response1.body) -// -// val validHeaders = Headers(Header("header-param", "aValidValue")) -// val response2 = client.get("/check-header-param", headers = validHeaders) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) -// val response3 = client.get("/check-header-param", headers = invalidHeaders) -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing header param", response3.body) -// } -// -// @Test fun `Optional header params are verified correctly`() { -// val response1 = client.get("/check-optional-header-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val validHeaders = Headers(Header("header-param", "aValidValue")) -// val response2 = client.get("/check-optional-header-param", headers = validHeaders) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// -// val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) -// val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing header param", response3.body) -// } -// -// @Test fun `Required cookies are verified correctly`() { -// client.cookies += Cookie("cookieParam", "aValidValue") -// val response1 = client.get("/check-cookie-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// client.cookies = emptyList() -// -// client.cookies += Cookie("cookieParam", "anInvalidValue") -// val response2 = client.get("/check-cookie-param") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing cookie param", response2.body) -// client.cookies = emptyList() -// -// val response3 = client.get("/check-cookie-param") -// assertEquals(BAD_REQUEST_400, response3.status) -// assertEquals("invalid or missing cookie param", response3.body) -// } -// -// @Test fun `Optional cookies are verified correctly`() { -// client.cookies += Cookie("cookieParam", "aValidValue") -// val response1 = client.get("/check-optional-cookie-param") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// client.cookies = emptyList() -// -// client.cookies += Cookie("cookieParam", "anInvalidValue") -// val response2 = client.get("/check-optional-cookie-param") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing cookie param", response2.body) -// client.cookies = emptyList() -// -// val response3 = client.get("/check-optional-cookie-param") -// assertEquals(OK_200, response3.status) -// assertEquals("success", response3.body) -// } -// -// @Test fun `Body is verified correctly`() { -// val response1 = client.get("/check-body", body = "Some body content") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val response2 = client.get("/check-body") -// assertEquals(BAD_REQUEST_400, response2.status) -// assertEquals("invalid or missing request body", response2.body) -// } -// -// @Test fun `If Authorization is optional, it is skipped`() { -// val response1 = client.get("/check-optional-auth") -// assertEquals(OK_200, response1.status) -// assertEquals("success", response1.body) -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response2 = client.get("/check-optional-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Basic HTTP Authentication is verified correctly`() { -// val response1 = client.get("/check-basic-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response2 = client.get("/check-basic-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Bearer HTTP Authentication is verified correctly`() { -// val response1 = client.get("/check-bearer-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) -// val response2 = client.get("/check-bearer-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `HTTP Authentication with unknown scheme throws error`() { -// val response = client.get("/check-unknown-auth") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) -// } -// -// @Test fun `Query param API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-query-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val response2 = client.get("/check-query-api-auth?api-key=abcdefg") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Header API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-header-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// val headers = Headers(Header("api-key", "abcdefg")) -// val response2 = client.get("/check-header-api-auth", headers = headers) -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// } -// -// @Test fun `Cookie API Key Authentication is verified correctly`() { -// val response1 = client.get("/check-cookie-api-auth") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-cookie-api-auth") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// client.cookies = emptyList() -// } -// -// @Test fun `Unknown location API Key Authentication throws error`() { -// val response = client.get("/check-unknown-api-auth") -// assertEquals(INTERNAL_SERVER_ERROR_500, response.status) -// assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) -// } -// -// @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { -// val response1 = client.get("/check-multiple-mechanisms") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-multiple-mechanisms") -// assertEquals(OK_200, response2.status) -// assertEquals("success", response2.body) -// client.cookies = emptyList() -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response3 = client.get("/check-multiple-mechanisms", headers = headers) -// assertEquals(OK_200, response3.status) -// assertEquals("success", response3.body) -// } -// -// @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { -// val response1 = client.get("/check-multiple-mechanisms") -// assertEquals(UNAUTHORIZED_401, response1.status) -// assertEquals("Invalid authorization credentials", response1.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response2 = client.get("/check-multiple-schemes") -// assertEquals(UNAUTHORIZED_401, response2.status) -// assertEquals("Invalid authorization credentials", response2.body) -// client.cookies = emptyList() -// -// val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) -// val response3 = client.get("/check-multiple-schemes", headers = headers) -// assertEquals(UNAUTHORIZED_401, response3.status) -// assertEquals("Invalid authorization credentials", response3.body) -// -// client.cookies += Cookie("api-key", "abcdefg") -// val response4 = client.get("/check-multiple-schemes", headers = headers) -// assertEquals(OK_200, response4.status) -// assertEquals("success", response4.body) -// client.cookies = emptyList() -// } } From 83c1a6d41f3879bfadb51875629812537e7dbecd Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 4 Feb 2024 17:10:59 +0100 Subject: [PATCH 11/28] Fix Gradle warning --- gradle/kotlin.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle index 14dd041708..061f8e9631 100644 --- a/gradle/kotlin.gradle +++ b/gradle/kotlin.gradle @@ -78,7 +78,7 @@ tasks.withType(JavaCompile) { sourceSets.main.java.srcDirs = sourceSets.main.kotlin.srcDirs tasks.compileJava.destinationDirectory.set(tasks.compileKotlin.destinationDirectory) -tasks.compileKotlin.kotlinOptions.jvmTarget = targetCompatibility +tasks.compileKotlin.kotlinOptions.jvmTarget = tasks.compileJava.targetCompatibility tasks.compileTestKotlin.kotlinOptions.jvmTarget = tasks.compileKotlin.kotlinOptions.jvmTarget tasks.compileTestKotlin.kotlinOptions.apiVersion = tasks.compileKotlin.kotlinOptions.apiVersion From 172660dd2b36117ba3a95269bfd8090d03c4fe46 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Mon, 5 Feb 2024 20:32:45 +0100 Subject: [PATCH 12/28] Fix test --- .../kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt index 83eda2fda8..fdc32b322f 100644 --- a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt +++ b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt @@ -32,6 +32,7 @@ internal class SerializeRequestCallbackTest { val json = ContentType(APPLICATION_JSON) val jsonContext = HttpContext().receive(body = body, contentType = json) - assertEquals(jsonContext.receive("{\n \"key\" : \"value\"\n}"), callback(jsonContext)) + val jsonBody = "{\n \"key\" : \"value\"\n}".replace("\n", System.lineSeparator()) + assertEquals(jsonContext.receive(jsonBody), callback(jsonContext)) } } From 53048d6583218cff504a2dee063f347c701017a1 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 8 Feb 2024 08:05:16 +0100 Subject: [PATCH 13/28] Update dependencies --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 82bf8ff009..817331c57d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,7 +38,7 @@ junitVersion=5.10.2 gatlingVersion=3.10.3 slf4jVersion=2.0.11 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.7 +mkdocsMaterialVersion=9.5.8 mermaidDokkaVersion=0.4.4 nativeToolsVersion=0.10.0 @@ -47,7 +47,7 @@ nettyVersion=4.1.106.Final nettyTcNativeVersion=2.0.62.Final # http_server_helidon -helidonVersion=4.0.4 +helidonVersion=4.0.5 # http_server_servlet servletVersion=6.0.0 From bd2901e0b5c78fe5e0e4bd0a56c6aacf51677ce5 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 8 Feb 2024 08:27:08 +0100 Subject: [PATCH 14/28] Exclude experimental modules from coverage --- site/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/build.gradle.kts b/site/build.gradle.kts index f3131d52e0..d84e1f700c 100644 --- a/site/build.gradle.kts +++ b/site/build.gradle.kts @@ -20,6 +20,8 @@ tasks.register("jacocoRootReport") { .filterNot { it.absolutePath.contains("templates_test") } .filterNot { it.absolutePath.contains("rest") } .filterNot { it.absolutePath.contains("rest_tools") } + .filterNot { it.absolutePath.contains("serverless_http") } + .filterNot { it.absolutePath.contains("serverless_http_google") } .filterNot { it.absolutePath.contains("web") } .toList() // TODO Include the filtered modules when they are ready From 1bb2d24a51a252f53bda0fbc4eb98fc6c5719743 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 8 Feb 2024 08:51:26 +0100 Subject: [PATCH 15/28] Simplify logging --- .../com/hexagonkt/core/ClasspathHandler.kt | 3 ++- .../com/hexagonkt/core/logging/Logger.kt | 27 ++++++++++--------- .../core/ClasspathHandlerProviderTest.kt | 2 +- .../com/hexagonkt/core/logging/LoggingTest.kt | 5 ++++ core/src/test/resources/sample.properties | 9 ++++--- .../src/test/resources/logging.properties | 10 +++---- http/http_server_netty/build.gradle.kts | 2 ++ .../com/hexagonkt/http/test/BaseTest.kt | 4 +++ .../src/main/resources/logging.properties | 9 +++++++ 9 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 http/http_test/src/main/resources/logging.properties diff --git a/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt b/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt index 4c1e9038b3..7b6ca9c1b6 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt @@ -1,5 +1,6 @@ package com.hexagonkt.core +import com.hexagonkt.core.logging.logger import java.net.URL import java.net.URLConnection import java.net.URLStreamHandler @@ -21,7 +22,7 @@ object ClasspathHandler : URLStreamHandler() { } } catch (e: Error) { - e.printStackTrace() + logger.error(e) } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt index 55d82cef7e..eb507deb76 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt @@ -15,6 +15,15 @@ class Logger( val name: String, internal val logger: System.Logger = System.getLogger(name) ) { + + /** + * Logger class with Kotlin improvements like lazy evaluation. + * + * @param type Logger type. It is shown in the logs messages and used for log filtering. + */ + constructor(type: KClass<*>): + this(type.qualifiedName ?: error("Cannot get qualified name of type")) + /** * Check if this logger is enabled for a given log level. * @@ -47,21 +56,13 @@ class Logger( logger.log(level, messageSupplier) } - /** - * Logger class with Kotlin improvements like lazy evaluation. - * - * @param type Logger type. It is shown in the logs messages and used for log filtering. - */ - constructor(type: KClass<*>): - this(type.qualifiedName ?: error("Cannot get qualified name of type")) - /** * Log a message using [TRACE] level. * * @param message The required message to log. */ fun trace(message: () -> Any?) { - logger.log(TRACE, message) + log(TRACE, message) } /** @@ -70,7 +71,7 @@ class Logger( * @param message The required message to log. */ fun debug(message: () -> Any?) { - logger.log(DEBUG, message) + log(DEBUG, message) } /** @@ -79,7 +80,7 @@ class Logger( * @param message The required message to log. */ fun info(message: () -> Any?) { - logger.log(INFO, message) + log(INFO, message) } /** @@ -88,7 +89,7 @@ class Logger( * @param message The required message to log. */ fun warn(message: () -> Any?) { - logger.log(WARNING, message) + log(WARNING, message) } /** @@ -97,7 +98,7 @@ class Logger( * @param message The required message to log. */ fun error(message: () -> Any?) { - logger.log(ERROR, message) + log(ERROR, message) } /** diff --git a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt index c9ab44a238..75fc882615 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt @@ -43,7 +43,7 @@ internal class ClasspathHandlerProviderTest { @Test fun `Read classpath resource returns resource's text` () { val resourceText = urlOf("classpath:sample.properties").readText() - assert(resourceText.contains("handlers=com.hexagonkt.core.logging.jul.SystemStreamHandler")) + assert(resourceText.contains("handlers=java.util.logging.ConsoleHandler")) } @Test fun `Unknown protocol throws exception`() { diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt index 381596c91f..63ed5cbf84 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt @@ -1,11 +1,16 @@ package com.hexagonkt.core.logging +import com.hexagonkt.core.urlOf import org.junit.jupiter.api.Test +import java.util.logging.LogManager import kotlin.test.assertEquals internal class LoggingTest { @Test fun `Log helpers`() { + val configuration = urlOf("classpath:sample.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + assertEquals(defaultLoggerName, logger.name) assertEquals("foo", "foo".trace(">>> ")) diff --git a/core/src/test/resources/sample.properties b/core/src/test/resources/sample.properties index 224547780c..79a82dd5c7 100644 --- a/core/src/test/resources/sample.properties +++ b/core/src/test/resources/sample.properties @@ -1,6 +1,9 @@ -handlers=com.hexagonkt.core.logging.jul.SystemStreamHandler +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n -.level=ALL +.level=INFO -com.hexagonkt.core.logging.jul.JulLoggingAdapter.level=WARNING +com.hexagonkt.level=ALL diff --git a/http/http_client_jetty/src/test/resources/logging.properties b/http/http_client_jetty/src/test/resources/logging.properties index 1ab53cda83..6092598869 100644 --- a/http/http_client_jetty/src/test/resources/logging.properties +++ b/http/http_client_jetty/src/test/resources/logging.properties @@ -1,11 +1,9 @@ handlers=java.util.logging.ConsoleHandler - -.level=SEVERE - -java.util.logging.ConsoleHandler.level=TRACE +java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter - java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n -com.hexagonkt.level=WARNING +.level=ERROR + +com.hexagonkt.level=ALL diff --git a/http/http_server_netty/build.gradle.kts b/http/http_server_netty/build.gradle.kts index 55b8f57680..2237b36845 100644 --- a/http/http_server_netty/build.gradle.kts +++ b/http/http_server_netty/build.gradle.kts @@ -14,6 +14,7 @@ description = "HTTP server adapter for Netty." dependencies { val nettyVersion = properties["nettyVersion"] val nettyTcNativeVersion = properties["nettyTcNativeVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server")) "api"("io.netty:netty-codec-http2:$nettyVersion") { exclude(group = "org.slf4j") } @@ -27,4 +28,5 @@ dependencies { "testImplementation"(project(":http:http_client_jetty_ws")) "testImplementation"(project(":serialization:serialization_jackson_json")) "testImplementation"(project(":serialization:serialization_jackson_yaml")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt index 9664240415..d4d08646fe 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt @@ -15,6 +15,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.util.logging.LogManager import kotlin.test.assertEquals @TestInstance(PER_CLASS) @@ -35,6 +36,9 @@ abstract class BaseTest { } @BeforeAll fun startUp() { + val configuration = urlOf("classpath:logging.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + server.start() client.start() } diff --git a/http/http_test/src/main/resources/logging.properties b/http/http_test/src/main/resources/logging.properties new file mode 100644 index 0000000000..79a82dd5c7 --- /dev/null +++ b/http/http_test/src/main/resources/logging.properties @@ -0,0 +1,9 @@ + +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n + +.level=INFO + +com.hexagonkt.level=ALL From 2ec94429c443896cf8d2327c11e813ae1af8f47c Mon Sep 17 00:00:00 2001 From: jaguililla Date: Wed, 14 Feb 2024 08:01:07 +0100 Subject: [PATCH 16/28] Update dependencies --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 817331c57d..753cc50b4f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,8 +38,8 @@ junitVersion=5.10.2 gatlingVersion=3.10.3 slf4jVersion=2.0.11 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.8 -mermaidDokkaVersion=0.4.4 +mkdocsMaterialVersion=9.5.9 +mermaidDokkaVersion=0.6.0 nativeToolsVersion=0.10.0 # http_server_netty From 8c651646bbd15066aa8cb13c9718eb6134adf93f Mon Sep 17 00:00:00 2001 From: jaguililla Date: Fri, 16 Feb 2024 19:08:24 +0100 Subject: [PATCH 17/28] Fix tests logging --- .../kotlin/com/hexagonkt/core/logging/LoggerTest.kt | 11 +++++++++++ .../kotlin/com/hexagonkt/core/logging/LoggingTest.kt | 8 +++++++- core/src/test/resources/sample.properties | 9 ++++++++- http/http_client_jetty/build.gradle.kts | 2 ++ .../src/test/resources/logging.properties | 9 --------- http/http_client_jetty_ws/build.gradle.kts | 2 ++ http/http_test/build.gradle.kts | 8 ++++++-- http/http_test/src/main/resources/logging.properties | 9 ++++++++- http/rest/build.gradle.kts | 2 ++ http/rest_tools/build.gradle.kts | 2 ++ .../rest/tools/openapi/VerifySpecCallback.kt | 2 ++ http/rest_tools/src/main/kotlin/module-info.java_ | 1 - http/web/build.gradle.kts | 2 ++ 13 files changed, 52 insertions(+), 15 deletions(-) delete mode 100644 http/http_client_jetty/src/test/resources/logging.properties diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt index 377ea3f6ef..a7d18c37ad 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt @@ -4,18 +4,29 @@ import com.hexagonkt.core.text.Ansi.RESET import com.hexagonkt.core.text.AnsiColor.BRIGHT_WHITE import com.hexagonkt.core.text.AnsiColor.RED_BG import com.hexagonkt.core.text.AnsiEffect.UNDERLINE +import com.hexagonkt.core.urlOf import io.mockk.every import io.mockk.mockk +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.condition.DisabledInNativeImage import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import java.lang.System.Logger.Level.ERROR import java.lang.System.Logger.Level.TRACE +import java.util.logging.LogManager import kotlin.IllegalStateException import kotlin.reflect.KClass import kotlin.test.* +@TestInstance(PER_CLASS) internal class LoggerTest { + @BeforeAll fun setUp() { + val configuration = urlOf("classpath:sample.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + } + @Suppress("RedundantExplicitType", "UNUSED_VARIABLE") // Ignored for examples generation @Test fun loggerUsage() { // logger diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt index 63ed5cbf84..720d677b66 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt @@ -1,16 +1,22 @@ package com.hexagonkt.core.logging import com.hexagonkt.core.urlOf +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import java.util.logging.LogManager import kotlin.test.assertEquals +@TestInstance(PER_CLASS) internal class LoggingTest { - @Test fun `Log helpers`() { + @BeforeAll fun setUp() { val configuration = urlOf("classpath:sample.properties") LogManager.getLogManager().readConfiguration(configuration.openStream()) + } + @Test fun `Log helpers`() { assertEquals(defaultLoggerName, logger.name) assertEquals("foo", "foo".trace(">>> ")) diff --git a/core/src/test/resources/sample.properties b/core/src/test/resources/sample.properties index 79a82dd5c7..ac1553aa19 100644 --- a/core/src/test/resources/sample.properties +++ b/core/src/test/resources/sample.properties @@ -2,7 +2,14 @@ handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n +#java.util.logging.SimpleFormatter.format=%1$tH:%1$ Date: Sun, 18 Feb 2024 10:13:13 +0100 Subject: [PATCH 18/28] Improve REST utilities --- .../com/hexagonkt/rest/tools/StateHttpClient.kt | 2 ++ .../com/hexagonkt/rest/tools/DynamicServerTest.kt | 14 +++++++------- .../hexagonkt/rest/tools/StateHttpClientTest.kt | 6 ++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt index 12806a3730..81178d9611 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/StateHttpClient.kt @@ -44,9 +44,11 @@ data class StateHttpClient( ) private val client = HttpClient(adapter, settings, handler = handler) + private lateinit var lastRequest: HttpRequest private lateinit var lastAttributes: Map private lateinit var lastResponse: HttpResponsePort + val request: HttpRequest get() = lastRequest val attributes: Map get() = lastAttributes val response: HttpResponsePort get() = lastResponse diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt index fae33cf311..42bd4e81d5 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt @@ -49,8 +49,9 @@ class DynamicHttpServerTest { val url = "http://localhost:${dynamicServer.runtimePort}" StateHttpClient(JettyClientAdapter(), url).request { + start() get("/hello/mike") - assertEquals(OK_200, response.status) + assertOk() } } @@ -64,18 +65,17 @@ class DynamicHttpServerTest { val url = "http://localhost:${dynamicServer.runtimePort}" StateHttpClient(JettyClientAdapter(), url).request { get("/foo") - assertEquals(OK_200, response.status) - assertEquals("dynamic", response.body) + assertOk() + assertBody("dynamic") dynamicServer.path = path { get("/foo") { ok("changed") } } + stop() get("/foo") - assertEquals(OK_200, response.status) - assertEquals(OK_200, response.status) - assertEquals("changed", response.body) - assertEquals("changed", response.body) + assertOk() + assertBody("changed") } } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt index 201cda52f0..2153739787 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt @@ -67,6 +67,12 @@ internal class StateHttpClientTest { delete("/hello/mike").assertBody("DELETE /hello/mike", headers) patch("/hello/mike").assertBody("PATCH /hello/mike", headers) trace("/hello/mike").assertBody("TRACE /hello/mike", headers) + + assertEquals(text, contentType) + assertEquals("TRACE /hello/mike", body) + assertEquals(headers, this.headers) + assertTrue(cookies.isEmpty()) + assertTrue(attributes.isEmpty()) } } From 91cb349451718cce72a75d607888659194ccab6c Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 18 Feb 2024 13:29:40 +0100 Subject: [PATCH 19/28] Check macOS building --- http/http_server_netty/build.gradle.kts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/http/http_server_netty/build.gradle.kts b/http/http_server_netty/build.gradle.kts index 2237b36845..6429654fd5 100644 --- a/http/http_server_netty/build.gradle.kts +++ b/http/http_server_netty/build.gradle.kts @@ -13,16 +13,16 @@ description = "HTTP server adapter for Netty." dependencies { val nettyVersion = properties["nettyVersion"] - val nettyTcNativeVersion = properties["nettyTcNativeVersion"] +// val nettyTcNativeVersion = properties["nettyTcNativeVersion"] val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server")) "api"("io.netty:netty-codec-http2:$nettyVersion") { exclude(group = "org.slf4j") } - if (System.getProperty("os.name").lowercase().contains("mac")) - "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { - exclude(group = "org.slf4j") - } +// if (System.getProperty("os.name").lowercase().contains("mac")) +// "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { +// exclude(group = "org.slf4j") +// } "testImplementation"(project(":http:http_test")) "testImplementation"(project(":http:http_client_jetty_ws")) From afd9438955aca3430b7fb9481342a7daff973bf4 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 18 Feb 2024 13:41:58 +0100 Subject: [PATCH 20/28] Check macOS building --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 753cc50b4f..fc52059481 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,14 +36,14 @@ dokkaVersion=1.9.10 mockkVersion=1.13.9 junitVersion=5.10.2 gatlingVersion=3.10.3 -slf4jVersion=2.0.11 +slf4jVersion=2.0.12 jmhVersion=1.37 mkdocsMaterialVersion=9.5.9 mermaidDokkaVersion=0.6.0 nativeToolsVersion=0.10.0 # http_server_netty -nettyVersion=4.1.106.Final +nettyVersion=4.1.107.Final nettyTcNativeVersion=2.0.62.Final # http_server_helidon From 2c62199fc92f36ab70b5a0c1026d7944d4780611 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 18 Feb 2024 18:26:18 +0100 Subject: [PATCH 21/28] Check macOS building --- http/http_test/src/main/resources/logging.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/http_test/src/main/resources/logging.properties b/http/http_test/src/main/resources/logging.properties index ac1553aa19..5c9f23e3ea 100644 --- a/http/http_test/src/main/resources/logging.properties +++ b/http/http_test/src/main/resources/logging.properties @@ -13,4 +13,4 @@ java.util.logging.SimpleFormatter.format=\ .level=INFO -com.hexagonkt.level=ALL +com.hexagonkt.level=INFO From 2f05670b1a712620858a938aa17ff876be5f8e8b Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 18 Feb 2024 18:46:40 +0100 Subject: [PATCH 22/28] Check macOS building --- .../kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt index 2153739787..f05f2fcb98 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt @@ -69,8 +69,7 @@ internal class StateHttpClientTest { trace("/hello/mike").assertBody("TRACE /hello/mike", headers) assertEquals(text, contentType) - assertEquals("TRACE /hello/mike", body) - assertEquals(headers, this.headers) + assertBodyContains("TRACE /hello/mike") assertTrue(cookies.isEmpty()) assertTrue(attributes.isEmpty()) } From 8b1a56b46b6682e6298c2066f625088a5ca2c457 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Sun, 18 Feb 2024 18:49:53 +0100 Subject: [PATCH 23/28] Check macOS building --- .../serverless_http_google/build.gradle.kts | 3 ++- .../google/GoogleServerlessHttpAdapterTest.kt | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/serverless/serverless_http_google/build.gradle.kts b/serverless/serverless_http_google/build.gradle.kts index a58d9e082b..0bd7495a2f 100644 --- a/serverless/serverless_http_google/build.gradle.kts +++ b/serverless/serverless_http_google/build.gradle.kts @@ -23,8 +23,9 @@ dependencies { "testImplementation"("com.google.cloud.functions:functions-framework-api:$functionsVersion") "testImplementation"("com.google.cloud.functions.invoker:java-function-invoker:$invokerVersion") + "testImplementation"(project(":http:http_client_jetty")) - invoker("com.google.cloud.functions.invoker:java-function-invoker:1.3.1") + "invoker"("com.google.cloud.functions.invoker:java-function-invoker:$invokerVersion") } tasks.register("runFunction") { diff --git a/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt index a2e9bfdab5..5bdb465ad0 100644 --- a/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt +++ b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt @@ -1,13 +1,28 @@ package com.hexagonkt.serverless.http.google import com.google.cloud.functions.invoker.runner.Invoker +import com.hexagonkt.core.freePort +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.client.HttpClient +import com.hexagonkt.http.client.HttpClientSettings +import com.hexagonkt.http.client.jetty.JettyClientAdapter +import kotlin.reflect.KClass import kotlin.test.Test internal class GoogleServerlessHttpAdapterTest { @Test fun `Google functions work ok`() { -// Invoker.main( -// -// ) + val port = freePort() +// val invoker = invoker(port, GoogleServerlessHttpAdapter::class) + val client = HttpClient(JettyClientAdapter(), HttpClientSettings(urlOf("http://localhost:${port}"))) + } + + private fun invoker(port: Int, function: KClass<*>): Invoker { + val target = function.qualifiedName + val classLoader = ClassLoader.getSystemClassLoader() + val invoker = Invoker(port, target, null, classLoader) +// invoker.startTestServer() + invoker.startServer() + return invoker } } From 464c035ede32f493db1da01d7928b56b6aedf668 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Tue, 20 Feb 2024 11:36:59 +0100 Subject: [PATCH 24/28] Update dependencies --- build.gradle.kts | 2 +- gradle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3cb688d48d..cb013e7dfe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ plugins { id("org.jetbrains.dokka") version("1.9.10") id("com.github.jk1.dependency-license-report") version("2.5") id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.14.0") - id("org.graalvm.buildtools.native") version("0.10.0") apply(false) + id("org.graalvm.buildtools.native") version("0.10.1") apply(false) id("io.gitlab.arturbosch.detekt") version("1.23.5") apply(false) id("me.champeau.jmh") version("0.7.2") apply(false) } diff --git a/gradle.properties b/gradle.properties index fc52059481..395490ea93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,9 +38,9 @@ junitVersion=5.10.2 gatlingVersion=3.10.3 slf4jVersion=2.0.12 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.9 +mkdocsMaterialVersion=9.5.10 mermaidDokkaVersion=0.6.0 -nativeToolsVersion=0.10.0 +nativeToolsVersion=0.10.1 # http_server_netty nettyVersion=4.1.107.Final From a6df14571da5c4e70480d3f701ea1a6388822065 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Wed, 21 Feb 2024 09:50:23 +0100 Subject: [PATCH 25/28] Fix Netty native tests on macOS --- http/http_server_netty/build.gradle.kts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/http/http_server_netty/build.gradle.kts b/http/http_server_netty/build.gradle.kts index 6429654fd5..2237b36845 100644 --- a/http/http_server_netty/build.gradle.kts +++ b/http/http_server_netty/build.gradle.kts @@ -13,16 +13,16 @@ description = "HTTP server adapter for Netty." dependencies { val nettyVersion = properties["nettyVersion"] -// val nettyTcNativeVersion = properties["nettyTcNativeVersion"] + val nettyTcNativeVersion = properties["nettyTcNativeVersion"] val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server")) "api"("io.netty:netty-codec-http2:$nettyVersion") { exclude(group = "org.slf4j") } -// if (System.getProperty("os.name").lowercase().contains("mac")) -// "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { -// exclude(group = "org.slf4j") -// } + if (System.getProperty("os.name").lowercase().contains("mac")) + "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { + exclude(group = "org.slf4j") + } "testImplementation"(project(":http:http_test")) "testImplementation"(project(":http:http_client_jetty_ws")) From 37ee9ea99b168fe6c02bb73de7f825f07f64d51d Mon Sep 17 00:00:00 2001 From: jaguililla Date: Mon, 26 Feb 2024 23:42:08 +0100 Subject: [PATCH 26/28] Update dependencies --- gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 395490ea93..1ddda872e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.warning.mode=all org.gradle.console=plain # Gradle -version=3.4.8 +version=3.5.0 group=com.hexagonkt description=The atoms of your platform @@ -35,16 +35,16 @@ kotlinVersion=1.9.22 dokkaVersion=1.9.10 mockkVersion=1.13.9 junitVersion=5.10.2 -gatlingVersion=3.10.3 +gatlingVersion=3.10.4 slf4jVersion=2.0.12 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.10 +mkdocsMaterialVersion=9.5.11 mermaidDokkaVersion=0.6.0 nativeToolsVersion=0.10.1 # http_server_netty nettyVersion=4.1.107.Final -nettyTcNativeVersion=2.0.62.Final +nettyTcNativeVersion=2.0.63.Final # http_server_helidon helidonVersion=4.0.5 From 35f56e90fcc82b163ea67bc7a2aa52ff9c43e7ac Mon Sep 17 00:00:00 2001 From: jaguililla Date: Tue, 27 Feb 2024 21:02:09 +0100 Subject: [PATCH 27/28] Disable serverless for release --- build.gradle.kts | 4 ++-- http/http_server_netty/build.gradle.kts | 4 +--- settings.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index cb013e7dfe..13e64c90eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -159,8 +159,8 @@ apiValidation { // Experimental modules "rest", "rest_tools", - "serverless_http", - "serverless_http_google", +// "serverless_http", +// "serverless_http_google", "web", "templates_jte", ) diff --git a/http/http_server_netty/build.gradle.kts b/http/http_server_netty/build.gradle.kts index 2237b36845..538f9763a1 100644 --- a/http/http_server_netty/build.gradle.kts +++ b/http/http_server_netty/build.gradle.kts @@ -20,9 +20,7 @@ dependencies { "api"("io.netty:netty-codec-http2:$nettyVersion") { exclude(group = "org.slf4j") } if (System.getProperty("os.name").lowercase().contains("mac")) - "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { - exclude(group = "org.slf4j") - } + "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") "testImplementation"(project(":http:http_test")) "testImplementation"(project(":http:http_client_jetty_ws")) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d60af98f5..9b8130bdc0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,7 +11,7 @@ include( includeNestedModules( "http", "serialization", - "serverless", +// "serverless", "templates" ) From a99018527f25b26ca055789ab89d56ff9754cfd0 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Tue, 27 Feb 2024 23:06:56 +0100 Subject: [PATCH 28/28] Fix site links --- core/README.md | 10 ++-------- http/http_client/README.md | 8 ++++---- http/http_handlers/README.md | 20 ++++++++++---------- http/http_server/README.md | 26 ++++++++++++-------------- site/pages/index.md | 19 ++++++++----------- templates/templates/README.md | 2 +- 6 files changed, 37 insertions(+), 48 deletions(-) diff --git a/core/README.md b/core/README.md index 68a8951fc6..ba05616d5a 100644 --- a/core/README.md +++ b/core/README.md @@ -42,16 +42,10 @@ The following code block shows the most common use cases for the [Logger] class: @code core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt?logger -By default, Hexagon uses the [Java Util Logging] logging library, you can use any of its -implementations by just adding another logging adapter as a dependency. Below you can see some -alternatives: - -* [Logback](/logging_logback) -* [SLF4J JUL](/logging_slf4j_jul) +By default, Hexagon uses the [System.Logger] class. [Logger]: /api/core/com.hexagonkt.core.logging/-logger -[Java Util Logging]: -https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html +[System.Logger]: https://docs.oracle.com/javase/9/docs/api/java/lang/System.Logger.html # Package com.hexagonkt.core.media Media types definitions and constants for default media types. diff --git a/http/http_client/README.md b/http/http_client/README.md index e5a9614dce..a98f184aa1 100644 --- a/http/http_client/README.md +++ b/http/http_client/README.md @@ -40,8 +40,8 @@ Check this code snippet to get a glimpse on how to send the most general request @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt?genericRequest -[Request]: /api/http_client/com.hexagonkt.http.client.model/-http-client-request -[Response]: /api/http_client/com.hexagonkt.http.client.model/-http-client-response +[Request]: /api/http/http/com.hexagonkt.http.model/-http-request +[Response]: /api/http/http/com.hexagonkt.http.model/-http-response # Simple requests shortcuts There are utility methods to make the most common request in an easy way. @@ -89,8 +89,8 @@ format. To set up client/server certificates, you need to include [SslSettings] in your [ClientSettings]. In the sections below you can see how to configure these parameters. -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings -[ClientSettings]: /api/http_client/com.hexagonkt.http.client/-http-client-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings +[ClientSettings]: /api/http/http_client/com.hexagonkt.http.client/-http-client-settings ## Key Store This store holds the identity certificate, this certificate is presented to the server by the client diff --git a/http/http_handlers/README.md b/http/http_handlers/README.md index be496a5529..275de1f367 100644 --- a/http/http_handlers/README.md +++ b/http/http_handlers/README.md @@ -77,10 +77,10 @@ methods. @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?serverCreation -[server settings]: /api/http_server/com.hexagonkt.http.server/-http-server-settings +[server settings]: /api/http/http_server/com.hexagonkt.http.server/-http-server-settings [handlers section]: /http_server/#handlers -[start()]: /api/http_server/com.hexagonkt.http.server/-http-server/start.html -[stop()]: /api/http_server/com.hexagonkt.http.server/-http-server/stop.html +[start()]: /api/http/http_server/com.hexagonkt.http.server/-http-server +[stop()]: /api/http/http_server/com.hexagonkt.http.server/-http-server ## Servlet Web server There is a special server adapter for running inside Servlet Containers. To use it you should import @@ -103,7 +103,7 @@ full list of methods. This sample code illustrates the usage: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackCall -[API documentation]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context +[API documentation]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context # Handlers The main building blocks of Hexagon HTTP services are a set of handlers. A handler is made up of two @@ -139,7 +139,7 @@ Check the next snippet for Handlers usage examples: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?routesCreation -[next]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context/next.html +[next]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context @@ -157,7 +157,7 @@ the following fields: It yields true if all the supplied fields matches a call context. -[HttpPredicate]: /api/http_server/com.hexagonkt.http.handlers/-http-predicate +[HttpPredicate]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-predicate ## Path Patterns Patterns to match requests paths. They can have: @@ -302,7 +302,7 @@ different handlers. Check the [CorsCallback][CORS Callbacks] class for more deta @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/CorsTest.kt?cors -[CORS Callbacks]: /api/http_server/com.hexagonkt.http.server.callbacks/-cors-callback +[CORS Callbacks]: /api/http/http_server/com.hexagonkt.http.server.callbacks/-cors-callback # HTTPS It is possible to start a secure server enabling HTTPS. For this, you have to provide a server @@ -327,14 +327,14 @@ Below you can find a simple example to set up an HTTPS server and client with mu @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt?https -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings [HTTP/2]: https://en.wikipedia.org/wiki/HTTP/2 [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation [Gradle]: https://gradle.org [create sample certificates]: /gradle/#certificates [mutual TLS]: https://en.wikipedia.org/wiki/Mutual_authentication -[SslSettings.clientAuth]: /api/http/com.hexagonkt.http/-ssl-settings/client-auth.html -[Request.certificateChain]: /api/http_server/com.hexagonkt.http.server.model/-http-server-request/certificate-chain.html +[SslSettings.clientAuth]: /api/http/http/com.hexagonkt.http/-ssl-settings +[Request.certificateChain]: /api/http/http/com.hexagonkt.http.model/-http-request # WebSockets A Web Socket is an HTTP(S) connection made with the GET method and the `upgrade: websocket` and diff --git a/http/http_server/README.md b/http/http_server/README.md index fae94d34a1..643f99766d 100644 --- a/http/http_server/README.md +++ b/http/http_server/README.md @@ -72,10 +72,10 @@ methods. @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?serverCreation -[server settings]: /api/http_server/com.hexagonkt.http.server/-http-server-settings +[server settings]: /api/http/http_server/com.hexagonkt.http.server/-http-server-settings [handlers section]: /http_server/#handlers -[start()]: /api/http_server/com.hexagonkt.http.server/-http-server/start.html -[stop()]: /api/http_server/com.hexagonkt.http.server/-http-server/stop.html +[start()]: /api/http/http_server/com.hexagonkt.http.server/-http-server +[stop()]: /api/http/http_server/com.hexagonkt.http.server/-http-server ## Servlet Web server There is a special server adapter for running inside Servlet Containers. To use it you should import @@ -98,7 +98,7 @@ full list of methods. This sample code illustrates the usage: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackCall -[API documentation]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context +[API documentation]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context # Handlers The main building blocks of Hexagon HTTP services are a set of handlers. A handler is made up of two @@ -134,7 +134,7 @@ Check the next snippet for Handlers usage examples: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?routesCreation -[next]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context/next.html +[next]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context @@ -152,7 +152,7 @@ the following fields: It yields true if all the supplied fields matches a call context. -[HttpPredicate]: /api/http_server/com.hexagonkt.http.handlers/-http-predicate +[HttpPredicate]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-predicate ## Path Patterns Patterns to match requests paths. They can have: @@ -297,7 +297,7 @@ different handlers. Check the [CorsCallback][CORS Callbacks] class for more deta @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/CorsTest.kt?cors -[CORS Callbacks]: /api/http_server/com.hexagonkt.http.server.callbacks/-cors-callback +[CORS Callbacks]: /api/http/http_server/com.hexagonkt.http.server.callbacks/-cors-callback # HTTPS It is possible to start a secure server enabling HTTPS. For this, you have to provide a server @@ -322,14 +322,14 @@ Below you can find a simple example to set up an HTTPS server and client with mu @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt?https -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings [HTTP/2]: https://en.wikipedia.org/wiki/HTTP/2 [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation [Gradle]: https://gradle.org [create sample certificates]: /gradle/#certificates [mutual TLS]: https://en.wikipedia.org/wiki/Mutual_authentication -[SslSettings.clientAuth]: /api/http/com.hexagonkt.http/-ssl-settings/client-auth.html -[Request.certificateChain]: /api/http_server/com.hexagonkt.http.server.model/-http-server-request/certificate-chain.html +[SslSettings.clientAuth]: /api/http/http/com.hexagonkt.http/-ssl-settings +[Request.certificateChain]: /api/http/http/com.hexagonkt.http.model/-http-request # WebSockets A Web Socket is an HTTP(S) connection made with the GET method and the `upgrade: websocket` and @@ -395,9 +395,7 @@ Contains the HTTP handlers implementation (on top of Core's general event handle HTTP handlers (AfterHandler, OnHandler, PathHandler and FilterHandler) and the HTTP predicate. # Package com.hexagonkt.http.server.model -Classes to model server HTTP messages (requests and responses). Built on top of the [http] module. +Classes to model server HTTP messages (requests and responses). Built on top of the `http` module. # Package com.hexagonkt.http.model.ws -Classes to model server HTTP messages (requests and responses). Built on top of the [http] module. - -[http]: /http +Classes to model server HTTP messages (requests and responses). Built on top of the `http` module. diff --git a/site/pages/index.md b/site/pages/index.md index 34c8364cd5..1cd51ce6da 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -119,14 +119,11 @@ Singleton object to manage a cross toolkit aspect. I.e., Serialization or Templa The libraries inside the `hexagon_extra` repository provide extra features. They may be useful to develop applications, but not strictly required. Some of these modules are: -* [Schedulers]: Provides repeated tasks execution based on [Cron] expressions. -* [Models]: Contain classes that model common data objects. -* [Args]: Command line arguments definition and parsing. +* Schedulers: Provides repeated tasks execution based on [Cron] expressions. +* Models: Contain classes that model common data objects. +* Args: Command line arguments definition and parsing. [Web]: /web -[Schedulers]: /scheduler -[Models]: /models -[Args]: /args [Cron]: https://en.wikipedia.org/wiki/Cron # Architecture @@ -163,11 +160,11 @@ Ports with their provided implementations (Adapters). [Rocker]: /templates_rocker [jte]: /templates_jte [Serialization Formats]: /core/#serialization -[JSON]: /api/serialization_jackson_json/com.hexagonkt.serialization.jackson.json/-json -[YAML]: /api/serialization_jackson_yaml/com.hexagonkt.serialization.jackson.yaml/-yaml -[CSV]: /api/serialization_jackson_csv/com.hexagonkt.serialization.jackson.csv/-csv -[XML]: /api/serialization_jackson_xml/com.hexagonkt.serialization.jackson.xml/-xml -[TOML]: /api/serialization_jackson_toml/com.hexagonkt.serialization.jackson.toml/-toml +[JSON]: /api/serialization/serialization_jackson_json/com.hexagonkt.serialization.jackson.json/-json +[YAML]: /api/serialization/serialization_jackson_yaml/com.hexagonkt.serialization.jackson.yaml/-yaml +[CSV]: /api/serialization/serialization_jackson_csv/com.hexagonkt.serialization.jackson.csv/-csv +[XML]: /api/serialization/serialization_jackson_xml/com.hexagonkt.serialization.jackson.xml/-xml +[TOML]: /api/serialization/serialization_jackson_toml/com.hexagonkt.serialization.jackson.toml/-toml # Module Dependencies Module dependencies (including extra modules): diff --git a/templates/templates/README.md b/templates/templates/README.md index 98c203d7cc..20ace00fc3 100644 --- a/templates/templates/README.md +++ b/templates/templates/README.md @@ -31,7 +31,7 @@ and `_now_` variables) are added to the context automatically. Check the code be @code templates/templates/src/test/kotlin/com/hexagonkt/templates/examples/TemplatesTest.kt?templateUsage -[TemplateManager]: /api/templates/com.hexagonkt.templates/-template-manager/index.html +[TemplateManager]: /api/templates/templates/com.hexagonkt.templates/-template-manager # Package com.hexagonkt.templates Feature implementation code.