diff --git a/.gitignore b/.gitignore index 5bfae36ff8..2952e0d9e6 100644 --- a/.gitignore +++ b/.gitignore @@ -40,9 +40,7 @@ hs_err_pid* # Other Tools kotlin-js-store/ node_modules/ -package-lock.json .env -.sdkmanrc # System Files .DS_Store diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000000..9584f14f9c --- /dev/null +++ b/.sdkmanrc @@ -0,0 +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 diff --git a/README.md b/README.md index 2e37349688..2ef834bdab 100644 --- a/README.md +++ b/README.md @@ -447,8 +447,8 @@ If you like this project and want to support it, the easiest way is to [give it If you feel like you can do more. You can contribute to the project in different ways: -* By using it and [spreading the word][@hexagon_kt]. -* Giving feedback by [Twitter][@hexagon_kt] or [Slack]. +* By using it and [spreading the word][@hexagontk]. +* Giving feedback by [X (Twitter)][@hexagontk] or [Slack]. * Requesting [new features or submitting bugs][issues]. * Voting for the features you want in the [issue tracker][issues] (using [reactions]). * And... Drum roll... Submitting [code or documentation][contributing]. @@ -457,14 +457,14 @@ To know what issues are currently open and be aware of the next features you can [Organization Board] at GitHub. You can ask any question, suggestion or complaint at the project's [Slack channel][Slack]. You can -be up-to-date of project's news following [@hexagon_kt] on Twitter. +be up-to-date of project's news following [@hexagontk] on X (Twitter). Thanks to all project's [contributors]! [![CodeTriage](https://www.codetriage.com/hexagonkt/hexagon/badges/users.svg)][CodeTriage] [give it a star]: https://github.com/hexagonkt/hexagon/stargazers -[@hexagon_kt]: https://twitter.com/hexagon_kt +[@hexagontk]: https://twitter.com/hexagontk [Slack]: https://kotlinlang.slack.com/messages/hexagon [issues]: https://github.com/hexagonkt/hexagon/issues [reactions]: https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments diff --git a/contributing.md b/contributing.md index fc815643ae..cbda36f021 100644 --- a/contributing.md +++ b/contributing.md @@ -7,12 +7,12 @@ process or picking a task and building the code. You can use the repository's [Discussions tab][discussion] to ask questions or resolve problems. You can also ask any question, make suggestions or complaints at the project's -[Slack channel][Slack]. You can also be up-to-date of project's news following [@hexagon_kt] on -Twitter. +[Slack channel][Slack]. You can also be up-to-date of project's news following [@hexagontk] on +X (Twitter). [discussion]: https://github.com/hexagonkt/hexagon/discussions [Slack]: https://kotlinlang.slack.com/messages/hexagon -[@hexagon_kt]: https://twitter.com/hexagon_kt +[@hexagontk]: https://twitter.com/hexagontk ## Report a Bug To file a bug, create an issue with the [bug template]. @@ -120,7 +120,7 @@ If you want to generate the documentation site, check the Hexagon's site module * Dev.to * Kotlin Slack * Reddit Kotlin - * Twitter + * X (Twitter) * Kotlin Weekly Newsletter * LinkedIn * Mailing lists (Awesome Kotlin, Kotlin Weekly) diff --git a/core/api/core.api b/core/api/core.api index f2b2c5f79f..f707a8e429 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -1,4 +1,5 @@ public final class com/hexagonkt/core/ChecksKt { + public static final fun check (Ljava/lang/String;[Lkotlin/jvm/functions/Function0;)V public static final fun checkSize (Ljava/util/Collection;Lkotlin/ranges/IntRange;)Ljava/util/Collection; public static final fun requireGreater (Ljava/lang/Object;Lkotlin/reflect/KProperty1;Ljava/lang/Object;)V public static final fun requireGreater (Ljava/lang/Object;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)V @@ -103,7 +104,6 @@ public final class com/hexagonkt/core/DatesKt { } public final class com/hexagonkt/core/ExceptionsKt { - public static final fun check (Ljava/lang/String;[Lkotlin/jvm/functions/Function0;)V public static final fun filterStackTrace (Ljava/lang/Throwable;Ljava/lang/String;)[Ljava/lang/StackTraceElement; public static final fun getAssertEnabled ()Z public static final fun getFail ()Ljava/lang/Void; @@ -340,6 +340,7 @@ public final class com/hexagonkt/core/media/MediaTypeGroup : java/lang/Enum { public final class com/hexagonkt/core/media/MediaTypesKt { public static final fun extensionsOf (Lcom/hexagonkt/core/media/MediaType;)Ljava/util/List; + public static final fun getANY_MEDIA ()Lcom/hexagonkt/core/media/MediaType; public static final fun getAPPLICATION_7Z ()Lcom/hexagonkt/core/media/MediaType; public static final fun getAPPLICATION_AVRO ()Lcom/hexagonkt/core/media/MediaType; public static final fun getAPPLICATION_BZIP ()Lcom/hexagonkt/core/media/MediaType; diff --git a/core/src/main/kotlin/com/hexagonkt/core/Checks.kt b/core/src/main/kotlin/com/hexagonkt/core/Checks.kt index 5038539980..2970867f4e 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Checks.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Checks.kt @@ -63,12 +63,34 @@ fun T.requireLowerOrEquals( } /** - * [TODO](https://github.com/hexagonkt/hexagon/issues/271). + * Ensure a collection has a fixed number of elements. * - * @receiver . - * @param count . - * @return . + * @receiver Collection which size will be checked. + * @param count Required number of elements. + * @return Receiver reference (to allow call chaining). */ fun Collection.checkSize(count: IntRange): Collection = this.apply { check(size in count) { "$size items while expecting only $count element" } } + +/** + * Execute a list of code block collecting the exceptions they may throw, in case there is any + * error, it throws a [MultipleException] with all the thrown exceptions. + * + * @param message Error message. + * @param blocks Blocks of code executed and checked. + */ +fun check(message: String, vararg blocks: () -> Unit) { + val exceptions: List = blocks.mapNotNull { + try { + it() + null + } + catch(e: Exception) { + e + } + } + + if (exceptions.isNotEmpty()) + throw MultipleException(message, exceptions) +} diff --git a/core/src/main/kotlin/com/hexagonkt/core/CodedException.kt b/core/src/main/kotlin/com/hexagonkt/core/CodedException.kt index 1b9ab1b57c..f3cf5dbe42 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/CodedException.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/CodedException.kt @@ -3,11 +3,9 @@ package com.hexagonkt.core /** * Exception with a numeric code. * - * [TODO](https://github.com/hexagonkt/hexagon/issues/271). - * - * @property code . - * @property message . - * @property cause . + * @property code Exception code. + * @property message Error message. + * @property cause Parent exception. */ -class CodedException (val code: Int, message: String = "", cause: Throwable? = null) : - RuntimeException (message, cause) +class CodedException(val code: Int, message: String = "", cause: Throwable? = null) : + RuntimeException(message, cause) diff --git a/core/src/main/kotlin/com/hexagonkt/core/Exceptions.kt b/core/src/main/kotlin/com/hexagonkt/core/Exceptions.kt index d93f8426e3..144674aa95 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Exceptions.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Exceptions.kt @@ -44,21 +44,3 @@ fun Throwable.toText(prefix: String = ""): String = this.filterStackTrace(prefix).joinToString(eol, eol) { "\tat $it" } + if (this.cause == null) "" else "${eol}Caused by: " + (this.cause as Throwable).toText(prefix) - -/** - * [TODO](https://github.com/hexagonkt/hexagon/issues/271). - */ -fun check(message: String, vararg blocks: () -> Unit) { - val exceptions: List = blocks.mapNotNull { - try { - it() - null - } - catch(e: Exception) { - e - } - } - - if (exceptions.isNotEmpty()) - throw MultipleException(message, exceptions) -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt index 9f3a3711e4..1ba4aeb608 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt @@ -1,5 +1,6 @@ package com.hexagonkt.core +import com.hexagonkt.core.text.SNAKE_CASE import com.hexagonkt.core.text.parseOrNull import java.io.Console import java.net.InetAddress @@ -13,7 +14,7 @@ import kotlin.reflect.KClass * Object with utilities to gather information about the running JVM. */ object Jvm { - private val systemSettingPattern: Regex by lazy { Regex("[a-zA-Z_]+[a-zA-Z0-9_]*") } + private val systemSettingPattern: Regex by lazy { SNAKE_CASE } /** Operating system name ('os.name' property). If `null` throws an exception. */ val os: String by lazy { os() } @@ -149,7 +150,7 @@ object Jvm { private fun systemSettingRaw(name: String): String? { val correctName = name.matches(systemSettingPattern) require(correctName) { "Setting name must match $systemSettingPattern" } - return System.getenv(name) ?: System.getProperty(name) + return System.getenv(name) ?: System.getenv(name.uppercase()) ?: System.getProperty(name) } /** Operating system name ('os.name' property). If `null` throws an exception. */ diff --git a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt index 001556f963..9ab99a6f06 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt @@ -5,18 +5,20 @@ package com.hexagonkt.core * cause. * * A coded multiple exception should be created this way: + * ```kotlin * CodedException(400, "Many errors", MultipleException()) + * ``` * * To pass a list of causes + * ```kotlin * CodedException (500, "Error", *list) + * ``` * - * [TODO](https://github.com/hexagonkt/hexagon/issues/271). - * - * @property causes . - * @property message . + * @property causes List of causing exceptions. + * @property message Error message. */ -class MultipleException (val causes: List, message: String = "") : - RuntimeException (message, null) { +class MultipleException(val causes: List, message: String = "") : + RuntimeException(message, null) { constructor(vararg causes: Throwable) : this(causes.toList()) constructor(message: String, causes: List) : this(causes, message) diff --git a/core/src/main/kotlin/com/hexagonkt/core/media/MediaTypes.kt b/core/src/main/kotlin/com/hexagonkt/core/media/MediaTypes.kt index 7efb8de3b9..8b493182d7 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/media/MediaTypes.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/media/MediaTypes.kt @@ -9,6 +9,8 @@ import kotlin.io.path.extension val MEDIA_TYPE_FORMAT: Regex by lazy { """\*|([\w+.-]+)""".toRegex() } +val ANY_MEDIA: MediaType by lazy { MediaType(ANY, "*") } + val APPLICATION_AVRO: MediaType by lazy { MediaType(APPLICATION, "avro") } val APPLICATION_CBOR: MediaType by lazy { MediaType(APPLICATION, "cbor") } val APPLICATION_JSON: MediaType by lazy { MediaType(APPLICATION, "json") } diff --git a/core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt b/core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt index 673e27728e..7b7af40e2b 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt @@ -2,8 +2,8 @@ package com.hexagonkt.core.text val CAMEL_CASE: Regex by lazy { Regex("[a-z]+([A-Z][a-z0-9]+)+") } val PASCAL_CASE: Regex by lazy { Regex("([A-Z][a-z0-9]+)+") } -val SNAKE_CASE: Regex by lazy { Regex("[A-Za-z]+(_[A-Za-z0-9]+)+") } -val KEBAB_CASE: Regex by lazy { Regex("[A-Za-z]+(-[A-Za-z0-9]+)+") } +val SNAKE_CASE: Regex by lazy { Regex("[_A-Za-z]+[_A-Za-z0-9]*") } +val KEBAB_CASE: Regex by lazy { Regex("[\\-A-Za-z]+[\\-A-Za-z0-9]*") } fun String.camelToWords(): List = split("(?=\\p{Upper}\\p{Lower})".toRegex()).toWords() diff --git a/core/src/test/kotlin/com/hexagonkt/core/ChecksTest.kt b/core/src/test/kotlin/com/hexagonkt/core/ChecksTest.kt index 894f6f5bbd..5f75d54579 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/ChecksTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/ChecksTest.kt @@ -141,4 +141,26 @@ internal class ChecksTest { val e = assertFailsWith(block = block) assertEquals(message, e.message) } + + @Test fun `Check multiple errors`() { + val e = assertFailsWith { + check( + "Test multiple exceptions", + { require(false) { "Sample error" } }, + { println("Good block") }, + { error("Bad state") }, + ) + } + + assertEquals("Test multiple exceptions", e.message) + assertEquals(2, e.causes.size) + assertEquals("Sample error", e.causes[0].message) + assertEquals("Bad state", e.causes[1].message) + + check( + "No exception thrown", + { println("Good block") }, + { println("Shouldn't throw an exception") }, + ) + } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/ExceptionsTest.kt b/core/src/test/kotlin/com/hexagonkt/core/ExceptionsTest.kt index bf8d445824..a42d717d59 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/ExceptionsTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/ExceptionsTest.kt @@ -58,26 +58,4 @@ internal class ExceptionsTest { assert(filteredTrace.contains("\tat ${ExceptionsTest::class.java.name}")) assertFalse(filteredTrace.contains("\tat org.junit.platform")) } - - @Test fun `Check multiple errors`() { - val e = assertFailsWith { - check( - "Test multiple exceptions", - { require(false) { "Sample error" } }, - { println("Good block") }, - { error("Bad state") }, - ) - } - - assertEquals("Test multiple exceptions", e.message) - assertEquals(2, e.causes.size) - assertEquals("Sample error", e.causes[0].message) - assertEquals("Bad state", e.causes[1].message) - - check( - "No exception thrown", - { println("Good block") }, - { println("Shouldn't throw an exception") }, - ) - } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt index 42cdae7717..8c03346695 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt @@ -41,7 +41,7 @@ internal class JvmTest { 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) + assertEquals("Property name must match [_A-Za-z]+[_A-Za-z0-9]* (1)", e.message) } @Test fun `OS kind is fetched properly`() { @@ -213,6 +213,7 @@ internal class JvmTest { assert(Jvm.systemSetting("system_property") == "value") assert(Jvm.systemSetting("PATH").isNotEmpty()) + assert(Jvm.systemSetting("path").isNotEmpty()) assertNull(Jvm.systemSettingOrNull("_not_defined_")) System.setProperty("PATH", "path override") diff --git a/core/src/test/kotlin/com/hexagonkt/core/media/MediaTypeTest.kt b/core/src/test/kotlin/com/hexagonkt/core/media/MediaTypeTest.kt index 068ff4c886..17a5ed6510 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/media/MediaTypeTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/media/MediaTypeTest.kt @@ -19,6 +19,8 @@ internal class MediaTypeTest { } @Test fun `Media types without extensions are correct`() { + assertEquals("*/*", ANY_MEDIA.fullType) + assertEquals("multipart/alternative", MULTIPART_ALTERNATIVE.fullType) assertEquals("multipart/appledouble", MULTIPART_APPLEDOUBLE.fullType) assertEquals("multipart/digest", MULTIPART_DIGEST.fullType) diff --git a/gradle.properties b/gradle.properties index 8e5601a4fc..451842e10f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,34 +33,35 @@ iconsDirectory=content # VERSIONS kotlinVersion=1.9.22 dokkaVersion=1.9.10 -mockkVersion=1.13.8 +mockkVersion=1.13.9 junitVersion=5.10.1 -gatlingVersion=3.10.2 +gatlingVersion=3.10.3 jmhVersion=1.37 -mkdocsMaterialVersion=9.5.2 +mkdocsMaterialVersion=9.5.5 mermaidDokkaVersion=0.4.4 nativeToolsVersion=0.9.28 # http_server_netty -nettyVersion=4.1.104.Final +nettyVersion=4.1.106.Final nettyTcNativeVersion=2.0.62.Final # http_server_helidon -helidonVersion=4.0.2 +helidonVersion=4.0.3 # http_server_servlet servletVersion=6.0.0 jettyVersion=12.0.5 # rest_tools -swaggerRequestValidatorVersion=2.39.0 +swaggerRequestValidatorVersion=2.40.0 +vertxVersion=4.5.1 # logging -slf4jVersion=2.0.9 +slf4jVersion=2.0.11 logbackVersion=1.4.14 # serialization -jacksonVersion=2.16.0 +jacksonVersion=2.16.1 dslJsonVersion=2.0.2 # templates_freemarker diff --git a/handlers/src/test/kotlin/com/hexagonkt/handlers/ExceptionHandlerTest.kt b/handlers/src/test/kotlin/com/hexagonkt/handlers/ExceptionHandlerTest.kt index e91eebd254..7738a93ea4 100644 --- a/handlers/src/test/kotlin/com/hexagonkt/handlers/ExceptionHandlerTest.kt +++ b/handlers/src/test/kotlin/com/hexagonkt/handlers/ExceptionHandlerTest.kt @@ -49,7 +49,7 @@ internal class ExceptionHandlerTest { ChainHandler( ExceptionHandler(Exception::class, false) { c, _ -> c.with("ok") }, - ExceptionHandler(Exception::class, false) { c, _ -> error("Fail") }, + ExceptionHandler(Exception::class, false) { _, _ -> error("Fail") }, OnHandler { error("Error") } ) .process(EventContext("test", { true })) diff --git a/http/http/api/http.api b/http/http/api/http.api index 4400dc7aa7..75e73f26c0 100644 --- a/http/http/api/http.api +++ b/http/http/api/http.api @@ -1,4 +1,6 @@ public final class com/hexagonkt/http/HttpKt { + public static final fun basicAuth (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun basicAuth$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; public static final fun checkHeaders (Lcom/hexagonkt/http/model/Headers;)V public static final fun formatQueryString (Lcom/hexagonkt/http/model/QueryParameters;)Ljava/lang/String; public static final fun getCHECKED_HEADERS ()Ljava/util/List; diff --git a/http/http/src/main/kotlin/com/hexagonkt/http/Http.kt b/http/http/src/main/kotlin/com/hexagonkt/http/Http.kt index 1cc564049f..1b7c98db46 100644 --- a/http/http/src/main/kotlin/com/hexagonkt/http/Http.kt +++ b/http/http/src/main/kotlin/com/hexagonkt/http/Http.kt @@ -4,6 +4,7 @@ import com.hexagonkt.core.GMT_ZONE import com.hexagonkt.core.assertEnabled import com.hexagonkt.core.Jvm import com.hexagonkt.core.media.MediaType +import com.hexagonkt.core.text.encodeToBase64 import com.hexagonkt.http.model.* import java.net.URLDecoder import java.net.URLEncoder @@ -20,6 +21,9 @@ val CHECKED_HEADERS: List by lazy { internal val HTTP_DATE_FORMATTER: DateTimeFormatter by lazy { RFC_1123_DATE_TIME.withZone(UTC) } +fun basicAuth(user: String, password: String = ""): String = + "$user:$password".encodeToBase64() + fun checkHeaders(headers: Headers) { if (!assertEnabled) return diff --git a/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt b/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt index 1883f8c64b..2272ca36e6 100644 --- a/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt +++ b/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt @@ -57,12 +57,21 @@ class HttpClient( error("HTTP client *MUST BE STARTED* before sending requests") else rootHandler - ?.process(request, attributes) + ?.process(request.setUp(), attributes) ?.let { if (it.exception != null) throw it.exception as Exception else it.response } - ?: adapter.send(request) + ?: adapter.send(request.setUp()) + + private fun HttpRequest.setUp(): HttpRequest { + return copy( + contentType = contentType ?: settings.contentType, + accept = accept.ifEmpty(settings::accept), + headers = settings.headers + headers, + authorization = authorization ?: settings.authorization, + ) + } fun sse(request: HttpRequest): Publisher = if (!started()) error("HTTP client *MUST BE STARTED* before sending requests") diff --git a/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt b/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt index d5a4bfb10a..a9ec50296e 100644 --- a/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt +++ b/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt @@ -111,7 +111,7 @@ internal class HttpClientTest { csvClient.request { get("/a").checkClient("/a", contentType = csv) - head("/a").checkClient("/a") + head("/a").checkClient("/a", contentType = csv) post("/a").checkClient("/a", contentType = csv) put("/a").checkClient("/a", contentType = csv) delete("/a").checkClient("/a", contentType = csv) @@ -120,7 +120,7 @@ internal class HttpClientTest { patch("/a").checkClient("/a", contentType = csv) get("/a", headers).checkClient("/a", headers = headers, contentType = csv) - head("/a", headers).checkClient("/a", headers = headers) + head("/a", headers).checkClient("/a", headers = headers, contentType = csv) get("/a", body = body).checkClient("/a", body, contentType = csv) options("/a", body).checkClient("/a", body, contentType = csv) post("/a", body).checkClient("/a", body, contentType = csv) 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 6a776f44d3..f82ccb0018 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 @@ -48,21 +48,24 @@ open class JettyClientAdapter : HttpClientPort { protected lateinit var jettyClient: JettyHttpClient protected lateinit var httpClient: HttpClient + private lateinit var httpSettings: HttpClientSettings private var started: Boolean = false private val publisherExecutor = Executors.newSingleThreadExecutor() override fun startUp(client: HttpClient) { val clientConnector = ClientConnector() - clientConnector.sslContextFactory = sslContext(client.settings) + val settings = client.settings + clientConnector.sslContextFactory = sslContext(settings) val http2 = HTTP2(JettyHttp2Client(clientConnector)) val transport = HttpClientTransportDynamic(clientConnector, HTTP11, http2) jettyClient = JettyHttpClient(transport) httpClient = client + httpSettings = settings jettyClient.userAgentField = null // Disable default user agent header - jettyClient.isFollowRedirects = client.settings.followRedirects + jettyClient.isFollowRedirects = settings.followRedirects jettyClient.start() started = true } @@ -78,7 +81,7 @@ open class JettyClientAdapter : HttpClientPort { override fun send(request: HttpRequestPort): HttpResponsePort { val response = try { - createJettyRequest(httpClient, jettyClient, request).send() + createJettyRequest(jettyClient, request).send() } catch (e: ExecutionException) { val cause = e.cause @@ -109,7 +112,7 @@ open class JettyClientAdapter : HttpClientPort { headers = request.headers + Header("connection", "keep-alive") ) - createJettyRequest(httpClient, jettyClient, sseRequest) + createJettyRequest(jettyClient, sseRequest) .onResponseBegin { if (it.status !in 200 until 300) error("Invalid response: ${it.status}") @@ -140,9 +143,8 @@ open class JettyClientAdapter : HttpClientPort { ): HttpResponse { val bodyString = if (response is ContentResponse) response.contentAsString else "" - val settings = adapterHttpClient.settings - if (settings.useCookies) + if (httpSettings.useCookies) adapterHttpClient.cookies = adapterJettyClient.httpCookieStore.all().map { Cookie( it.name, @@ -177,18 +179,14 @@ open class JettyClientAdapter : HttpClientPort { ) private fun createJettyRequest( - adapterHttpClient: HttpClient, - adapterJettyClient: JettyHttpClient, - request: HttpRequestPort + adapterJettyClient: JettyHttpClient, request: HttpRequestPort ): Request { - val settings = adapterHttpClient.settings - val contentType = request.contentType ?: settings.contentType - val accept = request.accept.ifEmpty(settings::accept) - val authorization = request.authorization ?: settings.authorization - val baseUrl = settings.baseUrl + val contentType = request.contentType + val authorization = request.authorization + val baseUrl = httpSettings.baseUrl - if (settings.useCookies) { + if (httpSettings.useCookies) { val uri = (baseUrl ?: request.url()).toURI() addCookies(uri, adapterJettyClient.httpCookieStore, request.cookies) } @@ -202,11 +200,10 @@ open class JettyClientAdapter : HttpClientPort { it.put("content-type", contentType.text) if (authorization != null) it.put("authorization", authorization.text) - (settings.headers + request.headers).values - .forEach { (k, v) -> it.put(k, v.map(Any::toString)) } + request.headers.values.forEach { (k, v) -> it.put(k, v.map(Any::toString)) } } .body(createBody(request)) - .accept(*accept.map { it.text }.toTypedArray()) + .accept(*request.accept.map { it.text }.toTypedArray()) request.queryParameters .forEach { (k, v) -> v.strings().forEach { jettyRequest.param(k, it) } } diff --git a/http/http_handlers/api/http_handlers.api b/http/http_handlers/api/http_handlers.api index 1b36b35746..6eafd8212e 100644 --- a/http/http_handlers/api/http_handlers.api +++ b/http/http_handlers/api/http_handlers.api @@ -193,6 +193,9 @@ public final class com/hexagonkt/http/handlers/HandlersKt { public static synthetic fun process$default (Lkotlin/jvm/functions/Function1;Lcom/hexagonkt/http/model/HttpRequest;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; } +public abstract interface class com/hexagonkt/http/handlers/HttpCallback : kotlin/jvm/functions/Function1 { +} + public final class com/hexagonkt/http/handlers/HttpContext : com/hexagonkt/handlers/Context { public fun (Lcom/hexagonkt/handlers/Context;)V public fun (Lcom/hexagonkt/http/model/HttpCall;Lkotlin/jvm/functions/Function1;Ljava/util/List;ILjava/lang/Exception;Ljava/util/Map;Z)V @@ -259,8 +262,8 @@ public final class com/hexagonkt/http/handlers/HttpContext : com/hexagonkt/handl public static synthetic fun notFound$default (Lcom/hexagonkt/http/handlers/HttpContext;Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; public final fun ok (Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/Map;)Lcom/hexagonkt/http/handlers/HttpContext; public static synthetic fun ok$default (Lcom/hexagonkt/http/handlers/HttpContext;Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; - public final fun receive (Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/Map;)Lcom/hexagonkt/http/handlers/HttpContext; - public static synthetic fun receive$default (Lcom/hexagonkt/http/handlers/HttpContext;Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; + public final fun receive (Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/List;Ljava/util/Map;)Lcom/hexagonkt/http/handlers/HttpContext; + public static synthetic fun receive$default (Lcom/hexagonkt/http/handlers/HttpContext;Ljava/lang/Object;Lcom/hexagonkt/http/model/Headers;Lcom/hexagonkt/http/model/ContentType;Ljava/util/List;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; public final fun redirect (Lcom/hexagonkt/http/model/HttpStatus;Ljava/lang/String;Lcom/hexagonkt/http/model/Headers;Ljava/util/List;Ljava/util/Map;)Lcom/hexagonkt/http/handlers/HttpContext; public static synthetic fun redirect$default (Lcom/hexagonkt/http/handlers/HttpContext;Lcom/hexagonkt/http/model/HttpStatus;Ljava/lang/String;Lcom/hexagonkt/http/model/Headers;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/HttpContext; public final fun send (Lcom/hexagonkt/http/model/HttpRequestPort;Ljava/util/Map;)Lcom/hexagonkt/http/handlers/HttpContext; @@ -279,6 +282,29 @@ public final class com/hexagonkt/http/handlers/HttpContext : com/hexagonkt/handl public synthetic fun with (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Ljava/util/List;ILjava/lang/Exception;Ljava/util/Map;Z)Lcom/hexagonkt/handlers/Context; } +public abstract interface class com/hexagonkt/http/handlers/HttpController : com/hexagonkt/http/handlers/HttpHandler { + public abstract fun addPrefix (Ljava/lang/String;)Lcom/hexagonkt/http/handlers/HttpHandler; + public abstract fun getCallback ()Lkotlin/jvm/functions/Function1; + public abstract fun getHandler ()Lcom/hexagonkt/http/handlers/HttpHandler; + public abstract fun getHandlerPredicate ()Lcom/hexagonkt/http/handlers/HttpPredicate; + public abstract fun getPredicate ()Lkotlin/jvm/functions/Function1; + public abstract fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; +} + +public final class com/hexagonkt/http/handlers/HttpController$DefaultImpls { + public static fun addPrefix (Lcom/hexagonkt/http/handlers/HttpController;Ljava/lang/String;)Lcom/hexagonkt/http/handlers/HttpHandler; + public static fun byMethod (Lcom/hexagonkt/http/handlers/HttpController;)Ljava/util/Map; + public static fun filter (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/http/model/HttpMethod;)Lcom/hexagonkt/http/handlers/HttpHandler; + public static fun getCallback (Lcom/hexagonkt/http/handlers/HttpController;)Lkotlin/jvm/functions/Function1; + public static fun getHandlerPredicate (Lcom/hexagonkt/http/handlers/HttpController;)Lcom/hexagonkt/http/handlers/HttpPredicate; + public static fun getPredicate (Lcom/hexagonkt/http/handlers/HttpController;)Lkotlin/jvm/functions/Function1; + public static fun process (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; + public static fun process (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; +} + +public abstract interface class com/hexagonkt/http/handlers/HttpExceptionCallback : kotlin/jvm/functions/Function2 { +} + public abstract interface class com/hexagonkt/http/handlers/HttpHandler : com/hexagonkt/handlers/Handler { public abstract fun addPrefix (Ljava/lang/String;)Lcom/hexagonkt/http/handlers/HttpHandler; public abstract fun byMethod ()Ljava/util/Map; diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/AfterHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/AfterHandler.kt index e59de2bd80..35a6ea0e17 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/AfterHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/AfterHandler.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass data class AfterHandler( override val handlerPredicate: HttpPredicate = HttpPredicate(), - val block: HttpCallback + val block: HttpCallbackType ) : HttpHandler, Handler by AfterHandler(handlerPredicate, toCallback(block)) { constructor( @@ -17,14 +17,14 @@ data class AfterHandler( pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - block: HttpCallback, + block: HttpCallbackType, ) : this(HttpPredicate(methods, pattern, exception, status), block) - constructor(method: HttpMethod, pattern: String = "", block: HttpCallback) : + constructor(method: HttpMethod, pattern: String = "", block: HttpCallbackType) : this(setOf(method), pattern, block = block) - constructor(pattern: String, block: HttpCallback) : + constructor(pattern: String, block: HttpCallbackType) : this(emptySet(), pattern, block = block) override fun addPrefix(prefix: String): HttpHandler = diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/BeforeHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/BeforeHandler.kt index 4261a919a8..5f61157291 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/BeforeHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/BeforeHandler.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass data class BeforeHandler( override val handlerPredicate: HttpPredicate = HttpPredicate(), - val block: HttpCallback, + val block: HttpCallbackType, ) : HttpHandler, Handler by BeforeHandler(handlerPredicate, toCallback(block)) { constructor( @@ -17,14 +17,14 @@ data class BeforeHandler( pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - block: HttpCallback, + block: HttpCallbackType, ) : this(HttpPredicate(methods, pattern, exception, status), block) - constructor(method: HttpMethod, pattern: String = "", block: HttpCallback) : + constructor(method: HttpMethod, pattern: String = "", block: HttpCallbackType) : this(setOf(method), pattern, block = block) - constructor(pattern: String, block: HttpCallback) : + constructor(pattern: String, block: HttpCallbackType) : this(emptySet(), pattern, block = block) override fun addPrefix(prefix: String): HttpHandler = diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/ExceptionHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/ExceptionHandler.kt index 2c123747e4..4acd949a8a 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/ExceptionHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/ExceptionHandler.kt @@ -8,7 +8,7 @@ import kotlin.reflect.KClass data class ExceptionHandler( val exception: KClass, val clear: Boolean = true, - val block: HttpExceptionCallback + val block: HttpExceptionCallbackType ) : HttpHandler, Handler by ExceptionHandler(exception, clear, toCallback(block)) { override val handlerPredicate: HttpPredicate = HttpPredicate() diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/FilterHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/FilterHandler.kt index c26f2bebfd..7c5feeff5d 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/FilterHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/FilterHandler.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass data class FilterHandler( override val handlerPredicate: HttpPredicate = HttpPredicate(), - val block: HttpCallback + val block: HttpCallbackType ) : HttpHandler, Handler by FilterHandler(handlerPredicate, toCallback(block)) { constructor( @@ -17,14 +17,14 @@ data class FilterHandler( pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - block: HttpCallback, + block: HttpCallbackType, ) : this(HttpPredicate(methods, pattern, exception, status), block) - constructor(method: HttpMethod, pattern: String = "", block: HttpCallback) : + constructor(method: HttpMethod, pattern: String = "", block: HttpCallbackType) : this(setOf(method), pattern, block = block) - constructor(pattern: String, block: HttpCallback) : + constructor(pattern: String, block: HttpCallbackType) : this(emptySet(), pattern, block = block) override fun addPrefix(prefix: String): HttpHandler = diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HandlerBuilder.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HandlerBuilder.kt index ee5b5f67b6..cd2d9a14ed 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HandlerBuilder.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HandlerBuilder.kt @@ -32,7 +32,7 @@ class HandlerBuilder(var handlers: List = emptyList()) { fun on( predicate: HttpPredicate = HttpPredicate(), - callback: HttpCallback + callback: HttpCallbackType ) { use(OnHandler(predicate, callback)) } @@ -42,22 +42,22 @@ class HandlerBuilder(var handlers: List = emptyList()) { pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - callback: HttpCallback, + callback: HttpCallbackType, ) { use(OnHandler(methods, pattern, exception, status, callback)) } - fun on(method: HttpMethod, pattern: String = "", callback: HttpCallback) { + fun on(method: HttpMethod, pattern: String = "", callback: HttpCallbackType) { use(OnHandler(method, pattern, callback)) } - fun on(pattern: String, callback: HttpCallback) { + fun on(pattern: String, callback: HttpCallbackType) { use(OnHandler(pattern, callback)) } fun filter( predicate: HttpPredicate = HttpPredicate(), - callback: HttpCallback + callback: HttpCallbackType ) { use(FilterHandler(predicate, callback)) } @@ -67,24 +67,24 @@ class HandlerBuilder(var handlers: List = emptyList()) { pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - callback: HttpCallback, + callback: HttpCallbackType, ) { use( FilterHandler(methods, pattern, exception, status, callback) ) } - fun filter(method: HttpMethod, pattern: String = "", callback: HttpCallback) { + fun filter(method: HttpMethod, pattern: String = "", callback: HttpCallbackType) { use(FilterHandler(method, pattern, callback)) } - fun filter(pattern: String, callback: HttpCallback) { + fun filter(pattern: String, callback: HttpCallbackType) { use(FilterHandler(pattern, callback)) } fun after( predicate: HttpPredicate = HttpPredicate(), - callback: HttpCallback + callback: HttpCallbackType ) { use(AfterHandler(predicate, callback)) } @@ -94,22 +94,22 @@ class HandlerBuilder(var handlers: List = emptyList()) { pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - callback: HttpCallback, + callback: HttpCallbackType, ) { use(AfterHandler(methods, pattern, exception, status, callback)) } - fun after(method: HttpMethod, pattern: String = "", callback: HttpCallback) { + fun after(method: HttpMethod, pattern: String = "", callback: HttpCallbackType) { use(AfterHandler(method, pattern, callback)) } - fun after(pattern: String, callback: HttpCallback) { + fun after(pattern: String, callback: HttpCallbackType) { use(AfterHandler(pattern, callback)) } fun before( predicate: HttpPredicate = HttpPredicate(), - callback: HttpCallback + callback: HttpCallbackType ) { use(BeforeHandler(predicate, callback)) } @@ -119,64 +119,64 @@ class HandlerBuilder(var handlers: List = emptyList()) { pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - callback: HttpCallback, + callback: HttpCallbackType, ) { use(BeforeHandler(methods, pattern, exception, status, callback)) } - fun before(method: HttpMethod, pattern: String = "", callback: HttpCallback) { + fun before(method: HttpMethod, pattern: String = "", callback: HttpCallbackType) { use(BeforeHandler(method, pattern, callback)) } - fun before(pattern: String, callback: HttpCallback) { + fun before(pattern: String, callback: HttpCallbackType) { use(BeforeHandler(pattern, callback)) } fun exception( - exception: KClass, clear: Boolean = true, callback: HttpExceptionCallback + exception: KClass, clear: Boolean = true, callback: HttpExceptionCallbackType ) { use(ExceptionHandler(exception, clear, callback)) } inline fun exception( - clear: Boolean = true, noinline callback: HttpExceptionCallback, + clear: Boolean = true, noinline callback: HttpExceptionCallbackType, ) { use(ExceptionHandler(T::class, clear, callback)) } - fun get(pattern: String = "", callback: HttpCallback) { + fun get(pattern: String = "", callback: HttpCallbackType) { use(Get(pattern, callback)) } - fun ws(pattern: String = "", callback: HttpCallback) { + fun ws(pattern: String = "", callback: HttpCallbackType) { use(Ws(pattern, callback)) } - fun head(pattern: String = "", callback: HttpCallback) { + fun head(pattern: String = "", callback: HttpCallbackType) { use(Head(pattern, callback)) } - fun post(pattern: String = "", callback: HttpCallback) { + fun post(pattern: String = "", callback: HttpCallbackType) { use(Post(pattern, callback)) } - fun put(pattern: String = "", callback: HttpCallback) { + fun put(pattern: String = "", callback: HttpCallbackType) { use(Put(pattern, callback)) } - fun delete(pattern: String = "", callback: HttpCallback) { + fun delete(pattern: String = "", callback: HttpCallbackType) { use(Delete(pattern, callback)) } - fun trace(pattern: String = "", callback: HttpCallback) { + fun trace(pattern: String = "", callback: HttpCallbackType) { use(Trace(pattern, callback)) } - fun options(pattern: String = "", callback: HttpCallback) { + fun options(pattern: String = "", callback: HttpCallbackType) { use(Options(pattern, callback)) } - fun patch(pattern: String = "", callback: HttpCallback) { + fun patch(pattern: String = "", callback: HttpCallbackType) { use(Patch(pattern, callback)) } } diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/Handlers.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/Handlers.kt index 5bdd33f724..38c326ee97 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/Handlers.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/Handlers.kt @@ -7,14 +7,11 @@ import com.hexagonkt.handlers.Context import com.hexagonkt.http.model.* import com.hexagonkt.http.model.HttpMethod.* import com.hexagonkt.http.model.HttpProtocol.HTTP -import com.hexagonkt.http.model.HttpCall -import com.hexagonkt.http.model.HttpRequest -import java.lang.IllegalStateException import java.math.BigInteger import java.security.cert.X509Certificate -typealias HttpCallback = HttpContext.() -> HttpContext -typealias HttpExceptionCallback = HttpContext.(T) -> HttpContext +typealias HttpCallbackType = HttpContext.() -> HttpContext +typealias HttpExceptionCallbackType = HttpContext.(T) -> HttpContext private val logger: Logger by lazy { Logger(HttpHandler::class.java.packageName) } private val BODY_TYPES_NAMES: String by lazy { @@ -22,21 +19,21 @@ private val BODY_TYPES_NAMES: String by lazy { bodyTypes.joinToString(", ") { it.simpleName.toString() } } -internal fun toCallback(block: HttpCallback): (Context) -> Context = +internal fun toCallback(block: HttpCallbackType): (Context) -> Context = { context -> HttpContext(context).block() } internal fun toCallback( - block: HttpExceptionCallback + block: HttpExceptionCallbackType ): (Context, E) -> Context = { context, e -> HttpContext(context).block(e) } -fun HttpCallback.process( +fun HttpCallbackType.process( request: HttpRequest, attributes: Map<*, *> = emptyMap() ): HttpContext = this(HttpContext(request = request, attributes = attributes)) -fun HttpCallback.process( +fun HttpCallbackType.process( method: HttpMethod = GET, protocol: HttpProtocol = HTTP, host: String = "localhost", @@ -90,31 +87,31 @@ fun path(contextPath: String = "", handlers: List): PathHandler = PathHandler(contextPath, it) } -fun Get(pattern: String = "", callback: HttpCallback): OnHandler = +fun Get(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(GET, pattern, callback) -fun Ws(pattern: String = "", callback: HttpCallback): OnHandler = +fun Ws(pattern: String = "", callback: HttpCallbackType): OnHandler = Get(pattern, callback) -fun Head(pattern: String = "", callback: HttpCallback): OnHandler = +fun Head(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(HEAD, pattern, callback) -fun Post(pattern: String = "", callback: HttpCallback): OnHandler = +fun Post(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(POST, pattern, callback) -fun Put(pattern: String = "", callback: HttpCallback): OnHandler = +fun Put(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(PUT, pattern, callback) -fun Delete(pattern: String = "", callback: HttpCallback): OnHandler = +fun Delete(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(DELETE, pattern, callback) -fun Trace(pattern: String = "", callback: HttpCallback): OnHandler = +fun Trace(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(TRACE, pattern, callback) -fun Options(pattern: String = "", callback: HttpCallback): OnHandler = +fun Options(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(OPTIONS, pattern, callback) -fun Patch(pattern: String = "", callback: HttpCallback): OnHandler = +fun Patch(pattern: String = "", callback: HttpCallbackType): OnHandler = OnHandler(PATCH, pattern, callback) fun bodyToBytes(body: Any): ByteArray = diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpCallback.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpCallback.kt new file mode 100644 index 0000000000..86f1203a78 --- /dev/null +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpCallback.kt @@ -0,0 +1,3 @@ +package com.hexagonkt.http.handlers + +interface HttpCallback : (HttpContext) -> HttpContext diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpContext.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpContext.kt index 68b3faec93..76dabeb0e2 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpContext.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpContext.kt @@ -275,10 +275,11 @@ data class HttpContext( ) fun receive( - body: Any = response.body, - headers: Headers = response.headers, - contentType: ContentType? = response.contentType, - cookies: List = response.cookies, + body: Any = request.body, + headers: Headers = request.headers, + contentType: ContentType? = request.contentType, + accept: List = request.accept, + cookies: List = request.cookies, attributes: Map<*, *> = this.attributes, ): HttpContext = send( @@ -286,6 +287,7 @@ data class HttpContext( body = body, headers = headers, contentType = contentType, + accept = accept, cookies = cookies, ), attributes diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpController.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpController.kt new file mode 100644 index 0000000000..739e813053 --- /dev/null +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpController.kt @@ -0,0 +1,26 @@ +package com.hexagonkt.http.handlers + +import com.hexagonkt.handlers.Context +import com.hexagonkt.http.model.HttpCall + +/** + * Utility to encapsulate a handler in a class. TODO + */ +interface HttpController : HttpHandler { + val handler: HttpHandler + + override val handlerPredicate: HttpPredicate + get() = handler.handlerPredicate + + override fun addPrefix(prefix: String): HttpHandler = + handler.addPrefix(prefix) + + override fun process(context: Context): Context = + handler.process(context) + + override val predicate: (Context) -> Boolean + get() = handler.predicate + + override val callback: (Context) -> Context + get() = handler.callback +} diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpExceptionCallback.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpExceptionCallback.kt new file mode 100644 index 0000000000..cd8deba07d --- /dev/null +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpExceptionCallback.kt @@ -0,0 +1,4 @@ +package com.hexagonkt.http.handlers + +// TODO Use this type to implement the RFC 7807 exception handler +interface HttpExceptionCallback : (HttpContext, T) -> HttpContext diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt index b9fafda377..8f7dffd0a2 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt @@ -37,7 +37,7 @@ sealed interface HttpHandler : Handler { is BeforeHandler -> copy(handlerPredicate = handlerPredicate.clearMethods()) - is ExceptionHandler<*> -> + else -> this } diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/OnHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/OnHandler.kt index 0ef081b185..1a91d72476 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/OnHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/OnHandler.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass data class OnHandler( override val handlerPredicate: HttpPredicate = HttpPredicate(), - val block: HttpCallback, + val block: HttpCallbackType, ) : HttpHandler, Handler by OnHandler(handlerPredicate, toCallback(block)) { constructor( @@ -17,14 +17,14 @@ data class OnHandler( pattern: String = "", exception: KClass? = null, status: HttpStatus? = null, - block: HttpCallback, + block: HttpCallbackType, ) : this(HttpPredicate(methods, pattern, exception, status), block) - constructor(method: HttpMethod, pattern: String = "", block: HttpCallback) : + constructor(method: HttpMethod, pattern: String = "", block: HttpCallbackType) : this(setOf(method), pattern, block = block) - constructor(pattern: String, block: HttpCallback) : + constructor(pattern: String, block: HttpCallbackType) : this(emptySet(), pattern, block = block) override fun addPrefix(prefix: String): HttpHandler = diff --git a/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/Examples.kt b/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/Examples.kt index 0223ed4617..e8d4516f6e 100644 --- a/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/Examples.kt +++ b/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/Examples.kt @@ -1,6 +1,6 @@ package com.hexagonkt.http.handlers -import com.hexagonkt.core.text.encodeToBase64 +import com.hexagonkt.http.basicAuth import com.hexagonkt.http.model.* import com.hexagonkt.http.parseQueryString import kotlin.test.assertEquals @@ -38,7 +38,7 @@ internal fun HttpRequest.auth( val authorization = if (user != null || password != null) - "$user:$password".encodeToBase64() + basicAuth(user ?: "", password ?: "") else null diff --git a/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/HttpControllerTest.kt b/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/HttpControllerTest.kt new file mode 100644 index 0000000000..e343d3d445 --- /dev/null +++ b/http/http_handlers/src/test/kotlin/com/hexagonkt/http/handlers/HttpControllerTest.kt @@ -0,0 +1,22 @@ +package com.hexagonkt.http.handlers + +import com.hexagonkt.http.model.HttpRequest +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class HttpControllerTest { + + class TestController : HttpController { + override val handler: HttpHandler = Get { ok("Good") } + } + + @Test fun `A controller can be used as a handler`() { + + val path = path { + use(TestController()) + } + + val response = path.process(HttpRequest()) + assertEquals("Good", response.response.bodyString()) + } +} 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 884e7af934..2347d38343 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,6 +1,5 @@ package com.hexagonkt.http.test -import com.hexagonkt.core.text.encodeToBase64 import com.hexagonkt.core.logging.LoggingLevel.DEBUG import com.hexagonkt.core.logging.LoggingLevel.OFF import com.hexagonkt.core.logging.LoggingManager @@ -64,10 +63,6 @@ abstract class BaseTest { assertResponseContains(response, OK_200, *content) } - // TODO Move to `http` module to share basic and digest auth among client and server - protected fun basicAuth(user: String, password: String? = null): String = - "Basic " + "$user:$password".encodeToBase64() - protected fun assertResponseEquals( response: HttpResponsePort?, status: HttpStatus = OK_200, content: String ) { diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt index d4b0714324..4ca3df6ba5 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt @@ -17,7 +17,7 @@ import com.hexagonkt.http.model.HttpProtocol.HTTPS import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500 import com.hexagonkt.http.model.OK_200 import com.hexagonkt.http.server.* -import com.hexagonkt.http.handlers.HttpCallback +import com.hexagonkt.http.handlers.HttpCallbackType import com.hexagonkt.http.handlers.HttpHandler import com.hexagonkt.http.handlers.path import com.hexagonkt.http.test.BaseTest @@ -42,7 +42,7 @@ abstract class ClientTest( final override val serverSettings: HttpServerSettings = HttpServerSettings(), ) : BaseTest() { - private var callback: HttpCallback = { this } + private var callback: HttpCallbackType = { this } override val handler: HttpHandler = path { post("*") { callback() } diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FiltersTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FiltersTest.kt index eda6e4b79b..bd9fa4367d 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FiltersTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FiltersTest.kt @@ -1,13 +1,13 @@ package com.hexagonkt.http.test.examples import com.hexagonkt.core.text.decodeBase64 +import com.hexagonkt.http.basicAuth import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientPort import com.hexagonkt.http.client.HttpClientSettings import com.hexagonkt.http.model.FORBIDDEN_403 import com.hexagonkt.http.model.UNAUTHORIZED_401 import com.hexagonkt.http.model.Header -import com.hexagonkt.http.model.Headers import com.hexagonkt.http.model.HttpMethod.PUT import com.hexagonkt.http.model.* import com.hexagonkt.http.server.HttpServerPort @@ -132,7 +132,7 @@ abstract class FiltersTest( private fun authorizedClient(user: String, password: String): HttpClient { val settings = HttpClientSettings( baseUrl = server.binding, - headers = Headers(Header("authorization", basicAuth(user, password))) + authorization = Authorization("basic", basicAuth(user, password)) ) return HttpClient(clientAdapter(), settings).apply { start() } } 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 0c80eaeb6d..9d7d90111c 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 @@ -554,7 +554,7 @@ abstract class SamplesTest( @Test fun mockRequest() { // mockRequest // Test callback (basically, a handler without a predicate) - val callback: HttpCallback = { + val callback: HttpCallbackType = { val fakeAttribute = attributes["fake"] val fakeHeader = request.headers["fake"]?.value ok("Callback result $fakeAttribute $fakeHeader") diff --git a/http/http_test/src/main/resources/assets/index.html b/http/http_test/src/main/resources/assets/index.html index 57aa9e563d..b21e834a02 100644 --- a/http/http_test/src/main/resources/assets/index.html +++ b/http/http_test/src/main/resources/assets/index.html @@ -37,8 +37,8 @@ - - + + @@ -107,7 +107,7 @@ @@ -258,7 +258,7 @@

Contribute

Community

@@ -291,7 +291,7 @@

Community

aria-label="Fork hexagonkt/hexagon on GitHub">Fork + href="https://twitter.com/share?text=Hexagon Toolkit">Post diff --git a/http/rest/src/main/kotlin/com/hexagonkt/rest/Rest.kt b/http/rest/src/main/kotlin/com/hexagonkt/rest/Rest.kt index 0dbf8e0265..41b1ff3528 100644 --- a/http/rest/src/main/kotlin/com/hexagonkt/rest/Rest.kt +++ b/http/rest/src/main/kotlin/com/hexagonkt/rest/Rest.kt @@ -1,9 +1,14 @@ package com.hexagonkt.rest +import com.hexagonkt.core.media.ANY_MEDIA import com.hexagonkt.core.media.MediaType +import com.hexagonkt.http.model.ContentType import com.hexagonkt.http.model.HttpBase import com.hexagonkt.serialization.* +val anyContentType = ContentType(ANY_MEDIA) +val emptyBodies = setOf("", ByteArray(0)) + fun HttpBase.bodyList(): List<*> = bodyString().parseList(mediaType()) diff --git a/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeRequestCallback.kt b/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeRequestCallback.kt index d4f3cfe6d5..4ca94062f6 100644 --- a/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeRequestCallback.kt +++ b/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeRequestCallback.kt @@ -1,15 +1,21 @@ package com.hexagonkt.rest +import com.hexagonkt.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext import com.hexagonkt.serialization.SerializationManager import com.hexagonkt.serialization.serialize -class SerializeRequestCallback: (HttpContext) -> HttpContext { +class SerializeRequestCallback : HttpCallback { - // TODO Short circuit if body is empty - override fun invoke(context: HttpContext): HttpContext = - context.request.contentType?.mediaType + override fun invoke(context: HttpContext): HttpContext { + val requestBody = context.request.body + + if (requestBody in emptyBodies) + return context + + return context.request.contentType?.mediaType ?.let(SerializationManager::formatOfOrNull) - ?.let { context.receive(body = context.request.body.serialize(it)) } + ?.let { context.receive(body = requestBody.serialize(it)) } ?: context + } } diff --git a/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeResponseCallback.kt b/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeResponseCallback.kt index 31d7027940..bc23fbb3b7 100644 --- a/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeResponseCallback.kt +++ b/http/rest/src/main/kotlin/com/hexagonkt/rest/SerializeResponseCallback.kt @@ -1,22 +1,25 @@ package com.hexagonkt.rest +import com.hexagonkt.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext -import com.hexagonkt.http.model.ContentType import com.hexagonkt.serialization.SerializationManager import com.hexagonkt.serialization.serialize -class SerializeResponseCallback: (HttpContext) -> HttpContext { +class SerializeResponseCallback: HttpCallback { - fun HttpContext.accept(): List = - request.accept.ifEmpty { response.contentType?.let(::listOf) ?: emptyList() } + override fun invoke(context: HttpContext): HttpContext { + val responseBody = context.response.body - // TODO Short circuit if body is empty - override fun invoke(context: HttpContext): HttpContext = - context.accept() + if (responseBody in emptyBodies) + return context + + return (context.request.accept - anyContentType) + .ifEmpty { context.response.contentType?.let(::listOf) ?: emptyList() } .associateWith { SerializationManager.formatOfOrNull(it.mediaType) } .mapNotNull { (k, v) -> v?.let { k to it } } .firstOrNull() - ?.let { (ct, sf) -> ct to context.response.body.serialize(sf) } + ?.let { (ct, sf) -> ct to responseBody.serialize(sf) } ?.let { (ct, c) -> context.send(body = c, contentType = ct) } ?: context + } } diff --git a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt new file mode 100644 index 0000000000..83eda2fda8 --- /dev/null +++ b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeRequestCallbackTest.kt @@ -0,0 +1,37 @@ +package com.hexagonkt.rest + +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.core.media.APPLICATION_YAML +import com.hexagonkt.http.handlers.HttpContext +import com.hexagonkt.http.model.ContentType +import com.hexagonkt.serialization.SerializationManager +import com.hexagonkt.serialization.jackson.json.Json +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame + +internal class SerializeRequestCallbackTest { + + private val callback = SerializeRequestCallback() + + @Test fun `Serialize empty request callback creates the proper response`() { + val stringContext = HttpContext().send(body = "") + assertSame(stringContext, callback(stringContext)) + + val binaryContext = HttpContext().send(body = ByteArray(0)) + assertSame(binaryContext, callback(binaryContext)) + } + + @Test fun `Serialize request callback creates the proper response`() { + SerializationManager.formats = setOf(Json) + val body = mapOf("key" to "value") + + val yaml = ContentType(APPLICATION_YAML) + val yamlContext = HttpContext().receive(body = body, contentType = yaml) + assertSame(yamlContext, callback(yamlContext)) + + val json = ContentType(APPLICATION_JSON) + val jsonContext = HttpContext().receive(body = body, contentType = json) + assertEquals(jsonContext.receive("{\n \"key\" : \"value\"\n}"), callback(jsonContext)) + } +} diff --git a/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt new file mode 100644 index 0000000000..47f4f98a6c --- /dev/null +++ b/http/rest/src/test/kotlin/com/hexagonkt/rest/SerializeResponseCallbackTest.kt @@ -0,0 +1,46 @@ +package com.hexagonkt.rest + +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.core.media.APPLICATION_YAML +import com.hexagonkt.http.handlers.HttpContext +import com.hexagonkt.http.model.ContentType +import com.hexagonkt.serialization.SerializationManager +import com.hexagonkt.serialization.jackson.json.Json +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame + +internal class SerializeResponseCallbackTest { + + private val callback = SerializeResponseCallback() + + @Test fun `Serialize empty response callback creates the proper response`() { + val stringContext = HttpContext().send(body = "") + assertSame(stringContext, callback(stringContext)) + + val binaryContext = HttpContext().send(body = ByteArray(0)) + assertSame(binaryContext, callback(binaryContext)) + } + + @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 yaml = ContentType(APPLICATION_YAML) + val yamlContext = HttpContext().send(body = body, contentType = yaml) + assertSame(yamlContext, callback(yamlContext)) + + val anyMedia = ContentType(APPLICATION_YAML) + val anyMediaContext = HttpContext().send(body = body, contentType = anyMedia) + assertSame(anyMediaContext, callback(anyMediaContext)) + + val json = ContentType(APPLICATION_JSON) + val jsonContext = HttpContext().send(body = body, contentType = json) + assertEquals(jsonContext.send(body = jsonBody), callback(jsonContext)) + + val acceptContext = HttpContext().send(body = body).receive(accept = listOf(json)) + val expectedContext = acceptContext.send(body = jsonBody, contentType = json) + assertEquals(expectedContext, callback(acceptContext)) + } +} diff --git a/http/rest_tools/build.gradle.kts b/http/rest_tools/build.gradle.kts index eecbd14b3a..167bda92f6 100644 --- a/http/rest_tools/build.gradle.kts +++ b/http/rest_tools/build.gradle.kts @@ -12,12 +12,21 @@ 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"] "api"(project(":http:rest")) "api"(project(":http:http_server")) "api"(project(":http:http_client")) - "api"("com.atlassian.oai:swagger-request-validator-core:$swaggerRequestValidatorVersion") + "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") + } "testImplementation"(project(":http:http_client_jetty")) "testImplementation"(project(":http:http_server_jetty")) 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/DynamicServer.kt index 98236979ba..ce3e681472 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicServer.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/DynamicServer.kt @@ -13,7 +13,7 @@ import java.net.URL * Server with dynamic handler (delegated to [path]). Root handler can be replaced at any time * without restarting the server. */ -data class DynamicServer( +data class DynamicHttpServer( private val adapter: HttpServerPort, private val settings: HttpServerSettings = HttpServerSettings(), var path: PathHandler = PathHandler(), @@ -25,7 +25,7 @@ data class DynamicServer( HttpServer(adapter, settings) { after("*", SerializeResponseCallback()) after(pattern = "*", status = NOT_FOUND_404) { - send(response = this@DynamicServer.path.process(request).response) + send(response = this@DynamicHttpServer.path.process(request).response) } } } 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/Http.kt index 1396f97986..12806a3730 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/Http.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/Http.kt @@ -15,7 +15,7 @@ import com.hexagonkt.http.model.HttpStatusType.SUCCESS import com.hexagonkt.http.patterns.createPathPattern import com.hexagonkt.rest.SerializeRequestCallback -data class Http( +data class StateHttpClient( val adapter: HttpClientPort, val url: String? = null, val httpContentType: ContentType? = null, @@ -23,6 +23,8 @@ data class Http( val httpHeaders: Map = emptyMap(), val sslSettings: SslSettings? = SslSettings(), val handler: HttpHandler? = serializeHandler, + val authorization: Authorization? = null, + val followRedirects: Boolean = false ) { companion object { val serializeHandler: HttpHandler = BeforeHandler("*", SerializeRequestCallback()) @@ -37,6 +39,8 @@ data class Http( headers = toHeaders(httpHeaders), insecure = true, sslSettings = sslSettings, + authorization = authorization, + followRedirects = followRedirects ) private val client = HttpClient(adapter, settings, handler = handler) @@ -80,8 +84,8 @@ data class Http( client.stop() } - fun request(block: Http.() -> Unit) { - client.request { block.invoke(this@Http) } + fun request(block: StateHttpClient.() -> Unit) { + client.request { block.invoke(this@StateHttpClient) } } fun assertStatus(status: HttpStatus) { @@ -100,6 +104,14 @@ data class Http( assertStatus(SUCCESS) } + fun assertContentType(contentType: ContentType) { + assert(this.contentType == contentType) + } + + fun assertContentType(mediaType: MediaType) { + assert(contentType == ContentType(mediaType)) + } + fun assertBody(body: Any) { assert(body == lastResponse.body) } @@ -123,9 +135,9 @@ data class Http( private fun send( method: HttpMethod = GET, path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, @@ -153,11 +165,10 @@ data class Http( private fun send( method: HttpMethod = GET, - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, @@ -165,38 +176,38 @@ data class Http( ): HttpResponsePort = send( method = method, - path = createPathPattern(pathPattern, false).insertParameters(pathParameters), - headers = toHeaders(headers), + path = createPathPattern(path.first, false).insertParameters(path.second), body = body, formParameters = formParameters, + headers = toHeaders(headers), parts = parts, contentType = contentType, accept = accept, attributes = attributes - + mapOf("pathPattern" to pathPattern, "pathParameters" to pathParameters), + + mapOf("pathPattern" to path.first, "pathParameters" to path.second), ) fun get( path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(GET, path, headers, body, formParameters, parts, contentType, accept) + send(GET, path, body, formParameters, headers, parts, contentType, accept) fun put( path: String = "/", body: Any = "", - headers: Map = emptyMap(), formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(PUT, path, headers, body, formParameters, parts, contentType, accept) + send(PUT, path, body, formParameters, headers, parts, contentType, accept) fun put( path: String = "/", @@ -207,171 +218,169 @@ data class Http( accept: List = settings.accept, body: () -> Any, ): HttpResponsePort = - send(PUT, path, headers, body(), formParameters, parts, contentType, accept) + put(path, body(), formParameters, headers, parts, contentType, accept) fun post( path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(POST, path, headers, body, formParameters, parts, contentType, accept) + send(POST, path, body, formParameters, headers, parts, contentType, accept) - fun options( + fun post( path: String = "/", + formParameters: List = emptyList(), headers: Map = emptyMap(), + parts: List = emptyList(), + contentType: ContentType? = settings.contentType, + accept: List = settings.accept, + body: () -> Any, + ): HttpResponsePort = + post(path, body(), formParameters, headers, parts, contentType, accept) + + fun options( + path: String = "/", body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(OPTIONS, path, headers, body, formParameters, parts, contentType, accept) + send(OPTIONS, path, body, formParameters, headers, parts, contentType, accept) fun delete( path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(DELETE, path, headers, body, formParameters, parts, contentType, accept) + send(DELETE, path, body, formParameters, headers, parts, contentType, accept) fun patch( path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(PATCH, path, headers, body, formParameters, parts, contentType, accept) + send(PATCH, path, body, formParameters, headers, parts, contentType, accept) fun trace( path: String = "/", - headers: Map = emptyMap(), body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send(TRACE, path, headers, body, formParameters, parts, contentType, accept) + send(TRACE, path, body, formParameters, headers, parts, contentType, accept) fun get( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - GET, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(GET, path, body, formParameters, headers, parts, contentType, accept) fun put( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - PUT, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(PUT, path, body, formParameters, headers, parts, contentType, accept) fun put( - pathPattern: String, - pathParameters: Map, + path: Pair>, formParameters: List = emptyList(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, body: () -> Any, ): HttpResponsePort = - send( - PUT, pathPattern, pathParameters, Headers(), body(), formParameters, parts, contentType, accept - ) + put(path, body(), formParameters, Headers(), parts, contentType, accept) fun post( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - POST, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(POST, path, body, formParameters, headers, parts, contentType, accept) - fun options( - pathPattern: String, - pathParameters: Map, + fun post( + path: Pair>, + formParameters: List = emptyList(), headers: Map = emptyMap(), + parts: List = emptyList(), + contentType: ContentType? = settings.contentType, + accept: List = settings.accept, + body: () -> Any, + ): HttpResponsePort = + post(path, body(), formParameters, headers, parts, contentType, accept) + + fun options( + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - OPTIONS, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(OPTIONS, path, body, formParameters, headers, parts, contentType, accept) fun delete( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - DELETE, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(DELETE, path, body, formParameters, headers, parts, contentType, accept) fun patch( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - PATCH, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(PATCH, path, body, formParameters, headers, parts, contentType, accept) fun trace( - pathPattern: String, - pathParameters: Map, - headers: Map = emptyMap(), + path: Pair>, body: Any = "", formParameters: List = emptyList(), + headers: Map = emptyMap(), parts: List = emptyList(), contentType: ContentType? = settings.contentType, accept: List = settings.accept, ): HttpResponsePort = - send( - TRACE, pathPattern, pathParameters, headers, body, formParameters, parts, contentType, accept - ) + send(TRACE, path, body, formParameters, headers, parts, contentType, accept) } 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 index 5207b23f43..45768a25a1 100644 --- 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 @@ -19,8 +19,6 @@ import io.swagger.v3.oas.models.security.SecurityScheme import io.swagger.v3.oas.models.security.SecurityScheme.Type import io.swagger.v3.parser.OpenAPIV3Parser -// TODO Validate bodies with vertx-json-schema -// TODO Check https://github.com/swagger-api/swagger-parser for route verification internal class OpenApiHandler(pathToSpec: String) { private val openAPIParser = OpenAPIV3Parser() @@ -48,7 +46,7 @@ internal class OpenApiHandler(pathToSpec: String) { private fun createHandler(method: HttpMethod, path: String, operation: Operation): HttpHandler = OnHandler(method, path, handleRequest(operation)) - private fun handleRequest(operation: Operation): HttpCallback = + private fun handleRequest(operation: Operation): HttpCallbackType = { verifyAuth(operation, this) ?: verifyParams(operation, this) 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 8b2c8f7401..4defd44908 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,6 +8,7 @@ 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.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext import com.hexagonkt.http.model.ContentType import com.hexagonkt.http.model.HttpMethod @@ -17,8 +18,10 @@ 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) : (HttpContext) -> HttpContext { +class VerifySpecCallback(spec: URL) : HttpCallback { private val messagePrefix: String = "\n- " private val validator: OpenApiInteractionValidator = 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 34d88089e6..fae33cf311 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 @@ -27,8 +27,8 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue @TestInstance(PER_CLASS) -class DynamicServerTest { - private val dynamicServer: DynamicServer = DynamicServer(JettyServletAdapter()) +class DynamicHttpServerTest { + private val dynamicServer: DynamicHttpServer = DynamicHttpServer(JettyServletAdapter()) @BeforeAll fun `Set up mock services`() { dynamicServer.start() @@ -47,7 +47,8 @@ class DynamicServerTest { } } - Http(JettyClientAdapter(), "http://localhost:${dynamicServer.runtimePort}").request { + val url = "http://localhost:${dynamicServer.runtimePort}" + StateHttpClient(JettyClientAdapter(), url).request { get("/hello/mike") assertEquals(OK_200, response.status) } @@ -60,7 +61,8 @@ class DynamicServerTest { } } - Http(JettyClientAdapter(), "http://localhost:${dynamicServer.runtimePort}").request { + val url = "http://localhost:${dynamicServer.runtimePort}" + StateHttpClient(JettyClientAdapter(), url).request { get("/foo") assertEquals(OK_200, response.status) assertEquals("dynamic", response.body) @@ -89,10 +91,10 @@ class DynamicServerTest { val headers = mapOf("alfa" to "beta", "charlie" to listOf("delta", "echo")) val recordCallback = RecordCallback() val recordHandler = BeforeHandler("*", recordCallback) - val http = Http( + val http = StateHttpClient( adapter, httpHeaders = headers, - handler = PathHandler(recordHandler, Http.serializeHandler) + handler = PathHandler(recordHandler, StateHttpClient.serializeHandler) ) http.get("http://localhost:$port/hello/mike").assertBody("GET /hello/mike", headers) @@ -135,7 +137,7 @@ class DynamicServerTest { val url = "http://localhost:${dynamicServer.runtimePort}" val adapter = JettyClientAdapter() val headers = mapOf("alfa" to "beta", "charlie" to listOf("delta", "echo")) - val http = Http(adapter, url, httpHeaders = headers) + val http = StateHttpClient(adapter, url, httpHeaders = headers) http.get("/hello/mike").assertBody("GET /hello/mike", headers) http.get().assertBody("GET / ", headers) @@ -187,13 +189,14 @@ class DynamicServerTest { SerializationManager.formats = linkedSetOf(Json) val settings = HttpServerSettings(bindPort = 0) - val server = DynamicServer(JettyServletAdapter(), settings).apply(DynamicServer::start) + val serverAdapter = JettyServletAdapter() + val server = DynamicHttpServer(serverAdapter, settings).apply(DynamicHttpServer::start) val headers = mapOf("alfa" to "beta", "charlie" to listOf("delta", "echo")) val text = ContentType(TEXT_PLAIN) val json = ContentType(APPLICATION_JSON) val binding = server.binding.toString() val adapter = JettyClientAdapter() - val http = Http(adapter, url = binding, httpHeaders = headers, httpContentType = json) + val http = StateHttpClient(adapter, binding, json, httpHeaders = headers) server.path { before("*") { @@ -210,7 +213,7 @@ class DynamicServerTest { } http.request { - put("/data/{id}", mapOf("id" to 102030)) { + put("/data/102030") { object { val title = "Casino Royale" val tags = listOf("007", "action") @@ -220,6 +223,17 @@ class DynamicServerTest { assertOk() response.body.info("BODY: ") response.contentType.info("CONTENT TYPE: ") + + post("/data/102039") { + object { + val title = "Batman Begins" + val tags = listOf("DC", "action") + } + } + + assertOk() + response.body.info("BODY: ") + response.contentType.info("CONTENT TYPE: ") } http.request { @@ -260,21 +274,24 @@ class DynamicServerTest { } } - private fun Http.assertBody(expectedBody: String, checkedHeaders: Map) { + private fun StateHttpClient.assertBody(expectedBody: String, checkedHeaders: Map) { assertOk() assertSuccess() assertStatus(OK_200) assertStatus(SUCCESS) + assertContentType(ContentType(TEXT_PLAIN)) + assertContentType(TEXT_PLAIN) assertBodyContains(expectedBody) + assertEquals(ContentType(APPLICATION_JSON), request.contentType) + assertEquals(OK_200, status) + for ((k, v) in checkedHeaders.entries) { assertBodyContains(k, v.toString()) } } - private fun HttpResponsePort.assertBody( - expectedBody: String, checkedHeaders: Map) { - + private fun HttpResponsePort.assertBody(expectedBody: String, checkedHeaders: Map) { val bodyString = bodyString() assertEquals(OK_200, status) 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 new file mode 100644 index 0000000000..201cda52f0 --- /dev/null +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt @@ -0,0 +1,84 @@ +package com.hexagonkt.rest.tools + +import com.hexagonkt.core.media.TEXT_PLAIN +import com.hexagonkt.http.client.jetty.JettyClientAdapter +import com.hexagonkt.http.model.ContentType +import com.hexagonkt.http.model.HttpMethod.POST +import com.hexagonkt.http.model.HttpMethod.PUT +import com.hexagonkt.http.model.HttpResponsePort +import com.hexagonkt.http.model.OK_200 +import com.hexagonkt.http.server.jetty.JettyServletAdapter +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 kotlin.test.assertEquals +import kotlin.test.assertTrue + +@TestInstance(PER_CLASS) +internal class StateHttpClientTest { + private val server: DynamicHttpServer = DynamicHttpServer(JettyServletAdapter()) + private val text = ContentType(TEXT_PLAIN) + + @BeforeAll fun `Set up mock services`() { + server.start() + } + + @AfterAll fun `Shut down mock services`() { + server.stop() + } + + @Test fun `Check all HTTP methods`() { + server.path { + before("*") { + ok("$method $path ${request.headers}", contentType = text) + } + + on(setOf(PUT, POST), "/bye/mike/{id}") { + ok("$method $path ${request.bodyString()} ${request.headers}", contentType = text) + } + } + + val url = "http://localhost:${server.runtimePort}" + val adapter = JettyClientAdapter() + val headers = mapOf("alfa" to "beta", "charlie" to listOf("delta", "echo")) + val params = mapOf("id" to 9) + val client = StateHttpClient(adapter, url, TEXT_PLAIN, headers = headers) + + client.get("/hello/mike/{id}" to params).assertBody("GET /hello/mike/9", headers) + client.put("/hello/mike/{id}" to params).assertBody("PUT /hello/mike/9", headers) + client.post("/hello/mike/{id}" to params).assertBody("POST /hello/mike/9", headers) + client.options("/hello/mike/{id}" to params).assertBody("OPTIONS /hello/mike/9", headers) + client.delete("/hello/mike/{id}" to params).assertBody("DELETE /hello/mike/9", headers) + client.patch("/hello/mike/{id}" to params).assertBody("PATCH /hello/mike/9", headers) + client.trace("/hello/mike/{id}" to params).assertBody("TRACE /hello/mike/9", headers) + + client.put("/bye/mike/{id}" to params) { "putLambdaBody" } + .assertBody("PUT /bye/mike/9 putLambdaBody", headers) + client.post("/bye/mike/{id}" to params) { "postLambdaBody" } + .assertBody("POST /bye/mike/9 postLambdaBody", headers) + + client.request { + get("/hello/mike").assertBody("GET /hello/mike", headers) + put("/hello/mike").assertBody("PUT /hello/mike", headers) + post("/hello/mike").assertBody("POST /hello/mike", headers) + options("/hello/mike").assertBody("OPTIONS /hello/mike", headers) + delete("/hello/mike").assertBody("DELETE /hello/mike", headers) + patch("/hello/mike").assertBody("PATCH /hello/mike", headers) + trace("/hello/mike").assertBody("TRACE /hello/mike", headers) + } + } + + private fun HttpResponsePort.assertBody(expectedBody: String, checkedHeaders: Map) { + val bodyString = bodyString() + + assertEquals(OK_200, status) + assertTrue(bodyString.startsWith(expectedBody)) + + for (entry in checkedHeaders.entries) { + assertTrue(bodyString.contains(entry.key)) + assertTrue(bodyString.contains(entry.value.toString())) + } + } +} 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 7e41a874e5..aa20a408b0 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,6 +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 // verify( // HttpRequest( // path = "/pet/findByStatus", @@ -35,7 +36,7 @@ internal class VerifySpecCallbackTest { // body = listOf( // mapOf( // "name" to "Keka", -// "photoUrls" to listOf("http://example.com") +// "photoUrls" to listOf("https://example.com") // ) // ), // ), @@ -52,7 +53,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ), ), listOf("ERROR: validation.request.operation.notAllowed [ ] HEAD operation not allowed on path '/pet/1'. [] []") @@ -64,7 +65,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), HttpResponse( @@ -73,12 +74,13 @@ internal class VerifySpecCallbackTest { body = listOf( mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), ), 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, @@ -112,7 +114,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ), ), ) @@ -143,7 +145,7 @@ internal class VerifySpecCallbackTest { body = listOf( mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), ), @@ -155,7 +157,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), HttpResponse( @@ -163,7 +165,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ), ), ) @@ -174,7 +176,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), HttpResponse( @@ -182,7 +184,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ), ), ) @@ -193,7 +195,7 @@ internal class VerifySpecCallbackTest { contentType = ContentType(APPLICATION_JSON), body = mapOf( "name" to "Keka", - "photoUrls" to listOf("http://example.com") + "photoUrls" to listOf("https://example.com") ) ), HttpResponse(status = NOT_FOUND_404), diff --git a/site/assets/img/architecture.svg b/site/assets/img/architecture.svg index 80783e4bc4..e59f1ed143 100644 --- a/site/assets/img/architecture.svg +++ b/site/assets/img/architecture.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/site/mkdocs.yml b/site/mkdocs.yml index 935e6dabe9..7535f41da7 100644 --- a/site/mkdocs.yml +++ b/site/mkdocs.yml @@ -71,7 +71,7 @@ nav: - Planning: planning.md - Contributing ↗: https://github.com/hexagonkt/hexagon/contribute - GitHub ↗: https://github.com/hexagonkt - - Dev.to ↗: https://dev.to/hexagonkt + - Dev.to ↗: https://dev.to/hexagontk - LibHunt ↗: https://kotlin.libhunt.com/hexagon-alternatives - StackShare ↗: https://stackshare.io/hexagon @@ -127,14 +127,14 @@ extra: provider: google property: G-BEKWF2E4DJ - twitter_user: hexagon_kt + twitter_user: hexagontk social: - icon: fontawesome/brands/github link: https://github.com/hexagonkt - icon: fontawesome/brands/dev - link: https://dev.to/hexagonkt - - icon: fontawesome/brands/twitter - link: https://twitter.com/hexagon_kt + link: https://dev.to/hexagontk + - icon: fontawesome/brands/x-twitter + link: https://twitter.com/hexagontk - icon: fontawesome/brands/slack link: https://kotlinlang.slack.com/messages/hexagon diff --git a/site/mkdocs/main.html b/site/mkdocs/main.html index 5b41d02be3..6130d174e9 100644 --- a/site/mkdocs/main.html +++ b/site/mkdocs/main.html @@ -128,7 +128,7 @@ aria-label="Fork {{ config.extra.repo }} on GitHub">Fork + href="https://twitter.com/intent/tweet?text=Hexagon Toolkit">Post {{ extracopyright }} diff --git a/site/pages/index.md b/site/pages/index.md index e94c33f531..5feba69bbe 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -180,7 +180,9 @@ graph TD http_client -->|uses| http_handlers web -->|uses| http_server web -->|uses| templates - rest -->|uses| http_server + rest -->|uses| http_handlers rest -->|uses| serialization rest_tools -->|uses| rest + rest_tools -->|uses| http_server + rest_tools -->|uses| http_client ```