Skip to content

Commit

Permalink
Small improvements and fixes
Browse files Browse the repository at this point in the history
* Any media type
* Fix case types regex
* Improve API documentation
* Regroup some utility methods
* Improve settings loading method
* Move basic auth
* Fix HTTP client errors
* Add utility interfaces to extend HTTP handlers
* Fix HTTP handlers context 'receive' method
* Fix REST serialize callbacks
* Improve REST testing tools
  • Loading branch information
jaguililla committed Jan 24, 2024
1 parent db77a79 commit 950b4df
Show file tree
Hide file tree
Showing 60 changed files with 636 additions and 315 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ hs_err_pid*
# Other Tools
kotlin-js-store/
node_modules/
package-lock.json
.env
.sdkmanrc

# System Files
.DS_Store
Expand Down
3 changes: 3 additions & 0 deletions .sdkmanrc
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion core/api/core.api
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
30 changes: 26 additions & 4 deletions core/src/main/kotlin/com/hexagonkt/core/Checks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,34 @@ fun <T : Any, N> 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 <Z> Collection<Z>.checkSize(count: IntRange): Collection<Z> = 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<Exception> = blocks.mapNotNull {
try {
it()
null
}
catch(e: Exception) {
e
}
}

if (exceptions.isNotEmpty())
throw MultipleException(message, exceptions)
}
12 changes: 5 additions & 7 deletions core/src/main/kotlin/com/hexagonkt/core/CodedException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 0 additions & 18 deletions core/src/main/kotlin/com/hexagonkt/core/Exceptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exception> = blocks.mapNotNull {
try {
it()
null
}
catch(e: Exception) {
e
}
}

if (exceptions.isNotEmpty())
throw MultipleException(message, exceptions)
}
5 changes: 3 additions & 2 deletions core/src/main/kotlin/com/hexagonkt/core/Jvm.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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() }
Expand Down Expand Up @@ -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. */
Expand Down
14 changes: 8 additions & 6 deletions core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Throwable>, message: String = "") :
RuntimeException (message, null) {
class MultipleException(val causes: List<Throwable>, message: String = "") :
RuntimeException(message, null) {

constructor(vararg causes: Throwable) : this(causes.toList())
constructor(message: String, causes: List<Throwable>) : this(causes, message)
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/kotlin/com/hexagonkt/core/media/MediaTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> =
split("(?=\\p{Upper}\\p{Lower})".toRegex()).toWords()
Expand Down
22 changes: 22 additions & 0 deletions core/src/test/kotlin/com/hexagonkt/core/ChecksTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,26 @@ internal class ChecksTest {
val e = assertFailsWith<IllegalArgumentException>(block = block)
assertEquals(message, e.message)
}

@Test fun `Check multiple errors`() {
val e = assertFailsWith<MultipleException> {
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") },
)
}
}
22 changes: 0 additions & 22 deletions core/src/test/kotlin/com/hexagonkt/core/ExceptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<MultipleException> {
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") },
)
}
}
3 changes: 2 additions & 1 deletion core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal class JvmTest {
assertEquals("z3", System.getProperty("s3"))

val e = assertFailsWith<IllegalStateException> { 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`() {
Expand Down Expand Up @@ -213,6 +213,7 @@ internal class JvmTest {
assert(Jvm.systemSetting<String>("system_property") == "value")

assert(Jvm.systemSetting<String>("PATH").isNotEmpty())
assert(Jvm.systemSetting<String>("path").isNotEmpty())
assertNull(Jvm.systemSettingOrNull<String>("_not_defined_"))

System.setProperty("PATH", "path override")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 9 additions & 8 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal class ExceptionHandlerTest {

ChainHandler(
ExceptionHandler<String, Exception>(Exception::class, false) { c, _ -> c.with("ok") },
ExceptionHandler<String, Exception>(Exception::class, false) { c, _ -> error("Fail") },
ExceptionHandler(Exception::class, false) { _, _ -> error("Fail") },
OnHandler { error("Error") }
)
.process(EventContext("test", { true }))
Expand Down
2 changes: 2 additions & 0 deletions http/http/api/http.api
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 4 additions & 0 deletions http/http/src/main/kotlin/com/hexagonkt/http/Http.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +21,9 @@ val CHECKED_HEADERS: List<String> 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
Expand Down
Loading

0 comments on commit 950b4df

Please sign in to comment.