From 4d0e808009d069f14e4849256b635f8d8be32aff Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Fri, 10 Nov 2023 21:33:24 +0100 Subject: [PATCH 1/5] GH-40 Replace plugins with a new routing API v1 --- build.gradle.kts | 6 +- .../community/routing/RouteComparator.kt | 2 +- .../community/routing/dsl/DslRouting.kt | 40 +++++++++ .../community/routing/dsl/DslRoutingPlugin.kt | 89 ------------------- .../routing/dsl/InPlaceRoutingDslTest.kt | 8 +- 5 files changed, 50 insertions(+), 95 deletions(-) create mode 100644 routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt delete mode 100644 routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRoutingPlugin.kt diff --git a/build.gradle.kts b/build.gradle.kts index a2aabc8..640c4e8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -121,8 +121,10 @@ subprojects { compileOnly("io.javalin:javalin:$javalin") testImplementation("io.javalin:javalin:$javalin") testImplementation("io.javalin:javalin-testtools:$javalin") - kaptTest("io.javalin.community.openapi:openapi-annotation-processor:$javalin") - testImplementation("io.javalin.community.openapi:javalin-openapi-plugin:$javalin") + + val openapi = "5.6.3" + kaptTest("io.javalin.community.openapi:openapi-annotation-processor:$openapi") + testImplementation("io.javalin.community.openapi:javalin-openapi-plugin:$openapi") val jackson = "2.15.3" testImplementation("com.fasterxml.jackson.core:jackson-databind:$jackson") diff --git a/routing-core/src/main/kotlin/io/javalin/community/routing/RouteComparator.kt b/routing-core/src/main/kotlin/io/javalin/community/routing/RouteComparator.kt index 4f58631..1920f96 100644 --- a/routing-core/src/main/kotlin/io/javalin/community/routing/RouteComparator.kt +++ b/routing-core/src/main/kotlin/io/javalin/community/routing/RouteComparator.kt @@ -4,7 +4,7 @@ import java.text.Collator import java.text.RuleBasedCollator import java.util.Locale -fun List.sortRoutes(): List = +fun Collection.sortRoutes(): List = sortedWith(RouteComparator()) open class RouteComparator : Comparator { diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt new file mode 100644 index 0000000..f1cc789 --- /dev/null +++ b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt @@ -0,0 +1,40 @@ +package io.javalin.community.routing.dsl + +import io.javalin.community.routing.dsl.defaults.DefaultDsl +import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultConfiguration +import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultScope +import io.javalin.community.routing.dsl.defaults.DefaultRoute +import io.javalin.community.routing.sortRoutes +import io.javalin.config.JavalinConfig +import io.javalin.http.HandlerType +import io.javalin.router.InternalRouter +import io.javalin.router.RoutingApiInitializer +import java.util.function.Consumer + +open class DslRouting< + CONFIG : RoutingDslConfiguration, + ROUTE : DslRoute, + CONTEXT, + RESPONSE : Any +>( + private val factory: RoutingDslFactory +) : RoutingApiInitializer { + + companion object { + object Dsl : DslRouting(DefaultDsl) + } + + override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer) { + val dslConfig = factory.createConfiguration() + setup.accept(dslConfig) + + dslConfig.routes + .sortRoutes() + .map { route -> route to factory.createHandler(route) } + .forEach { (route, handler) -> internalRouter.addHttpHandler(HandlerType.valueOf(route.method.toString()), route.path, handler) } + + dslConfig.exceptionHandlers.forEach { (exceptionClass, handler) -> + internalRouter.addHttpExceptionHandler(exceptionClass.java, factory.createExceptionHandler(handler)) + } + } +} \ No newline at end of file diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRoutingPlugin.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRoutingPlugin.kt deleted file mode 100644 index f9bf69c..0000000 --- a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRoutingPlugin.kt +++ /dev/null @@ -1,89 +0,0 @@ -package io.javalin.community.routing.dsl - -import io.javalin.Javalin -import io.javalin.community.routing.dsl.defaults.DefaultDsl -import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultConfiguration -import io.javalin.community.routing.dsl.defaults.DefaultRoutes -import io.javalin.community.routing.registerRoute -import io.javalin.community.routing.sortRoutes -import io.javalin.config.JavalinConfig -import io.javalin.plugin.Plugin -import kotlin.reflect.KClass - -class DslRoutingPlugin< - CONFIG : RoutingDslConfiguration, - ROUTE : DslRoute, - CONTEXT, - RESPONSE : Any ->( - private val routingDsl: RoutingDslFactory -) : Plugin { - - private val registeredRoutes = mutableListOf() - private val registeredExceptionHandlers = mutableMapOf, DslExceptionHandler>() - - override fun apply(app: Javalin) { - registeredRoutes - .sortRoutes() - .map { route -> route to routingDsl.createHandler(route) } - .forEach { (route, handler) -> app.registerRoute(route.method, route.path, handler) } - - registeredExceptionHandlers.forEach { (exceptionClass, handler) -> - app.exception(exceptionClass.java, routingDsl.createExceptionHandler(handler)) - } - } - - fun routing(init: CONFIG.() -> Unit): DslRoutingPlugin = also { - val routingConfiguration = routingDsl.createConfiguration() - routingConfiguration.init() - registeredRoutes.addAll(routingConfiguration.routes) - registeredExceptionHandlers.putAll(routingConfiguration.exceptionHandlers) - } - - fun routing(vararg containers: DslContainer): DslRoutingPlugin = - routing { - containers.forEach { container -> - addRoutes(container.routes()) - container.exceptionHandlers().forEach { exception(it.type, it.handler) } - } - } - - fun routing(routes: Collection): DslRoutingPlugin = - routing { - addRoutes(routes) - } - - fun routing(vararg routes: ROUTE): DslRoutingPlugin = - routing(routes.toList()) - -} - -fun < - CONFIG : RoutingDslConfiguration, - ROUTE : DslRoute, - CONTEXT, - RESPONSE : Any -> JavalinConfig.routing( - routingDsl: RoutingDslFactory, - vararg routes: DslContainer -) = plugins.register( - DslRoutingPlugin(routingDsl).routing(*routes) -) - -fun JavalinConfig.routing(vararg routes: DefaultRoutes) = - routing(DefaultDsl, *routes) - -fun < - CONFIG : RoutingDslConfiguration, - ROUTE : DslRoute, - CONTEXT, - RESPONSE : Any -> JavalinConfig.routing( - routingDsl: RoutingDslFactory, - init: CONFIG.() -> Unit -) = plugins.register( - DslRoutingPlugin(routingDsl).routing(init) -) - -fun JavalinConfig.routing(init: DefaultConfiguration.() -> Unit) = - routing(DefaultDsl, init) \ No newline at end of file diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt index 4aedeb3..759181e 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt @@ -2,6 +2,7 @@ package io.javalin.community.routing.dsl import io.javalin.Javalin import io.javalin.community.routing.Route +import io.javalin.community.routing.dsl.DslRouting.Companion.Dsl import io.javalin.community.routing.dsl.defaults.Path import io.javalin.community.routing.dsl.specification.TestSpecification import io.javalin.testtools.JavalinTest @@ -18,8 +19,8 @@ class InPlaceRoutingDslTest : TestSpecification() { fun `each http dsl method is properly mapped to javalin handler`() = JavalinTest.test( // given: a javalin app with routes defined using the dsl Javalin.create { config -> - config.routing { - before("/before") { header("test", "before") } + config.router.mount(Dsl) { + it.before("/before") { header("test", "before") } after("/after") { header("test", "after") } get("/throwing") { throw RuntimeException() } @@ -37,7 +38,8 @@ class InPlaceRoutingDslTest : TestSpecification() { defaultConfig ) { _, client -> // when: a request is made to the http route - Route.values() + Route.entries + .asSequence() .filter { it.isHttpMethod } .map { it.name.lowercase() } .map { it to request(it, "${client.origin}/$it").asEmpty() } From 23883beac398e1806caf34b5bfe1e8004ce107d9 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Fri, 10 Nov 2023 22:54:15 +0100 Subject: [PATCH 2/5] GH-40 Replace reactive plugin with a new routing API --- .../routing/JavalinRoutingExtensions.kt | 26 ++++---- .../community/routing/JavalinRoutesTest.kt | 13 ++-- .../coroutines/ReactiveRoutingPlugin.kt | 53 +++++++++------- .../routing/CoroutinesRoutingTest.kt | 56 ++++++----------- .../examples/ReactiveRoutingExample.kt | 14 +++-- .../community/routing/dsl/DslFactory.kt | 61 ++++++++----------- .../community/routing/dsl/DslRouting.kt | 1 + .../dsl/defaults/DefaultContextScope.kt | 40 +++--------- .../routing/dsl/InPlaceRoutingDslTest.kt | 61 +++++++++---------- .../routing/dsl/PropertyRoutingDslTest.kt | 5 +- .../routing/dsl/examples/InPlaceExample.kt | 20 +++--- 11 files changed, 158 insertions(+), 192 deletions(-) diff --git a/routing-core/src/main/kotlin/io/javalin/community/routing/JavalinRoutingExtensions.kt b/routing-core/src/main/kotlin/io/javalin/community/routing/JavalinRoutingExtensions.kt index 6a52be5..b1b45a9 100644 --- a/routing-core/src/main/kotlin/io/javalin/community/routing/JavalinRoutingExtensions.kt +++ b/routing-core/src/main/kotlin/io/javalin/community/routing/JavalinRoutingExtensions.kt @@ -2,6 +2,8 @@ package io.javalin.community.routing import io.javalin.Javalin import io.javalin.http.Handler +import io.javalin.http.HandlerType +import io.javalin.router.InternalRouter import io.javalin.security.RouteRole class JavalinRoutingExtensions(private val javalin: Javalin) { @@ -20,7 +22,7 @@ class JavalinRoutingExtensions(private val javalin: Javalin) { fun register(): Javalin { routes .sortRoutes() - .forEach { javalin.registerRoute(it) } + .forEach { javalin.unsafeConfig().pvt.internalRouter.registerRoute(it) } return javalin } @@ -34,19 +36,19 @@ data class HandlerEntry @JvmOverloads constructor( val roles: List = emptyList(), ) : Routed -fun Javalin.registerRoute(handlerEntry: HandlerEntry) = +fun InternalRouter.registerRoute(handlerEntry: HandlerEntry) = registerRoute(handlerEntry.route, handlerEntry.path, handlerEntry.handler, *handlerEntry.roles.toTypedArray()) -fun Javalin.registerRoute(route: Route, path: String, handler: Handler, vararg roles: RouteRole) { +fun InternalRouter.registerRoute(route: Route, path: String, handler: Handler, vararg roles: RouteRole) { when (route) { - Route.HEAD -> head(path, handler, *roles) - Route.PATCH -> patch(path, handler, *roles) - Route.OPTIONS -> options(path, handler, *roles) - Route.GET -> get(path, handler, *roles) - Route.PUT -> put(path, handler, *roles) - Route.POST -> post(path, handler, *roles) - Route.DELETE -> delete(path, handler, *roles) - Route.AFTER -> after(path, handler) - Route.BEFORE -> before(path, handler) + Route.HEAD -> addHttpHandler(HandlerType.HEAD, path, handler, *roles) + Route.PATCH -> addHttpHandler(HandlerType.PATCH, path, handler, *roles) + Route.OPTIONS -> addHttpHandler(HandlerType.OPTIONS, path, handler, *roles) + Route.GET -> addHttpHandler(HandlerType.GET, path, handler, *roles) + Route.PUT -> addHttpHandler(HandlerType.PUT, path, handler, *roles) + Route.POST -> addHttpHandler(HandlerType.POST, path, handler, *roles) + Route.DELETE -> addHttpHandler(HandlerType.DELETE, path, handler, *roles) + Route.AFTER -> addHttpHandler(HandlerType.AFTER, path, handler) + Route.BEFORE -> addHttpHandler(HandlerType.BEFORE, path, handler) } } \ No newline at end of file diff --git a/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt b/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt index 664c43b..050e411 100644 --- a/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt +++ b/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt @@ -5,6 +5,7 @@ import io.javalin.community.routing.Route.GET import io.javalin.community.routing.Route.PUT import io.javalin.http.Handler import io.javalin.http.HandlerType +import io.javalin.util.Util.firstOrNull import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,7 +14,7 @@ class JavalinRoutesTest { @Test fun `should properly register handler by given enum`() { // given: a list of routes - val routes = Route.values() + val routes = Route.entries .map { it to Handler { ctx -> ctx.result(it.name) } } // when: routes are registered @@ -23,9 +24,8 @@ class JavalinRoutesTest { // then: all routes are registered by as proper HandlerType routes.forEach { (method, handler) -> assertThat( - app.javalinServlet() - .matcher - .findEntries(HandlerType.findByName(method.name), "/") + app.unsafeConfig().pvt.internalRouter + .findHttpHandlerEntries(HandlerType.findByName(method.name), "/") .firstOrNull() ?.handler ).isEqualTo(handler) @@ -41,9 +41,8 @@ class JavalinRoutesTest { listOf("GET", "PUT").forEach { method -> assertThat( - app.javalinServlet() - .matcher - .findEntries(HandlerType.findByName(method), "/") + app.unsafeConfig().pvt.internalRouter + .findHttpHandlerEntries(HandlerType.findByName(method), "/") .firstOrNull() ).isNotNull } diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt index 08cb406..8e78d9d 100644 --- a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt +++ b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt @@ -1,39 +1,46 @@ package io.javalin.community.routing.coroutines -import io.javalin.Javalin +import io.javalin.community.routing.Route import io.javalin.community.routing.coroutines.servlet.CoroutinesServlet -import io.javalin.community.routing.registerRoute import io.javalin.community.routing.sortRoutes import io.javalin.config.JavalinConfig import io.javalin.http.Handler -import io.javalin.plugin.Plugin +import io.javalin.http.HandlerType +import io.javalin.router.InternalRouter +import io.javalin.router.RoutingApiInitializer +import java.util.function.Consumer -class ReactiveRoutingPlugin, CONTEXT, RESPONSE : Any>( - private val servlet: CoroutinesServlet -) : Plugin { +class CoroutinesRouting { + internal val routes = mutableListOf>() - private val routes = mutableListOf() + fun route(route: ReactiveRoute) { + routes.add(route) + } - override fun apply(app: Javalin) { - routes - .sortRoutes() - .map { it to Handler { ctx -> servlet.handle(ctx, it) } } - .forEach { (reactiveRoute, handler) -> app.registerRoute(reactiveRoute.method, reactiveRoute.path, handler) } + fun route(method: Route, path: String, async: Boolean = true, handler: suspend CONTEXT.() -> RESPONSE) { + routes.add(ReactiveRoute(path, method, async, handler)) } - fun > routing(vararg reactiveRoutes: ROUTES) { - reactiveRoutes.forEach { - routes.addAll(it.routes()) - } + fun routes(exampleEndpoint: ReactiveRoutes, CONTEXT, RESPONSE>): CoroutinesRouting { + exampleEndpoint.routes().forEach { route(it) } + return this } } -fun , ROUTES : ReactiveRoutes, CONTEXT, RESPONSE : Any> JavalinConfig.reactiveRouting( - servlet: CoroutinesServlet, - vararg routes: ROUTES -) { - val reactiveRoutingPlugin = ReactiveRoutingPlugin(servlet) - reactiveRoutingPlugin.routing(*routes) - this.plugins.register(reactiveRoutingPlugin) +class Coroutines, CONTEXT, RESPONSE : Any>( + private val servlet: CoroutinesServlet, +) : RoutingApiInitializer> { + + override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer>) { + val coroutinesRouting = CoroutinesRouting() + setup.accept(coroutinesRouting) + + coroutinesRouting + .routes + .sortRoutes() + .map { it to Handler { ctx -> servlet.handle(ctx, it) } } + .forEach { (route, handler) -> internalRouter.addHttpHandler(HandlerType.valueOf(route.method.toString()), route.path, handler) } + } + } \ No newline at end of file diff --git a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt index 7db244c..9b2f704 100644 --- a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt +++ b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt @@ -1,17 +1,14 @@ package io.javalin.community.routing import io.javalin.Javalin +import io.javalin.community.routing.coroutines.Coroutines import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet -import io.javalin.community.routing.coroutines.ReactiveRoute -import io.javalin.community.routing.coroutines.ReactiveRoutes -import io.javalin.community.routing.coroutines.reactiveRouting -import io.javalin.http.Context import io.javalin.testtools.JavalinTest +import java.util.concurrent.Executors import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import java.util.concurrent.Executors class CoroutinesRoutingTest { @@ -26,22 +23,19 @@ class CoroutinesRoutingTest { assertThat(executor.isShutdown).isTrue } + private val coroutines = Coroutines(DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it }) + @Test fun `should properly execute coroutine`() = JavalinTest.test( Javalin.create { config -> - config.reactiveRouting( - DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it }, - object : ReactiveRoutes, Context, Unit>() { - override fun routes() = setOf( - reactiveRoute("/test", Route.GET) { - withContext(Dispatchers.IO) { - result(Thread.currentThread().name) - } - } - ) + config.router.mount(coroutines) { + it.route(Route.GET, "/test") { + withContext(Dispatchers.IO) { + result(Thread.currentThread().name) + } } - ) + } } ) { _, client -> assertThat(client.get("/test").body?.string()).contains("DefaultDispatcher") @@ -53,18 +47,13 @@ class CoroutinesRoutingTest { fun `javalin should be able to handle exceptions from coroutines`() = JavalinTest.test( Javalin.create { config -> - config.reactiveRouting( - DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it }, - object : ReactiveRoutes, Context, Unit>() { - override fun routes() = setOf( - reactiveRoute("/test", Route.GET) { - withContext(Dispatchers.IO) { - throw TestException() - } - } - ) + config.router.mount(coroutines) { + it.route(Route.GET, "/test") { + withContext(Dispatchers.IO) { + throw TestException() + } } - ) + } }.exception(TestException::class.java) { _, ctx -> ctx.result("Handled") } @@ -76,16 +65,11 @@ class CoroutinesRoutingTest { fun `should execute non-async handlers in regular thread pool`() = JavalinTest.test( Javalin.create { config -> - config.reactiveRouting( - DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it }, - object : ReactiveRoutes, Context, Unit>() { - override fun routes() = setOf( - reactiveRoute("/test", Route.GET, async = false) { - result(Thread.currentThread().name) - } - ) + config.router.mount(coroutines) { + it.route(Route.GET, "/test", async = false) { + result(Thread.currentThread().name) } - ) + } } ) { _, client -> assertThat(client.get("/test").body?.string()).contains("JettyServerThreadPool") diff --git a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt index 143e549..6817219 100644 --- a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt +++ b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt @@ -1,16 +1,16 @@ package io.javalin.community.routing.examples -import io.javalin.community.routing.coroutines.ReactiveRoutes -import io.javalin.community.routing.Route.GET import io.javalin.Javalin -import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet +import io.javalin.community.routing.Route.GET +import io.javalin.community.routing.coroutines.Coroutines import io.javalin.community.routing.coroutines.ReactiveRoute -import io.javalin.community.routing.coroutines.reactiveRouting +import io.javalin.community.routing.coroutines.ReactiveRoutes +import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet import io.javalin.http.Context -import kotlinx.coroutines.delay import java.lang.Thread.sleep import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger +import kotlinx.coroutines.delay // Some dependencies class ExampleService { @@ -78,7 +78,9 @@ fun main() { // setup Javalin with reactive routing Javalin .create { config -> - config.reactiveRouting(coroutinesServlet, ExampleEndpoint(exampleService)) + config.router.mount(Coroutines(coroutinesServlet)) { + it.routes(ExampleEndpoint(exampleService)) + } } .events { it.serverStopping { coroutinesServlet.prepareShutdown() } diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt index 2a31b75..d799fc3 100644 --- a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt +++ b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt @@ -1,6 +1,15 @@ package io.javalin.community.routing.dsl import io.javalin.community.routing.Route +import io.javalin.community.routing.Route.AFTER +import io.javalin.community.routing.Route.BEFORE +import io.javalin.community.routing.Route.DELETE +import io.javalin.community.routing.Route.GET +import io.javalin.community.routing.Route.HEAD +import io.javalin.community.routing.Route.OPTIONS +import io.javalin.community.routing.Route.PATCH +import io.javalin.community.routing.Route.POST +import io.javalin.community.routing.Route.PUT import io.javalin.community.routing.Routes import io.javalin.http.ExceptionHandler import io.javalin.http.Handler @@ -28,42 +37,23 @@ open class RoutingDslConfiguration, CONTEXT, internal val routes = mutableSetOf() internal val exceptionHandlers = mutableMapOf, DslExceptionHandler>() - fun get(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.GET, path, handler) - - fun post(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.POST, path, handler) - - fun put(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.PUT, path, handler) - - fun delete(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.DELETE, path, handler) - - fun patch(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.PATCH, path, handler) - - fun head(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.HEAD, path, handler) - - fun options(path: String, handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.OPTIONS, path, handler) - - fun before(path: String = "", handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.BEFORE, path, handler) - - fun after(path: String = "", handler: CONTEXT.() -> RESPONSE) = - addRoute(Route.AFTER, path, handler) - - @Suppress("UNCHECKED_CAST") - fun addRoutes(routesToAdd: Collection>) { - routesToAdd.forEach { - routes.add(it as ROUTE) - } + fun get(path: String, handler: CONTEXT.() -> RESPONSE) = route(GET, path, handler) + fun post(path: String, handler: CONTEXT.() -> RESPONSE) = route(POST, path, handler) + fun put(path: String, handler: CONTEXT.() -> RESPONSE) = route(PUT, path, handler) + fun delete(path: String, handler: CONTEXT.() -> RESPONSE) = route(DELETE, path, handler) + fun patch(path: String, handler: CONTEXT.() -> RESPONSE) = route(PATCH, path, handler) + fun head(path: String, handler: CONTEXT.() -> RESPONSE) = route(HEAD, path, handler) + fun options(path: String, handler: CONTEXT.() -> RESPONSE) = route(OPTIONS, path, handler) + fun before(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(BEFORE, path, handler) + fun after(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(AFTER, path, handler) + + fun routes(container: DslContainer) = routes(container.routes()) + fun routes(routesToAdd: Collection>) = routesToAdd.forEach { + @Suppress("UNCHECKED_CAST") + routes.add(it as ROUTE) } - - fun addRoute(method: Route, path: String, handler: CONTEXT.() -> RESPONSE) { - addRoutes( + fun route(method: Route, path: String, handler: CONTEXT.() -> RESPONSE) { + routes( listOf( DefaultDslRoute( method = method, @@ -79,7 +69,6 @@ open class RoutingDslConfiguration, CONTEXT, if (exceptionHandlers.containsKey(type)) { throw IllegalArgumentException("Exception handler for type ${type.simpleName} is already registered") } - exceptionHandlers[type] = handler as DslExceptionHandler } diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt index f1cc789..7b8fad1 100644 --- a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt +++ b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslRouting.kt @@ -37,4 +37,5 @@ open class DslRouting< internalRouter.addHttpExceptionHandler(exceptionClass.java, factory.createExceptionHandler(handler)) } } + } \ No newline at end of file diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/defaults/DefaultContextScope.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/defaults/DefaultContextScope.kt index 46496d2..da0f21c 100644 --- a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/defaults/DefaultContextScope.kt +++ b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/defaults/DefaultContextScope.kt @@ -26,10 +26,7 @@ open class DefaultContextScopeConfiguration< > : RoutingDslConfiguration() { @OptIn(ExperimentalStdlibApi::class) - inline fun RoutingDslConfiguration.method( - method: Route, - crossinline handler: (CONTEXT, PATH) -> RESPONSE - ) { + inline fun method(method: Route, crossinline handler: (CONTEXT, PATH) -> RESPONSE) { val path = PATH::class.findAnnotation() ?.path ?: throw IllegalArgumentException("@Path annotation not found") @@ -72,31 +69,14 @@ open class DefaultContextScopeConfiguration< } } - inline fun RoutingDslConfiguration.get(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.GET, handler) - - inline fun RoutingDslConfiguration.post(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.POST, handler) - - inline fun RoutingDslConfiguration.put(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.PUT, handler) - - inline fun RoutingDslConfiguration.delete(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.DELETE, handler) - - inline fun RoutingDslConfiguration.patch(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.PATCH, handler) - - inline fun RoutingDslConfiguration.head(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.HEAD, handler) - - inline fun RoutingDslConfiguration.options(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.OPTIONS, handler) - - inline fun RoutingDslConfiguration.before(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.BEFORE, handler) - - inline fun RoutingDslConfiguration.after(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = - method(Route.AFTER, handler) + inline fun get(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.GET, handler) + inline fun post(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.POST, handler) + inline fun put(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.PUT, handler) + inline fun delete(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.DELETE, handler) + inline fun patch(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.PATCH, handler) + inline fun head(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.HEAD, handler) + inline fun options(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.OPTIONS, handler) + inline fun before(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.BEFORE, handler) + inline fun after(crossinline handler: CONTEXT.(PATH) -> RESPONSE) = method(Route.AFTER, handler) } diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt index 759181e..73c6698 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/InPlaceRoutingDslTest.kt @@ -21,18 +21,16 @@ class InPlaceRoutingDslTest : TestSpecification() { Javalin.create { config -> config.router.mount(Dsl) { it.before("/before") { header("test", "before") } - after("/after") { header("test", "after") } - - get("/throwing") { throw RuntimeException() } - exception(Exception::class) { header("exception", it::class.java.name) } - - get("/get") { header("test", "get") } - post("/post") { header("test", "post") } - put("/put") { header("test", "put") } - patch("/patch") { header("test", "patch") } - delete("/delete") { header("test", "delete") } - head("/head") { header("test", "head") } - options("/options") { header("test", "options") } + it.after("/after") { header("test", "after") } + it.get("/throwing") { throw RuntimeException() } + it.exception(Exception::class) { header("exception", it::class.java.name) } + it.get("/get") { header("test", "get") } + it.post("/post") { header("test", "post") } + it.put("/put") { header("test", "put") } + it.patch("/patch") { header("test", "patch") } + it.delete("/delete") { header("test", "delete") } + it.head("/head") { header("test", "head") } + it.options("/options") { header("test", "options") } } }, defaultConfig @@ -77,8 +75,8 @@ class InPlaceRoutingDslTest : TestSpecification() { fun `should properly handle class based route`() = JavalinTest.test( // given: a javalin app with routes defined using the dsl Javalin.create { config -> - config.routing { - get { result("Panda") } + config.router.mount(Dsl) { + it.get { result("Panda") } } }, defaultConfig @@ -97,8 +95,8 @@ class InPlaceRoutingDslTest : TestSpecification() { fun `should properly handle class based route with parameter`() = JavalinTest.test( // given: a javalin app with routes defined using the dsl Javalin.create { config -> - config.routing { - get { result(it.name) } + config.router.mount(Dsl) { + it.get { result(it.name) } } }, defaultConfig @@ -117,9 +115,9 @@ class InPlaceRoutingDslTest : TestSpecification() { // given: a javalin app with routes defined using the dsl val app = ThrowingCallable { Javalin.create { - it.routing { + it.router.mount(Dsl) { // when: a route is defined with path without @Path annotation - get { } + it.get { } } } } @@ -138,9 +136,9 @@ class InPlaceRoutingDslTest : TestSpecification() { // given: a javalin app with routes defined using the dsl val app = ThrowingCallable { Javalin.create { - it.routing { + it.router.mount(Dsl) { // when: a route is defined with invalid @Path annotation - get { } + it.get { } } } } @@ -158,16 +156,16 @@ class InPlaceRoutingDslTest : TestSpecification() { fun `should properly map all reified variants`() = JavalinTest.test( // given: a javalin app with routes defined by all available reified methods Javalin.create { config -> - config.routing { - before { result("Before ") } - get { result(result() + "GET") } - put { result(result() + "PUT") } - post { result(result() + "POST") } - patch { result(result() + "PATCH") } - delete { result(result() + "DELETE") } - head { result(result() + "HEAD") } - options { result(result() + "OPTIONS") } - after { + config.router.mount(Dsl) { + it.before { result("Before ") } + it.get { result(result() + "GET") } + it.put { result(result() + "PUT") } + it.post { result(result() + "POST") } + it.patch { result(result() + "PATCH") } + it.delete { result(result() + "DELETE") } + it.head { result(result() + "HEAD") } + it.options { result(result() + "OPTIONS") } + it.after { result(result() + " After") header("test", result()!!) } @@ -176,7 +174,8 @@ class InPlaceRoutingDslTest : TestSpecification() { defaultConfig ) { _, client -> // when: a request is made to the http route - Route.values() + Route.entries + .asSequence() .filter { it.isHttpMethod } .map { it to request(it.name, "${client.origin}/path").asString() } .forEach { (method, response) -> diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt index d08e884..b39da8c 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt @@ -2,6 +2,7 @@ package io.javalin.community.routing.dsl import io.javalin.Javalin import io.javalin.community.routing.Route.GET +import io.javalin.community.routing.dsl.DslRouting.Companion.Dsl import io.javalin.community.routing.dsl.defaults.DefaultRoutes import io.javalin.community.routing.dsl.specification.TestSpecification import io.javalin.testtools.JavalinTest @@ -27,7 +28,9 @@ class PropertyRoutingDslTest : TestSpecification() { fun `should register valid route in javalin instance`() = JavalinTest.test( Javalin.create { - it.routing(ValidTestEndpoints()) + it.router.mount(Dsl) { + it.routes(ValidTestEndpoints()) + } }, defaultConfig ) { _, client -> diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt index 3c8a578..db3ae8c 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt @@ -1,15 +1,15 @@ package io.javalin.community.routing.dsl.examples import io.javalin.Javalin -import io.javalin.community.routing.dsl.defaults.DefaultContextScopeConfiguration -import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultScope import io.javalin.community.routing.dsl.DslExceptionHandler import io.javalin.community.routing.dsl.DslRoute -import io.javalin.community.routing.dsl.defaults.Path +import io.javalin.community.routing.dsl.DslRouting import io.javalin.community.routing.dsl.RoutingDslFactory -import io.javalin.community.routing.dsl.examples.CustomDsl.CustomScope +import io.javalin.community.routing.dsl.defaults.DefaultContextScopeConfiguration +import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultScope +import io.javalin.community.routing.dsl.defaults.Path import io.javalin.community.routing.dsl.examples.CustomDsl.CustomRoutingConfiguration -import io.javalin.community.routing.dsl.routing +import io.javalin.community.routing.dsl.examples.CustomDsl.CustomScope import io.javalin.http.Context import io.javalin.http.ExceptionHandler import io.javalin.http.Handler @@ -44,20 +44,20 @@ data class PandaPath(val age: Int) fun main() { Javalin.create { config -> - config.routing(CustomDsl) { - before { + config.router.mount(DslRouting(CustomDsl)) { + it.before { // `endpointHandlerPath` comes from Context class result("Called endpoint: ${matchedPath()}") } - get("/") { + it.get("/") { // `helloWorld` comes from CustomScope class result(helloWorld()) } - get { path -> + it.get { path -> // support for type-safe paths result(path.age.toString()) } - exception(Exception::class) { anyException -> + it.exception(Exception::class) { anyException -> // support for exception handlers result(anyException.message ?: "Unknown error") } From 1a1dc68493b161292cdea88e912f65cdd94f7f75 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Sat, 11 Nov 2023 15:08:47 +0100 Subject: [PATCH 3/5] GH-40 Rename reactive plugin to `Coroutines` routing component --- .../routing/coroutines/Coroutines.kt | 27 +++++++++++ .../routing/coroutines/CoroutinesRouting.kt | 27 +++++++++++ .../coroutines/ReactiveRoutingPlugin.kt | 46 ------------------- .../{ReactiveRoute.kt => SuspendedRoute.kt} | 8 ++-- .../coroutines/servlet/CoroutinesServlet.kt | 8 ++-- .../routing/CoroutinesRoutingTest.kt | 1 + ...Example.kt => CoroutinesRoutingExample.kt} | 14 +++--- 7 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/Coroutines.kt create mode 100644 routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/CoroutinesRouting.kt delete mode 100644 routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt rename routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/{ReactiveRoute.kt => SuspendedRoute.kt} (55%) rename routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/{ReactiveRoutingExample.kt => CoroutinesRoutingExample.kt} (84%) diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/Coroutines.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/Coroutines.kt new file mode 100644 index 0000000..b89eada --- /dev/null +++ b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/Coroutines.kt @@ -0,0 +1,27 @@ +package io.javalin.community.routing.coroutines + +import io.javalin.community.routing.coroutines.servlet.CoroutinesServlet +import io.javalin.community.routing.sortRoutes +import io.javalin.config.JavalinConfig +import io.javalin.http.Handler +import io.javalin.http.HandlerType +import io.javalin.router.InternalRouter +import io.javalin.router.RoutingApiInitializer +import java.util.function.Consumer + +class Coroutines, CONTEXT, RESPONSE : Any>( + private val servlet: CoroutinesServlet, +) : RoutingApiInitializer> { + + override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer>) { + val coroutinesRouting = CoroutinesRouting() + setup.accept(coroutinesRouting) + + coroutinesRouting + .routes + .sortRoutes() + .map { it to Handler { ctx -> servlet.handle(ctx, it) } } + .forEach { (route, handler) -> internalRouter.addHttpHandler(HandlerType.valueOf(route.method.toString()), route.path, handler) } + } + +} \ No newline at end of file diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/CoroutinesRouting.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/CoroutinesRouting.kt new file mode 100644 index 0000000..9cda7ac --- /dev/null +++ b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/CoroutinesRouting.kt @@ -0,0 +1,27 @@ +package io.javalin.community.routing.coroutines + +import io.javalin.community.routing.Route + +class CoroutinesRouting, CONTEXT, RESPONSE : Any> { + + internal val routes = mutableListOf() + + fun route(route: ROUTE) { + routes.add(route) + } + + fun routes(exampleEndpoint: SuspendedRoutes): CoroutinesRouting { + exampleEndpoint.routes().forEach { route(it) } + return this + } + +} + +fun CoroutinesRouting, CONTEXT, RESPONSE>.route( + method: Route, + path: String, + async: Boolean = true, + handler: suspend CONTEXT.() -> RESPONSE +) { + routes.add(SuspendedRoute(path, method, async, handler)) +} diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt deleted file mode 100644 index 8e78d9d..0000000 --- a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoutingPlugin.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.javalin.community.routing.coroutines - -import io.javalin.community.routing.Route -import io.javalin.community.routing.coroutines.servlet.CoroutinesServlet -import io.javalin.community.routing.sortRoutes -import io.javalin.config.JavalinConfig -import io.javalin.http.Handler -import io.javalin.http.HandlerType -import io.javalin.router.InternalRouter -import io.javalin.router.RoutingApiInitializer -import java.util.function.Consumer - -class CoroutinesRouting { - internal val routes = mutableListOf>() - - fun route(route: ReactiveRoute) { - routes.add(route) - } - - fun route(method: Route, path: String, async: Boolean = true, handler: suspend CONTEXT.() -> RESPONSE) { - routes.add(ReactiveRoute(path, method, async, handler)) - } - - fun routes(exampleEndpoint: ReactiveRoutes, CONTEXT, RESPONSE>): CoroutinesRouting { - exampleEndpoint.routes().forEach { route(it) } - return this - } - -} - -class Coroutines, CONTEXT, RESPONSE : Any>( - private val servlet: CoroutinesServlet, -) : RoutingApiInitializer> { - - override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer>) { - val coroutinesRouting = CoroutinesRouting() - setup.accept(coroutinesRouting) - - coroutinesRouting - .routes - .sortRoutes() - .map { it to Handler { ctx -> servlet.handle(ctx, it) } } - .forEach { (route, handler) -> internalRouter.addHttpHandler(HandlerType.valueOf(route.method.toString()), route.path, handler) } - } - -} \ No newline at end of file diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoute.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/SuspendedRoute.kt similarity index 55% rename from routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoute.kt rename to routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/SuspendedRoute.kt index 170a2d6..4611da6 100644 --- a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/ReactiveRoute.kt +++ b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/SuspendedRoute.kt @@ -4,17 +4,17 @@ import io.javalin.community.routing.Route import io.javalin.community.routing.Routed import io.javalin.community.routing.Routes -class ReactiveRoute( +class SuspendedRoute( override val path: String, val method: Route, val async: Boolean = true, val handler: suspend CONTEXT.() -> RESPONSE ) : Routed -abstract class ReactiveRoutes, CONTEXT, RESPONSE : Any> : Routes { +abstract class SuspendedRoutes, CONTEXT, RESPONSE : Any> : Routes { - fun reactiveRoute(path: String, method: Route, async: Boolean = true, handler: suspend CONTEXT.() -> RESPONSE): ReactiveRoute = - ReactiveRoute( + fun route(path: String, method: Route, async: Boolean = true, handler: suspend CONTEXT.() -> RESPONSE): SuspendedRoute = + SuspendedRoute( path = path, method = method, async = async, diff --git a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/servlet/CoroutinesServlet.kt b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/servlet/CoroutinesServlet.kt index 38372ef..a8380f8 100644 --- a/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/servlet/CoroutinesServlet.kt +++ b/routing-coroutines/src/main/kotlin/io/javalin/community/routing/coroutines/servlet/CoroutinesServlet.kt @@ -1,6 +1,6 @@ package io.javalin.community.routing.coroutines.servlet -import io.javalin.community.routing.coroutines.ReactiveRoute +import io.javalin.community.routing.coroutines.SuspendedRoute import io.javalin.http.Context import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName @@ -16,8 +16,8 @@ open class CoroutinesServlet( name: String = "javalin-reactive-routing", private val coroutinesEnabled: Boolean = true, protected val dispatcher: CoroutineDispatcher, - protected val syncHandler: suspend (Context, ReactiveRoute) -> RESPONSE, - protected val asyncHandler: suspend (Context, ReactiveRoute, CompletableFuture) -> RESPONSE, + protected val syncHandler: suspend (Context, SuspendedRoute) -> RESPONSE, + protected val asyncHandler: suspend (Context, SuspendedRoute, CompletableFuture) -> RESPONSE, private val responseConsumer: (suspend (Context, RESPONSE) -> Unit)? = null, uncaughtExceptionConsumer: (CoroutineNameRepresentation, Throwable) -> Unit, ) { @@ -28,7 +28,7 @@ open class CoroutinesServlet( private val id = AtomicLong() private val finished = AtomicLong() - fun handle(ctx: Context, route: ReactiveRoute) { + fun handle(ctx: Context, route: SuspendedRoute) { id.incrementAndGet() when { diff --git a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt index 9b2f704..eff5b0b 100644 --- a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt +++ b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/CoroutinesRoutingTest.kt @@ -2,6 +2,7 @@ package io.javalin.community.routing import io.javalin.Javalin import io.javalin.community.routing.coroutines.Coroutines +import io.javalin.community.routing.coroutines.route import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet import io.javalin.testtools.JavalinTest import java.util.concurrent.Executors diff --git a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/CoroutinesRoutingExample.kt similarity index 84% rename from routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt rename to routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/CoroutinesRoutingExample.kt index 6817219..e31c910 100644 --- a/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/ReactiveRoutingExample.kt +++ b/routing-coroutines/src/test/kotlin/io/javalin/community/routing/examples/CoroutinesRoutingExample.kt @@ -3,8 +3,8 @@ package io.javalin.community.routing.examples import io.javalin.Javalin import io.javalin.community.routing.Route.GET import io.javalin.community.routing.coroutines.Coroutines -import io.javalin.community.routing.coroutines.ReactiveRoute -import io.javalin.community.routing.coroutines.ReactiveRoutes +import io.javalin.community.routing.coroutines.SuspendedRoute +import io.javalin.community.routing.coroutines.SuspendedRoutes import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet import io.javalin.http.Context import java.lang.Thread.sleep @@ -29,30 +29,30 @@ class CustomScope(val ctx: Context) : Context by ctx { } // Utility class representing group of reactive routes -abstract class ExampleRoutes : ReactiveRoutes, CustomScope, Unit>() +abstract class ExampleRoutes : SuspendedRoutes, CustomScope, Unit>() // Endpoint (domain router) class ExampleEndpoint(private val exampleService: ExampleService) : ExampleRoutes() { // you can use suspend functions in coroutines context // and as long as they're truly reactive, they won't freeze it - private val nonBlockingAsync = reactiveRoute("/async", GET) { + private val nonBlockingAsync = route("/async", GET) { result(nonBlockingDelay("Non-blocking Async")) } // using truly-blocking functions in coroutines context will freeze thread anyway - private val blockingAsync = reactiveRoute("/async-blocking", GET) { + private val blockingAsync = route("/async-blocking", GET) { result(blockingDelay("Blocking Async")) } // you can also use async = false, to run coroutine in sync context (runBlocking) - private val sync = reactiveRoute("/sync", GET, async = false) { + private val sync = route("/sync", GET, async = false) { result(blockingDelay("Sync")) } // you can visit /stream in browser and see that despite single-threaded executor, // you can make multiple concurrent requests and each request is handled - private val stream = reactiveRoute("/stream", GET) { + private val stream = route("/stream", GET) { val id = exampleService.streamId.incrementAndGet() while (true) { From b610d59e18f36e01597ca5e8837468e92426c2f6 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Sat, 11 Nov 2023 16:58:36 +0100 Subject: [PATCH 4/5] GH-40 Convert annotated routing plugin into routing API component --- ...edRoutingPlugin.kt => AnnotatedRouting.kt} | 80 ++-- .../annotations/ReflectiveEndpointLoader.kt | 6 +- .../annotations/AnnotatedRoutingTest.kt | 358 ++++++++++-------- .../example/AnnotatedRoutingExample.java | 16 +- .../javalin/community/routing/RoutingApi.kt | 2 - .../community/routing/JavalinRoutesTest.kt | 2 +- .../community/routing/dsl/DslFactory.kt | 11 +- .../routing/dsl/PropertyRoutingDslTest.kt | 8 +- .../dsl/examples/PropertyDslExample.kt | 25 +- 9 files changed, 269 insertions(+), 239 deletions(-) rename routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/{AnnotatedRoutingPlugin.kt => AnnotatedRouting.kt} (55%) diff --git a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRoutingPlugin.kt b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt similarity index 55% rename from routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRoutingPlugin.kt rename to routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt index 8706a01..f57a6d8 100644 --- a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRoutingPlugin.kt +++ b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/AnnotatedRouting.kt @@ -1,6 +1,5 @@ package io.javalin.community.routing.annotations -import io.javalin.Javalin import io.javalin.community.routing.Route import io.javalin.community.routing.dsl.DslRoute import io.javalin.community.routing.registerRoute @@ -9,14 +8,16 @@ import io.javalin.config.JavalinConfig import io.javalin.http.BadRequestResponse import io.javalin.http.Context import io.javalin.http.Handler -import io.javalin.plugin.Plugin -import java.lang.UnsupportedOperationException +import io.javalin.router.InternalRouter +import io.javalin.router.RoutingApiInitializer +import java.util.function.Consumer fun interface HandlerResultConsumer { fun handle(ctx: Context, value: T) } -class AnnotatedRoutingPluginConfiguration { +class AnnotatedRoutingConfig { + var apiVersionHeader: String = "X-API-Version" var resultHandlers: MutableMap, HandlerResultConsumer<*>> = mutableMapOf( String::class.java to HandlerResultConsumer { ctx, value -> value?.also { ctx.result(it) } }, @@ -24,53 +25,73 @@ class AnnotatedRoutingPluginConfiguration { Void::class.java to HandlerResultConsumer { _, _ -> }, Void.TYPE to HandlerResultConsumer { _, _ -> }, ) + internal val registeredRoutes = mutableListOf() - fun registerResultHandler(type: Class, handler: HandlerResultConsumer): AnnotatedRoutingPluginConfiguration = also { + fun registerResultHandler(type: Class, handler: HandlerResultConsumer): AnnotatedRoutingConfig = also { this.resultHandlers[type] = handler } - inline fun registerResultHandler(handler: HandlerResultConsumer): AnnotatedRoutingPluginConfiguration = + inline fun registerResultHandler(handler: HandlerResultConsumer): AnnotatedRoutingConfig = registerResultHandler(T::class.java, handler) + fun registerEndpoints(vararg endpoints: Any) { + registeredRoutes.addAll(endpoints) + } + } -class AnnotatedRoutingPlugin @JvmOverloads constructor( - private val configuration: AnnotatedRoutingPluginConfiguration = AnnotatedRoutingPluginConfiguration() -) : Plugin { +object AnnotatedRouting : RoutingApiInitializer { - private val registeredRoutes = mutableListOf() - private val registeredExceptionHandlers = mutableListOf() - private val reflectiveEndpointLoader = ReflectiveEndpointLoader(configuration.resultHandlers) + @JvmField val Annotated = this private data class RouteIdentifier(val route: Route, val path: String) - override fun apply(app: Javalin) { + override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer) { + val configuration = AnnotatedRoutingConfig() + setup.accept(configuration) + + val loader = ReflectiveEndpointLoader(configuration.resultHandlers) + val registeredRoutes = mutableListOf() + val registeredExceptionHandlers = mutableListOf() + + configuration.registeredRoutes.forEach { + val detectedRoutes = loader.loadRoutesFromEndpoint(it) + registeredRoutes.addAll(detectedRoutes) + + val detectedExceptionHandlers = loader.loadExceptionHandlers(it) + registeredExceptionHandlers.addAll(detectedExceptionHandlers) + } + registeredRoutes .sortRoutes() .groupBy { RouteIdentifier(it.method, it.path) } .map { (id, routes) -> id to when (routes.size) { 1 -> routes.first().let { Handler { ctx -> it.handler(ctx) } } - else -> createVersionedRoute(id, routes) + else -> createVersionedRoute( + apiVersionHeader = configuration.apiVersionHeader, + id = id, + routes = routes + ) } } .forEach { (id, handler) -> - app.registerRoute(id.route, id.path, handler) + internalRouter.registerRoute(id.route, id.path, handler) } registeredExceptionHandlers.forEach { annotatedException -> - app.exception(annotatedException.type.java) { exception, ctx -> + internalRouter.addHttpExceptionHandler(annotatedException.type.java) { exception, ctx -> annotatedException.handler.invoke(ctx, exception) } } } - private fun createVersionedRoute(id: RouteIdentifier, routes: List>): Handler { + private fun createVersionedRoute(apiVersionHeader: String, id: RouteIdentifier, routes: List>): Handler { val versions = routes.map { it.version } check(versions.size == versions.toSet().size) { "Duplicated version found for the same route: ${id.route} ${id.path} (versions: $versions)" } return Handler { ctx -> - val version = ctx.header(configuration.apiVersionHeader) + val version = ctx.header(apiVersionHeader) routes.firstOrNull { it.version == version } ?.handler @@ -79,25 +100,4 @@ class AnnotatedRoutingPlugin @JvmOverloads constructor( } } - fun registerPrecompiledEndpoints() { - throw UnsupportedOperationException("Not implemented") - } - - fun registerEndpoints(vararg endpoints: Any) { - val detectedRoutes = endpoints.flatMap { reflectiveEndpointLoader.loadRoutesFromEndpoint(it) } - registeredRoutes.addAll(detectedRoutes) - - val detectedExceptionHandlers = endpoints.flatMap { reflectiveEndpointLoader.loadExceptionHandlers(it) } - registeredExceptionHandlers.addAll(detectedExceptionHandlers) - } - -} - -fun JavalinConfig.registerAnnotatedEndpoints(configuration: AnnotatedRoutingPluginConfiguration, vararg endpoints: Any) { - val plugin = AnnotatedRoutingPlugin(configuration) - plugin.registerEndpoints(*endpoints) - this.plugins.register(plugin) -} - -fun JavalinConfig.registerAnnotatedEndpoints(vararg endpoints: Any) = - registerAnnotatedEndpoints(AnnotatedRoutingPluginConfiguration(), *endpoints) \ No newline at end of file +} \ No newline at end of file diff --git a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt index 1c54afe..18b8f2f 100644 --- a/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt +++ b/routing-annotations/routing-annotated/src/main/kotlin/io/javalin/community/routing/annotations/ReflectiveEndpointLoader.kt @@ -5,7 +5,7 @@ import io.javalin.community.routing.dsl.DefaultDslException import io.javalin.community.routing.dsl.DefaultDslRoute import io.javalin.http.Context import io.javalin.http.HttpStatus -import io.javalin.validation.Validator +import io.javalin.validation.validation import java.lang.reflect.Method import java.lang.reflect.Parameter import kotlin.reflect.KClass @@ -141,7 +141,7 @@ internal class ReflectiveEndpointLoader( status: Status?, ctx: Context, resultHandler: HandlerResultConsumer - ): Any? = + ): Any = try { val result = method.invoke(instance, *arguments) status @@ -198,7 +198,7 @@ internal class ReflectiveEndpointLoader( getAnnotation(Cookie::class.java) .value .ifEmpty { name } - .let { Validator.create(type, ctx.cookie(it), it) } + .let { ctx.validation().validator(it, type, ctx.cookie(it)) } .get() } isAnnotationPresent(Body::class.java) -> { ctx, _ -> diff --git a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt index 5e96aa1..5f19b2b 100644 --- a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt +++ b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/AnnotatedRoutingTest.kt @@ -2,6 +2,7 @@ package io.javalin.community.routing.annotations import io.javalin.Javalin import io.javalin.community.routing.Route +import io.javalin.community.routing.annotations.AnnotatedRouting.Annotated import io.javalin.http.Context import io.javalin.http.HandlerType import io.javalin.http.HttpStatus @@ -17,28 +18,32 @@ class AnnotatedRoutingTest { @Test fun `should sanitize repeated path separators`() { - val app = Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints("/test/") - object { - @Get("/with") - fun get(ctx: Context) {} - }, - @Endpoints("test") - object { - @Get("without") - fun get(ctx: Context) {} - } - ) + val app = Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints("/test/") + object { + @Get("/with") + fun get(ctx: Context) { + } + }, + @Endpoints("test") + object { + @Get("without") + fun get(ctx: Context) { + } + } + ) + } } - val matcher = app.javalinServlet().matcher + val matcher = app.unsafeConfig().pvt.internalRouter - assertThat(matcher.findEntries(HandlerType.GET, "/test/with")) + assertThat(matcher.findHttpHandlerEntries(HandlerType.GET, "/test/with")) .hasSize(1) .allMatch { it.path == "/test/with" } - assertThat(matcher.findEntries(HandlerType.GET, "/test/without")) + assertThat(matcher.findHttpHandlerEntries(HandlerType.GET, "/test/without")) .hasSize(1) .allMatch { it.path == "/test/without" } } @@ -46,13 +51,14 @@ class AnnotatedRoutingTest { @Test fun `should throw exception if route has unsupported parameter in signature`() { assertThatThrownBy { - AnnotatedRoutingPlugin().registerEndpoints( - @Endpoints - object { - @Get("/test") - fun test(ctx: Context, unsupported: String) {} - } - ) + Javalin.create().unsafeConfig().router.mount(Annotated) { + it.registerEndpoints( + @Endpoints + object { + @Get("/test") fun test(ctx: Context, unsupported: String) {} + } + ) + } } .isExactlyInstanceOf(IllegalArgumentException::class.java) .hasMessageContaining("Unsupported parameter type") @@ -61,33 +67,28 @@ class AnnotatedRoutingTest { @Test fun `should properly register all annotated endpoints`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints("/test") - object { - @Before - fun beforeEach(ctx: Context) { ctx.header("before", "true") } - @After - fun afterEach(ctx: Context) { ctx.header("after", "true") } - @Get("/get") - fun testGet(ctx: Context) { ctx.header("get", "true") } - @Post("/post") - fun testPost(ctx: Context) { ctx.header("post", "true") } - @Put("/put") - fun testPut(ctx: Context) { ctx.header("put", "true") } - @Delete("/delete") - fun testDelete(ctx: Context) { ctx.header("delete", "true") } - @Patch("/patch") - fun testPatch(ctx: Context) { ctx.header("patch", "true") } - @Head("/head") - fun testHead(ctx: Context) { ctx.header("head", "true") } - @Options("/options") - fun testOptions(ctx: Context) { ctx.header("options", "true") } - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints("/test") + object { + // formatter:off + @Before fun beforeEach(ctx: Context) { ctx.header("before", "true") } + @After fun afterEach(ctx: Context) { ctx.header("after", "true") } + @Get("/get") fun testGet(ctx: Context) { ctx.header("get", "true") } + @Post("/post") fun testPost(ctx: Context) { ctx.header("post", "true") } + @Put("/put") fun testPut(ctx: Context) { ctx.header("put", "true") } + @Delete("/delete") fun testDelete(ctx: Context) { ctx.header("delete", "true") } + @Patch("/patch") fun testPatch(ctx: Context) { ctx.header("patch", "true") } + @Head("/head") fun testHead(ctx: Context) { ctx.header("head", "true") } + @Options("/options") fun testOptions(ctx: Context) { ctx.header("options", "true") } + // formatter:on + } + ) + } } ) { _, client -> - Route.values() + Route.entries .filter { it.isHttpMethod } .forEach { val response = request(it.name, "${client.origin}/test/${it.name.lowercase()}").asEmpty() @@ -100,16 +101,18 @@ class AnnotatedRoutingTest { @Test fun `should run async method in async context`() { JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints - object { - @Before("/test") - fun before(ctx: Context) { ctx.header("sync", Thread.currentThread().name) } - @Get("/test", async = true) - fun test(ctx: Context) { ctx.header("async", Thread.currentThread().name) } - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints + object { + // formatter:off + @Before("/test") fun before(ctx: Context) { ctx.header("sync", Thread.currentThread().name) } + @Get("/test", async = true) fun test(ctx: Context) { ctx.header("async", Thread.currentThread().name) } + // formatter:on + } + ) + } } ) { _, client -> val response = Unirest.get("${client.origin}/test").asEmpty() @@ -127,27 +130,29 @@ class AnnotatedRoutingTest { @Test fun `should inject all supported properties from context`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints - object { - @Post("/test/{param}") - fun test( - ctx: Context, - @Param param: Int, - @Header header: Int, - @Query query: Int, - @Cookie cookie: Int, - @Body body: Int, - ) { - ctx.header("param", param.toString()) - ctx.header("header", header.toString()) - ctx.header("query", query.toString()) - ctx.header("cookie", cookie.toString()) - ctx.header("body", body.toString()) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints + object { + @Post("/test/{param}") + fun test( + ctx: Context, + @Param param: Int, + @Header header: Int, + @Query query: Int, + @Cookie cookie: Int, + @Body body: Int, + ) { + ctx.header("param", param.toString()) + ctx.header("header", header.toString()) + ctx.header("query", query.toString()) + ctx.header("cookie", cookie.toString()) + ctx.header("body", body.toString()) + } } - } - ) + ) + } } ) { _, client -> val responseHeaders = Unirest.post("${client.origin}/test/1") @@ -168,14 +173,16 @@ class AnnotatedRoutingTest { @Test fun `should respond with bad request if property cannot be mapped into parameter`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints - object { - @Get("/test/{param}") - fun test(@Param param: Int) = Unit - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints + object { + @Get("/test/{param}") + fun test(@Param param: Int) = Unit + } + ) + } } ) { _, client -> val response = Unirest.get("${client.origin}/test/abc").asString() @@ -185,13 +192,15 @@ class AnnotatedRoutingTest { @Test fun `should skip methods in endpoint class that are not annotated`() { assertDoesNotThrow { - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints - object { - fun regularMethod() {} - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints + object { + fun regularMethod() {} + } + ) + } } } } @@ -200,20 +209,26 @@ class AnnotatedRoutingTest { fun `should throw if two routes with the same versions are found`() { assertThatThrownBy { Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints("/api/users") - object { - @Version("1") - @Get - fun findAll(ctx: Context) { ctx.result("Panda") } - }, - @Endpoints("/api/users") - object { - @Version("1") - @Get - fun test(ctx: Context) { ctx.result("Red Panda") } - } - ) + it.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints("/api/users") + object { + @Version("1") + @Get + fun findAll(ctx: Context) { + ctx.result("Panda") + } + }, + @Endpoints("/api/users") + object { + @Version("1") + @Get + fun test(ctx: Context) { + ctx.result("Red Panda") + } + } + ) + } } } .isInstanceOf(IllegalStateException::class.java) @@ -223,21 +238,27 @@ class AnnotatedRoutingTest { @Test fun `should properly serve versioned routes`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - @Endpoints("/api/users") - object { - @Version("1") - @Get - fun findAll(ctx: Context) { ctx.result("Panda") } - }, - @Endpoints("/api/users") - object { - @Version("2") - @Get - fun test(ctx: Context) { ctx.result("Red Panda") } - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + @Endpoints("/api/users") + object { + @Version("1") + @Get + fun findAll(ctx: Context) { + ctx.result("Panda") + } + }, + @Endpoints("/api/users") + object { + @Version("2") + @Get + fun test(ctx: Context) { + ctx.result("Red Panda") + } + } + ) + } } ) { _, client -> val v1 = Unirest.get("${client.origin}/api/users").header("X-API-Version", "1").asString().body @@ -253,14 +274,17 @@ class AnnotatedRoutingTest { fun `should properly handle exceptions`() = JavalinTest.test( Javalin.create { - it.registerAnnotatedEndpoints( - object { + it.router.mount(Annotated) { cfg -> + cfg.registerEndpoints(object { @Get("/throwing") fun throwing(ctx: Context): Nothing = throw IllegalStateException("This is a test") + @ExceptionHandler(IllegalStateException::class) - fun handleException(ctx: Context, e: IllegalStateException) { ctx.result(e::class.java.name) } - } - ) + fun handleException(ctx: Context, e: IllegalStateException) { + ctx.result(e::class.java.name) + } + }) + } } ) { _, client -> assertThat(Unirest.get("${client.origin}/throwing").asString().body).isEqualTo("java.lang.IllegalStateException") @@ -270,12 +294,14 @@ class AnnotatedRoutingTest { fun `should throw for unsupported return types`() { assertThatThrownBy { Javalin.create { - it.registerAnnotatedEndpoints( - object { - @Get("/unsupported") - fun unsupported(ctx: Context): Int = 1 - } - ) + it.router.mount(Annotated) { cfg -> + cfg.registerEndpoints( + object { + @Get("/unsupported") + fun unsupported(ctx: Context): Int = 1 + } + ) + } } } .isInstanceOf(IllegalStateException::class.java) @@ -289,19 +315,22 @@ class AnnotatedRoutingTest { @Test fun `should properly handle inheritance`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - configuration = AnnotatedRoutingPluginConfiguration() - .registerResultHandler { ctx, _ -> ctx.result("Animal") } - .registerResultHandler { ctx, _ -> ctx.result("RedPanda") } - .registerResultHandler { ctx, _ -> ctx.result("Panda") }, - object { - @Get("/base") - fun base(ctx: Context): Animal = Animal() - @Get("/closest") - fun closest(ctx: Context): RedPanda = RedPanda() - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerResultHandler { ctx, _ -> ctx.result("Animal") } + it.registerResultHandler { ctx, _ -> ctx.result("RedPanda") } + it.registerResultHandler { ctx, _ -> ctx.result("Panda") } + + it.registerEndpoints( + object { + @Get("/base") + fun base(ctx: Context): Animal = Animal() + + @Get("/closest") + fun closest(ctx: Context): RedPanda = RedPanda() + } + ) + } } ) { _, client -> assertThat(Unirest.get("${client.origin}/base").asString().body).isEqualTo("Animal") @@ -314,33 +343,34 @@ class AnnotatedRoutingTest { @Test fun `should throw if result handler matched multiple classes`() { assertThatThrownBy { - Javalin.create { - it.registerAnnotatedEndpoints( - configuration = AnnotatedRoutingPluginConfiguration() - .registerResultHandler { ctx, _ -> ctx.result("Panda") } - .registerResultHandler { ctx, _ -> ctx.result("Heavy") }, - object { + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerResultHandler { ctx, _ -> ctx.result("Panda") } + it.registerResultHandler { ctx, _ -> ctx.result("Heavy") } + it.registerEndpoints(object { @Get("/test") fun test(ctx: Context): GiantPanda = GiantPanda() - } - ) + }) + } } } - .isInstanceOf(IllegalStateException::class.java) - .hasMessageContaining("Unable to determine handler for type class") + .isInstanceOf(IllegalStateException::class.java) + .hasMessageContaining("Unable to determine handler for type class") } @Test fun `should use status code from annotation`() = JavalinTest.test( - Javalin.create { - it.registerAnnotatedEndpoints( - object { - @Get("/test") - @Status(success = HttpStatus.IM_A_TEAPOT) - fun test(ctx: Context): String = "abc" - } - ) + Javalin.create { cfg -> + cfg.router.mount(Annotated) { + it.registerEndpoints( + object { + @Get("/test") + @Status(success = HttpStatus.IM_A_TEAPOT) + fun test(ctx: Context): String = "abc" + } + ) + } } ) { _, client -> assertThat(Unirest.get("${client.origin}/test").asString().status).isEqualTo(HttpStatus.IM_A_TEAPOT.code) diff --git a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/example/AnnotatedRoutingExample.java b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/example/AnnotatedRoutingExample.java index 8421a30..017d5b6 100644 --- a/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/example/AnnotatedRoutingExample.java +++ b/routing-annotations/routing-annotated/src/test/java/io/javalin/community/routing/annotations/example/AnnotatedRoutingExample.java @@ -1,7 +1,6 @@ package io.javalin.community.routing.annotations.example; import io.javalin.Javalin; -import io.javalin.community.routing.annotations.AnnotatedRoutingPlugin; import io.javalin.community.routing.annotations.Before; import io.javalin.community.routing.annotations.Body; import io.javalin.community.routing.annotations.Endpoints; @@ -16,13 +15,14 @@ import io.javalin.openapi.OpenApiContent; import io.javalin.openapi.OpenApiParam; import io.javalin.openapi.OpenApiResponse; -import jakarta.annotation.Nullable; import kong.unirest.HttpResponse; import kong.unirest.Unirest; +import org.jetbrains.annotations.Nullable; import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import static io.javalin.community.routing.annotations.AnnotatedRouting.Annotated; import static io.javalin.http.Header.AUTHORIZATION; import static io.javalin.openapi.HttpMethod.GET; @@ -106,15 +106,15 @@ void defaultExceptionHandler(Exception e, Context ctx) { } public static void main(String[] args) { - Javalin.create(config -> { + Javalin.createAndStart(config -> { // prepare dependencies - ExampleEndpoints exampleEndpoints = new ExampleEndpoints(new ExampleService()); + var exampleService = new ExampleService(); // register endpoints - AnnotatedRoutingPlugin routingPlugin = new AnnotatedRoutingPlugin(); - routingPlugin.registerEndpoints(exampleEndpoints); - config.plugins.register(routingPlugin); - }).start(7000); + config.router.mount(Annotated, routing -> { + routing.registerEndpoints(new ExampleEndpoints(exampleService)); + }); + }); // test request to `saveExample` endpoint HttpResponse saved = Unirest.post("http://localhost:7000/api/hello") diff --git a/routing-core/src/main/kotlin/io/javalin/community/routing/RoutingApi.kt b/routing-core/src/main/kotlin/io/javalin/community/routing/RoutingApi.kt index f1833e4..32fb568 100644 --- a/routing-core/src/main/kotlin/io/javalin/community/routing/RoutingApi.kt +++ b/routing-core/src/main/kotlin/io/javalin/community/routing/RoutingApi.kt @@ -21,7 +21,5 @@ interface Routed { } interface Routes { - fun routes(): Collection = emptySet() - } \ No newline at end of file diff --git a/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt b/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt index 050e411..3f64f88 100644 --- a/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt +++ b/routing-core/src/test/kotlin/io/javalin/community/routing/JavalinRoutesTest.kt @@ -19,7 +19,7 @@ class JavalinRoutesTest { // when: routes are registered val app = Javalin.create() - .apply { routes.forEach { route -> registerRoute(route.first, "/", route.second) } } + app.unsafeConfig().pvt.internalRouter.apply { routes.forEach { route -> registerRoute(route.first, "/", route.second) } } // then: all routes are registered by as proper HandlerType routes.forEach { (method, handler) -> diff --git a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt index d799fc3..e54f20c 100644 --- a/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt +++ b/routing-dsl/src/main/kotlin/io/javalin/community/routing/dsl/DslFactory.kt @@ -47,11 +47,16 @@ open class RoutingDslConfiguration, CONTEXT, fun before(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(BEFORE, path, handler) fun after(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(AFTER, path, handler) - fun routes(container: DslContainer) = routes(container.routes()) + fun routes(container: DslContainer) { + routes(container.routes()) + container.exceptionHandlers().forEach { exception(it.type, it.handler) } + } + fun routes(routesToAdd: Collection>) = routesToAdd.forEach { @Suppress("UNCHECKED_CAST") routes.add(it as ROUTE) } + fun route(method: Route, path: String, handler: CONTEXT.() -> RESPONSE) { routes( listOf( @@ -78,10 +83,10 @@ interface DslContainer, CONTEXT, RESPONSE : fun exceptionHandlers(): Collection> = emptySet() - fun route(path: String, method: Route, handler: CONTEXT.() -> RESPONSE): DslRoute = + fun route(method: Route, path: String, handler: CONTEXT.() -> RESPONSE): DslRoute = DefaultDslRoute( - path = path, method = method, + path = path, handler = handler ) diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt index b39da8c..702cf85 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/PropertyRoutingDslTest.kt @@ -14,8 +14,8 @@ class PropertyRoutingDslTest : TestSpecification() { private class ValidTestEndpoints : DefaultRoutes() { override fun routes() = setOf( - route("/test", GET) { result("test") }, - route("/throwing", GET) { throw RuntimeException() } + route(GET, "/test") { result("test") }, + route(GET, "/throwing") { throw RuntimeException() } ) override fun exceptionHandlers() = setOf( @@ -27,8 +27,8 @@ class PropertyRoutingDslTest : TestSpecification() { @Test fun `should register valid route in javalin instance`() = JavalinTest.test( - Javalin.create { - it.router.mount(Dsl) { + Javalin.create { cfg -> + cfg.router.mount(Dsl) { it.routes(ValidTestEndpoints()) } }, diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/PropertyDslExample.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/PropertyDslExample.kt index 8bec40e..1ed8ae0 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/PropertyDslExample.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/PropertyDslExample.kt @@ -3,29 +3,24 @@ package io.javalin.community.routing.dsl.examples import io.javalin.Javalin import io.javalin.community.routing.Route.GET import io.javalin.community.routing.Route.POST -import io.javalin.community.routing.dsl.DslRoute -import io.javalin.community.routing.dsl.DslContainer -import io.javalin.community.routing.dsl.examples.CustomDsl.CustomScope -import io.javalin.community.routing.dsl.routing +import io.javalin.community.routing.dsl.DslRouting.Companion.Dsl +import io.javalin.community.routing.dsl.defaults.DefaultRoutes import io.javalin.openapi.HttpMethod import io.javalin.openapi.OpenApi // Some dependencies -class ExampleService { +private class ExampleService { fun save(animal: String) = println("Saved animal: $animal") } -// Utility representation of custom routing in your application -abstract class ExampleRouting : DslContainer, CustomScope, Unit> - // Endpoint (domain router) -class AnimalEndpoints(private val exampleService: ExampleService) : ExampleRouting() { +private class AnimalEndpoints(private val exampleService: ExampleService) : DefaultRoutes() { @OpenApi( path = "/animal/{name}", methods = [HttpMethod.GET] ) - private val findAnimalByName = route("/animal/", GET) { + private val findAnimalByName = route(GET, "/animal/") { result(pathParam("name")) } @@ -33,7 +28,7 @@ class AnimalEndpoints(private val exampleService: ExampleService) : ExampleRouti path = "/animal/{name}", methods = [HttpMethod.POST] ) - private val saveAnimal = route("/animal/", POST) { + private val saveAnimal = route(POST, "/animal/") { exampleService.save(pathParam("name")) } @@ -51,7 +46,9 @@ fun main() { val exampleService = ExampleService() // setup & launch application - Javalin - .create { it.routing(CustomDsl, AnimalEndpoints(exampleService) /*, provide more classes with endpoints */) } - .start(8080) + Javalin.createAndStart { cfg -> + cfg.router.mount(Dsl) { + it.routes(AnimalEndpoints(exampleService)) + } + } } \ No newline at end of file From 3296af981d8cdebeb8b9e130b0f176214ad548d4 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Sat, 11 Nov 2023 18:30:12 +0100 Subject: [PATCH 5/5] GH-40 Update in-place routing example --- .../{InPlaceExample.kt => InPlaceWithCustomDslExample.kt} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/{InPlaceExample.kt => InPlaceWithCustomDslExample.kt} (87%) diff --git a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceWithCustomDslExample.kt similarity index 87% rename from routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt rename to routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceWithCustomDslExample.kt index db3ae8c..f1d6eb1 100644 --- a/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceExample.kt +++ b/routing-dsl/src/test/kotlin/io/javalin/community/routing/dsl/examples/InPlaceWithCustomDslExample.kt @@ -8,13 +8,13 @@ import io.javalin.community.routing.dsl.RoutingDslFactory import io.javalin.community.routing.dsl.defaults.DefaultContextScopeConfiguration import io.javalin.community.routing.dsl.defaults.DefaultDsl.DefaultScope import io.javalin.community.routing.dsl.defaults.Path -import io.javalin.community.routing.dsl.examples.CustomDsl.CustomRoutingConfiguration -import io.javalin.community.routing.dsl.examples.CustomDsl.CustomScope +import io.javalin.community.routing.dsl.examples.ExampleDsl.CustomRoutingConfiguration +import io.javalin.community.routing.dsl.examples.ExampleDsl.CustomScope import io.javalin.http.Context import io.javalin.http.ExceptionHandler import io.javalin.http.Handler -object CustomDsl : RoutingDslFactory, CustomScope, Unit> { +object ExampleDsl : RoutingDslFactory, CustomScope, Unit> { // This is custom configuration class that will be used to register routes open class CustomRoutingConfiguration : DefaultContextScopeConfiguration, CustomScope, Unit>() @@ -44,7 +44,7 @@ data class PandaPath(val age: Int) fun main() { Javalin.create { config -> - config.router.mount(DslRouting(CustomDsl)) { + config.router.mount(DslRouting(ExampleDsl)) { it.before { // `endpointHandlerPath` comes from Context class result("Called endpoint: ${matchedPath()}")