Skip to content

Commit

Permalink
Merge pull request #685 from hexagonkt/develop
Browse files Browse the repository at this point in the history
Improvements in exception handling
  • Loading branch information
jaguililla committed Dec 21, 2023
2 parents a9bc96d + 4fcb8b9 commit c195218
Show file tree
Hide file tree
Showing 18 changed files with 83 additions and 83 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ private val path: PathHandler = path {
* Catching `Exception` handles any unhandled exception, has to be the last executed (first
* declared)
*/
exception<Exception>(NOT_FOUND_404) {
exception<Exception> {
internalServerError("Root handler")
}

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.gradle.api.tasks.wrapper.Wrapper.DistributionType.ALL
*/

plugins {
kotlin("jvm") version("1.9.21") apply(false)
kotlin("jvm") version("1.9.22") apply(false)

id("idea")
id("eclipse")
Expand Down
3 changes: 2 additions & 1 deletion contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ If you want to generate the documentation site, check the Hexagon's site module
11. Publish changelog on:
* Dev.to
* Kotlin Slack
* Reddit
* Reddit Kotlin
* Twitter
* Kotlin Weekly Newsletter
* LinkedIn
* Mailing lists (Awesome Kotlin, Kotlin Weekly)

[Nexus Repository Manager]: https://oss.sonatype.org

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/hexagonkt/core/Network.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import java.util.*
/** Internet address used to bind services to all local network interfaces. */
val ALL_INTERFACES: InetAddress by lazy { inetAddress(0, 0, 0, 0) }

/** Internet address used to bind services to the loopback interface. */
/** Internet address used to bind services to the loop-back interface. */
val LOOPBACK_INTERFACE: InetAddress by lazy { inetAddress(127, 0, 0, 1) }

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/hexagonkt/core/text/Ansi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package com.hexagonkt.core.text
*/
object Ansi {
/** Regex that matches ANSI escape sequences. */
val REGEX: Regex = """\u001B\[\d+?m""".toRegex()
val REGEX: Regex by lazy { """\u001B\[\d+?m""".toRegex() }

/** Control Sequence Introducer. */
const val CSI = "\u001B["
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/kotlin/com/hexagonkt/core/text/Cases.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.hexagonkt.core.text

val CAMEL_CASE: Regex = Regex("[a-z]+([A-Z][a-z0-9]+)+")
val PASCAL_CASE: Regex = Regex("([A-Z][a-z0-9]+)+")
val SNAKE_CASE: Regex = Regex("[A-Za-z]+(_[A-Za-z0-9]+)+")
val KEBAB_CASE: Regex = Regex("[A-Za-z]+(-[A-Za-z0-9]+)+")
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]+)+") }

fun String.camelToWords(): List<String> =
split("(?=\\p{Upper}\\p{Lower})".toRegex()).toWords()
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/kotlin/com/hexagonkt/core/text/Strings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.hexagonkt.core.urlOf
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
import java.lang.System.getProperty
import java.net.InetAddress
import java.net.URI
import java.net.URL
Expand All @@ -23,7 +22,7 @@ private val base64Encoder: Base64.Encoder by lazy { Base64.getEncoder().withoutP
private val base64Decoder: Base64.Decoder by lazy { Base64.getDecoder() }

/** Runtime specific end of line. */
val eol: String by lazy { getProperty("line.separator") }
val eol: String by lazy { System.lineSeparator() }

/** Supported types for the [parseOrNull] function. */
val parsedClasses: Set<KClass<*>> by lazy {
Expand Down
16 changes: 8 additions & 8 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ org.gradle.warning.mode=all
org.gradle.console=plain

# Gradle
version=3.4.5
version=3.4.6
group=com.hexagonkt
description=The atoms of your platform

Expand All @@ -31,26 +31,26 @@ logoSmall=assets/img/logo.svg
iconsDirectory=content

# VERSIONS
kotlinVersion=1.9.21
kotlinVersion=1.9.22
dokkaVersion=1.9.10
mockkVersion=1.13.8
junitVersion=5.10.1
gatlingVersion=3.9.5
gatlingVersion=3.10.2
jmhVersion=1.37
mkdocsMaterialVersion=9.4.14
mkdocsMaterialVersion=9.5.2
mermaidDokkaVersion=0.4.4
nativeToolsVersion=0.9.28

# http_server_netty
nettyVersion=4.1.101.Final
nettyVersion=4.1.104.Final
nettyTcNativeVersion=2.0.62.Final

# http_server_helidon
helidonVersion=4.0.1
helidonVersion=4.0.2

# http_server_servlet
servletVersion=6.0.0
jettyVersion=12.0.4
jettyVersion=12.0.5

# rest_tools
swaggerRequestValidatorVersion=2.39.0
Expand All @@ -67,7 +67,7 @@ dslJsonVersion=2.0.2
freemarkerVersion=2.3.32

# templates_jte
jteVersion=3.1.5
jteVersion=3.1.6

# templates_pebble
pebbleVersion=3.2.2
Expand Down
2 changes: 1 addition & 1 deletion gradle/kotlin.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ apply(plugin: "maven-publish")
defaultTasks("build")

java {
sourceCompatibility = JavaVersion.toVersion(findProperty("jvmTarget") ?: "17")
sourceCompatibility = JavaVersion.toVersion(findProperty("jvmTarget") ?: "11")
targetCompatibility = sourceCompatibility
}

Expand Down
30 changes: 26 additions & 4 deletions http/http_handlers/api/http_handlers.api
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ public final class com/hexagonkt/http/handlers/BeforeHandler : com/hexagonkt/han
public fun toString ()Ljava/lang/String;
}

public final class com/hexagonkt/http/handlers/ExceptionHandler : com/hexagonkt/handlers/Handler, com/hexagonkt/http/handlers/HttpHandler {
public fun <init> (Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun addPrefix (Ljava/lang/String;)Lcom/hexagonkt/http/handlers/HttpHandler;
public fun byMethod ()Ljava/util/Map;
public final fun component1 ()Lkotlin/reflect/KClass;
public final fun component2 ()Z
public final fun component3 ()Lkotlin/jvm/functions/Function2;
public final fun copy (Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;)Lcom/hexagonkt/http/handlers/ExceptionHandler;
public static synthetic fun copy$default (Lcom/hexagonkt/http/handlers/ExceptionHandler;Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/ExceptionHandler;
public fun equals (Ljava/lang/Object;)Z
public fun filter (Lcom/hexagonkt/http/model/HttpMethod;)Lcom/hexagonkt/http/handlers/HttpHandler;
public final fun getBlock ()Lkotlin/jvm/functions/Function2;
public fun getCallback ()Lkotlin/jvm/functions/Function1;
public final fun getClear ()Z
public final fun getException ()Lkotlin/reflect/KClass;
public fun getHandlerPredicate ()Lcom/hexagonkt/http/handlers/HttpPredicate;
public fun getPredicate ()Lkotlin/jvm/functions/Function1;
public fun hashCode ()I
public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context;
public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext;
public fun toString ()Ljava/lang/String;
}

public final class com/hexagonkt/http/handlers/FilterHandler : com/hexagonkt/handlers/Handler, com/hexagonkt/http/handlers/HttpHandler {
public fun <init> (Lcom/hexagonkt/http/handlers/HttpPredicate;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lcom/hexagonkt/http/handlers/HttpPredicate;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -96,8 +120,8 @@ public final class com/hexagonkt/http/handlers/HandlerBuilder {
public static synthetic fun before$default (Lcom/hexagonkt/http/handlers/HandlerBuilder;Ljava/util/Set;Ljava/lang/String;Lkotlin/reflect/KClass;Lcom/hexagonkt/http/model/HttpStatus;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun delete (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun delete$default (Lcom/hexagonkt/http/handlers/HandlerBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun exception (Lkotlin/reflect/KClass;Lcom/hexagonkt/http/model/HttpStatus;ZLkotlin/jvm/functions/Function2;)V
public static synthetic fun exception$default (Lcom/hexagonkt/http/handlers/HandlerBuilder;Lkotlin/reflect/KClass;Lcom/hexagonkt/http/model/HttpStatus;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
public final fun exception (Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;)V
public static synthetic fun exception$default (Lcom/hexagonkt/http/handlers/HandlerBuilder;Lkotlin/reflect/KClass;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
public final fun filter (Lcom/hexagonkt/http/handlers/HttpPredicate;Lkotlin/jvm/functions/Function1;)V
public final fun filter (Lcom/hexagonkt/http/model/HttpMethod;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun filter (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
Expand Down Expand Up @@ -142,8 +166,6 @@ public final class com/hexagonkt/http/handlers/HandlerBuilder {
public final class com/hexagonkt/http/handlers/HandlersKt {
public static final fun Delete (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/hexagonkt/http/handlers/OnHandler;
public static synthetic fun Delete$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/OnHandler;
public static final fun Exception (Lkotlin/reflect/KClass;Lcom/hexagonkt/http/model/HttpStatus;ZLkotlin/jvm/functions/Function2;)Lcom/hexagonkt/http/handlers/AfterHandler;
public static synthetic fun Exception$default (Lkotlin/reflect/KClass;Lcom/hexagonkt/http/model/HttpStatus;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/AfterHandler;
public static final fun Get (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/hexagonkt/http/handlers/OnHandler;
public static synthetic fun Get$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/hexagonkt/http/handlers/OnHandler;
public static final fun Head (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/hexagonkt/http/handlers/OnHandler;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.hexagonkt.http.handlers

import com.hexagonkt.handlers.ExceptionHandler
import com.hexagonkt.handlers.Handler
import com.hexagonkt.http.model.HttpCall
import kotlin.reflect.KClass

data class ExceptionHandler<E : Exception>(
val exception: KClass<E>,
val clear: Boolean = true,
val block: HttpExceptionCallback<E>
) : HttpHandler, Handler<HttpCall> by ExceptionHandler(exception, clear, toCallback(block)) {

override val handlerPredicate: HttpPredicate = HttpPredicate()

override fun addPrefix(prefix: String): HttpHandler =
this
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,15 @@ class HandlerBuilder(var handlers: List<HttpHandler> = emptyList()) {
}

fun <T : Exception> exception(
exception: KClass<T>? = null,
status: HttpStatus? = null,
clear: Boolean = true,
callback: HttpExceptionCallback<T>,
exception: KClass<T>, clear: Boolean = true, callback: HttpExceptionCallback<T>
) {
use(Exception(exception, status, clear, callback))
use(ExceptionHandler(exception, clear, callback))
}

inline fun <reified T : Exception> exception(
status: HttpStatus? = null,
clear: Boolean = true,
noinline callback: HttpExceptionCallback<T>,
clear: Boolean = true, noinline callback: HttpExceptionCallback<T>,
) {
use(Exception(T::class, status, clear, callback))
use(ExceptionHandler(T::class, clear, callback))
}

fun get(pattern: String = "", callback: HttpCallback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import com.hexagonkt.http.model.HttpRequest
import java.lang.IllegalStateException
import java.math.BigInteger
import java.security.cert.X509Certificate
import kotlin.reflect.KClass
import kotlin.reflect.cast

typealias HttpCallback = HttpContext.() -> HttpContext
typealias HttpExceptionCallback<T> = HttpContext.(T) -> HttpContext
Expand All @@ -27,6 +25,11 @@ private val BODY_TYPES_NAMES: String by lazy {
internal fun toCallback(block: HttpCallback): (Context<HttpCall>) -> Context<HttpCall> =
{ context -> HttpContext(context).block() }

internal fun <E : Exception> toCallback(
block: HttpExceptionCallback<E>
): (Context<HttpCall>, E) -> Context<HttpCall> =
{ context, e -> HttpContext(context).block(e) }

fun HttpCallback.process(
request: HttpRequest,
attributes: Map<*, *> = emptyMap<Any, Any>()
Expand Down Expand Up @@ -87,29 +90,6 @@ fun path(contextPath: String = "", handlers: List<HttpHandler>): PathHandler =
PathHandler(contextPath, it)
}

fun <T : Exception> Exception(
exception: KClass<T>? = null,
status: HttpStatus? = null,
clear: Boolean = true,
callback: HttpExceptionCallback<T>,
): AfterHandler =
AfterHandler(emptySet(), "*", exception, status) {
callback(this.exception.castException(exception)).let {
if (clear) it.copy(exception = null)
else it
}
}

inline fun <reified T : Exception> Exception(
status: HttpStatus? = null,
clear: Boolean = true,
noinline callback: HttpExceptionCallback<T>,
): AfterHandler =
Exception(T::class, status, clear, callback)

internal fun <T : Exception> Exception?.castException(exception: KClass<T>?) =
this?.let { exception?.cast(this) } ?: error("Exception 'null' or incorrect type")

fun Get(pattern: String = "", callback: HttpCallback): OnHandler =
OnHandler(GET, pattern, callback)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ sealed interface HttpHandler : Handler<HttpCall> {

is BeforeHandler ->
copy(handlerPredicate = handlerPredicate.clearMethods())

is ExceptionHandler<*> ->
this
}

fun process(request: HttpRequestPort): HttpContext =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import java.math.BigInteger
import java.time.LocalDate
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.reflect.KClass
import kotlin.test.assertContentEquals
import kotlin.test.assertNull

Expand Down Expand Up @@ -48,25 +47,9 @@ internal class HandlersTest {
assertEquals(expected2.handlersPredicates(), actual2.handlersPredicates())
}

@Test
@Suppress("CAST_NEVER_SUCCEEDS") // Required for test 'null' arguments
fun `Exceptions are casted properly`() {
assertFailsWith<IllegalStateException> { null.castException(Exception::class) }
assertFailsWith<IllegalStateException> { null.castException(null as? KClass<Exception>) }
assertFailsWith<ClassCastException> {
IllegalStateException().castException(IllegalArgumentException::class)
}
assertFailsWith<IllegalStateException> {
RuntimeException().castException(null as? KClass<Exception>)
}

val ise = IllegalStateException()
assertEquals(ise, ise.castException(RuntimeException::class))
}

@Test fun `Exceptions are cleared properly`() {
PathHandler(
Exception<Exception> { ok() },
ExceptionHandler(Exception::class) { ok() },
OnHandler { error("Error") }
)
.process(HttpRequest())
Expand All @@ -76,7 +59,7 @@ internal class HandlersTest {
}

PathHandler(
Exception<Exception> { this },
ExceptionHandler(Exception::class) { this },
OnHandler { error("Error") }
)
.process(HttpRequest())
Expand All @@ -86,7 +69,7 @@ internal class HandlersTest {
}

PathHandler(
Exception<Exception>(clear = false) { ok() },
ExceptionHandler(Exception::class, clear = false) { ok() },
OnHandler { error("Error") }
)
.process(HttpRequest())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class JettyServletAdapter(
serverConnector.host = settings.bindAddress.hostName
serverConnector.port = settings.bindPort
serverConnector.connectionFactories
.filterIsInstance(HttpConnectionFactory::class.java)
.filterIsInstance<HttpConnectionFactory>()
.map { it.httpConfiguration }
.map {
it.sendDateHeader = sendDateHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.hexagonkt.http.test.examples

import com.hexagonkt.core.fail
import com.hexagonkt.http.client.HttpClientPort
import com.hexagonkt.http.model.NOT_FOUND_404
import com.hexagonkt.http.model.HttpStatus
import com.hexagonkt.http.model.Header
import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500
Expand Down Expand Up @@ -32,7 +31,7 @@ abstract class ErrorsTest(
* Catching `Exception` handles any unhandled exception, has to be the last executed (first
* declared)
*/
exception<Exception>(NOT_FOUND_404) {
exception<Exception> {
internalServerError("Root handler")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ abstract class SamplesTest(

val server = serve(serverAdapter()) {
// errors
exception<Exception>(NOT_FOUND_404) {
exception<Exception> {
internalServerError("Root handler")
}

Expand All @@ -464,7 +464,7 @@ abstract class SamplesTest(
before(pattern = "*", status = HttpStatus(509)) {
send(HttpStatus(599))
}
before(pattern = "*", exception = IllegalStateException::class) {
exception<IllegalStateException> {
send(HTTP_VERSION_NOT_SUPPORTED_505, exception?.message ?: "empty")
}
// exceptions
Expand Down

0 comments on commit c195218

Please sign in to comment.