From 1ce4339601e7944618a76eee8e4881083924e0eb Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Sat, 11 Nov 2023 16:58:36 +0100 Subject: [PATCH] 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