diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDsl.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDsl.kt index edcbd6a386..1bf34a1810 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDsl.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDsl.kt @@ -149,13 +149,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun String.nest(r: (CoRouterFunctionDsl.() -> Unit)) = path(this).nest(r) /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun GET(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.GET(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun GET(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.GET(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `GET` * and the given `pattern` matches against the request path. @@ -164,13 +176,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern) /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun HEAD(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.HEAD(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun HEAD(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.HEAD(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `HEAD` * and the given `pattern` matches against the request path. @@ -179,13 +203,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun HEAD(pattern: String): RequestPredicate = RequestPredicates.HEAD(pattern) /** - * Route to the given handler function if the given `POST` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun POST(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.POST(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun POST(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.POST(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `POST` * and the given `pattern` matches against the request path. @@ -194,13 +230,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun POST(pattern: String): RequestPredicate = RequestPredicates.POST(pattern) /** - * Route to the given handler function if the given `PUT` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun PUT(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.PUT(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PUT(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.PUT(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `PUT` * and the given `pattern` matches against the request path. @@ -209,13 +257,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun PUT(pattern: String): RequestPredicate = RequestPredicates.PUT(pattern) /** - * Route to the given handler function if the given `PATCH` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun PATCH(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.PATCH(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PATCH(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.PATCH(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `PATCH` * and the given `pattern` matches against the request path. @@ -226,13 +286,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun PATCH(pattern: String): RequestPredicate = RequestPredicates.PATCH(pattern) /** - * Route to the given handler function if the given `DELETE` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun DELETE(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.DELETE(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun DELETE(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.DELETE(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `DELETE` * and the given `pattern` matches against the request path. @@ -243,13 +315,25 @@ class CoRouterFunctionDsl(private val init: (CoRouterFunctionDsl.() -> Unit)) { fun DELETE(pattern: String): RequestPredicate = RequestPredicates.DELETE(pattern) /** - * Route to the given handler function if the given OPTIONS predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun OPTIONS(pattern: String, f: suspend (ServerRequest) -> ServerResponse) { builder.OPTIONS(pattern, asHandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun OPTIONS(pattern: String, predicate: RequestPredicate, f: suspend (ServerRequest) -> ServerResponse) { + builder.OPTIONS(pattern, predicate, asHandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `OPTIONS` * and the given `pattern` matches against the request path. diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt index 2e3e29a966..5e511da80c 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt @@ -148,76 +148,136 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun GET(pattern: String, f: (ServerRequest) -> Mono) { builder.GET(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code GET} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun GET(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.GET(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `GET` + * and the given [pattern] matches against the request path. * @see RequestPredicates.GET */ fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern) /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun HEAD(pattern: String, f: (ServerRequest) -> Mono) { builder.HEAD(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code HEAD} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun HEAD(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.HEAD(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `HEAD` + * and the given `pattern` matches against the request path. * @see RequestPredicates.HEAD */ fun HEAD(pattern: String): RequestPredicate = RequestPredicates.HEAD(pattern) /** - * Route to the given handler function if the given POST predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun POST(pattern: String, f: (ServerRequest) -> Mono) { builder.POST(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code POST} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun POST(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.POST(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `POST` + * and the given `pattern` matches against the request path. * @see RequestPredicates.POST */ fun POST(pattern: String): RequestPredicate = RequestPredicates.POST(pattern) /** - * Route to the given handler function if the given PUT predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun PUT(pattern: String, f: (ServerRequest) -> Mono) { builder.PUT(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PUT} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PUT(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.PUT(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `PUT` + * and the given `pattern` matches against the request path. * @see RequestPredicates.PUT */ fun PUT(pattern: String): RequestPredicate = RequestPredicates.PUT(pattern) /** - * Route to the given handler function if the given PATCH predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to */ fun PATCH(pattern: String, f: (ServerRequest) -> Mono) { builder.PATCH(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PATCH} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PATCH(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.PATCH(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `PATCH` + * and the given `pattern` matches against the request path. * @param pattern the path pattern to match against * @return a predicate that matches if the request method is PATCH and if the given pattern * matches against the request path @@ -225,16 +285,28 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { fun PATCH(pattern: String): RequestPredicate = RequestPredicates.PATCH(pattern) /** - * Route to the given handler function if the given DELETE predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun DELETE(pattern: String, f: (ServerRequest) -> Mono) { builder.DELETE(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code DELETE} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun DELETE(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.DELETE(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `DELETE` + * and the given `pattern` matches against the request path. * @param pattern the path pattern to match against * @return a predicate that matches if the request method is DELETE and if the given pattern * matches against the request path @@ -242,16 +314,28 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { fun DELETE(pattern: String): RequestPredicate = RequestPredicates.DELETE(pattern) /** - * Route to the given handler function if the given OPTIONS predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun OPTIONS(pattern: String, f: (ServerRequest) -> Mono) { builder.OPTIONS(pattern) { f(it).cast(ServerResponse::class.java) } } /** - * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code OPTIONS} - * and the given {@code pattern} matches against the request path. + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun OPTIONS(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> Mono) { + builder.OPTIONS(pattern, predicate, HandlerFunction { f(it).cast(ServerResponse::class.java) }) + } + + /** + * Return a [RequestPredicate] that matches if request's HTTP method is `OPTIONS` + * and the given `pattern` matches against the request path. * @param pattern the path pattern to match against * @return a predicate that matches if the request method is OPTIONS and if the given pattern * matches against the request path @@ -267,30 +351,30 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that tests if the request's - * {@linkplain ServerRequest.Headers#accept() accept} header is - * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with any of the given media types. + * Return a [RequestPredicate] that tests if the request's + * [accept][ServerRequest.Headers.accept] header is + * [compatible][MediaType.isCompatibleWith] with any of the given media types. * @param mediaTypes the media types to match the request's accept header against * @return a predicate that tests the request's accept header against the given media types */ - fun accept(mediaType: MediaType): RequestPredicate = RequestPredicates.accept(mediaType) + fun accept(vararg mediaTypes: MediaType): RequestPredicate = RequestPredicates.accept(*mediaTypes) /** * Route to the given handler function if the given contentType predicate applies. * @see RouterFunctions.route */ - fun contentType(mediaType: MediaType, f: (ServerRequest) -> Mono) { - builder.add(RouterFunctions.route(RequestPredicates.contentType(mediaType), HandlerFunction { f(it).cast(ServerResponse::class.java) })) + fun contentType(mediaTypes: MediaType, f: (ServerRequest) -> Mono) { + builder.add(RouterFunctions.route(RequestPredicates.contentType(mediaTypes), HandlerFunction { f(it).cast(ServerResponse::class.java) })) } /** - * Return a {@code RequestPredicate} that tests if the request's - * {@linkplain ServerRequest.Headers#contentType() content type} is - * {@linkplain MediaType#includes(MediaType) included} by any of the given media types. + * Return a [RequestPredicate] that tests if the request's + * [content type][ServerRequest.Headers.contentType] is + * [included][MediaType.includes] by any of the given media types. * @param mediaTypes the media types to match the request's content type against * @return a predicate that tests the request's content type against the given media types */ - fun contentType(mediaType: MediaType): RequestPredicate = RequestPredicates.contentType(mediaType) + fun contentType(vararg mediaTypes: MediaType): RequestPredicate = RequestPredicates.contentType(*mediaTypes) /** * Route to the given handler function if the given headers predicate applies. @@ -301,7 +385,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that tests the request's headers against the given headers predicate. + * Return a [RequestPredicate] that tests the request's headers against the given headers predicate. * @param headersPredicate a predicate that tests against the request headers * @return a predicate that tests against the given header predicate */ @@ -317,7 +401,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that tests against the given HTTP method. + * Return a [RequestPredicate] that tests against the given HTTP method. * @param httpMethod the HTTP method to match to * @return a predicate that tests against the given HTTP method */ @@ -332,7 +416,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that tests the request path against the given path pattern. + * Return a [RequestPredicate] that tests the request path against the given path pattern. * @see RequestPredicates.path */ fun path(pattern: String): RequestPredicate = RequestPredicates.path(pattern) @@ -346,7 +430,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that matches if the request's path has the given extension. + * Return a [RequestPredicate] that matches if the request's path has the given extension. * @param extension the path extension to match against, ignoring case * @return a predicate that matches if the request's path has the given file extension */ @@ -361,7 +445,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that matches if the request's path matches the given + * Return a [RequestPredicate] that matches if the request's path matches the given * predicate. * @see RequestPredicates.pathExtension */ @@ -377,7 +461,7 @@ class RouterFunctionDsl(private val init: RouterFunctionDsl.() -> Unit) { } /** - * Return a {@code RequestPredicate} that tests the request's query parameter of the given name + * Return a [RequestPredicate] that tests the request's query parameter of the given name * against the given predicate. * @param name the name of the query parameter to test against * @param predicate predicate to test against the query parameter value diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDslTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDslTests.kt index 25e76fe045..74a3222f03 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDslTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDslTests.kt @@ -16,10 +16,8 @@ package org.springframework.web.reactive.function.server -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail import org.springframework.core.io.ClassPathResource import org.springframework.http.HttpHeaders.* import org.springframework.http.HttpMethod.* @@ -63,6 +61,18 @@ class CoRouterFunctionDslTests { .verifyComplete() } + @Test + fun acceptAndPOSTWithRequestPredicate() { + val request = builder() + .method(POST) + .uri(URI("/api/bar/")) + .header(CONTENT_TYPE, APPLICATION_JSON_VALUE) + .build() + StepVerifier.create(sampleRouter().route(request)) + .expectNextCount(1) + .verifyComplete() + } + @Test fun contentType() { val request = builder().uri(URI("/content")).header(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE).build() @@ -134,6 +144,7 @@ class CoRouterFunctionDslTests { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } "/api".nest { POST("/foo/", ::handleFromClass) + POST("/bar/", contentType(APPLICATION_JSON), ::handleFromClass) PUT("/foo/", :: handleFromClass) PATCH("/foo/") { ok().buildAndAwait() @@ -162,10 +173,10 @@ class CoRouterFunctionDslTests { path("/baz", ::handle) GET("/rendering") { RenderingResponse.create("index").buildAndAwait() } } + + @Suppress("UNUSED_PARAMETER") + private suspend fun handleFromClass(req: ServerRequest) = ServerResponse.ok().buildAndAwait() } -@Suppress("UNUSED_PARAMETER") -private suspend fun handleFromClass(req: ServerRequest) = ServerResponse.ok().buildAndAwait() - @Suppress("UNUSED_PARAMETER") private suspend fun handle(req: ServerRequest) = ServerResponse.ok().buildAndAwait() diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDslTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDslTests.kt index d3d18e2fd2..e45da7cd40 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDslTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDslTests.kt @@ -16,10 +16,8 @@ package org.springframework.web.reactive.function.server -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail import org.springframework.core.io.ClassPathResource import org.springframework.http.HttpHeaders.* import org.springframework.http.HttpMethod.* @@ -64,6 +62,18 @@ class RouterFunctionDslTests { .verifyComplete() } + @Test + fun acceptAndPOSTWithRequestPredicate() { + val request = builder() + .method(POST) + .uri(URI("/api/bar/")) + .header(CONTENT_TYPE, APPLICATION_JSON_VALUE) + .build() + StepVerifier.create(sampleRouter().route(request)) + .expectNextCount(1) + .verifyComplete() + } + @Test fun contentType() { val request = builder().uri(URI("/content")).header(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE).build() @@ -135,6 +145,7 @@ class RouterFunctionDslTests { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } "/api".nest { POST("/foo/", ::handleFromClass) + POST("/bar/", contentType(APPLICATION_JSON), ::handleFromClass) PUT("/foo/", :: handleFromClass) PATCH("/foo/") { ok().build() @@ -163,10 +174,10 @@ class RouterFunctionDslTests { path("/baz", ::handle) GET("/rendering") { RenderingResponse.create("index").build() } } + + @Suppress("UNUSED_PARAMETER") + private fun handleFromClass(req: ServerRequest) = ServerResponse.ok().build() } -@Suppress("UNUSED_PARAMETER") -private fun handleFromClass(req: ServerRequest) = ServerResponse.ok().build() - @Suppress("UNUSED_PARAMETER") private fun handle(req: ServerRequest) = ServerResponse.ok().build() diff --git a/spring-webmvc/src/main/kotlin/org/springframework/web/servlet/function/RouterFunctionDsl.kt b/spring-webmvc/src/main/kotlin/org/springframework/web/servlet/function/RouterFunctionDsl.kt index bb6708b49d..c5bfd18b85 100644 --- a/spring-webmvc/src/main/kotlin/org/springframework/web/servlet/function/RouterFunctionDsl.kt +++ b/spring-webmvc/src/main/kotlin/org/springframework/web/servlet/function/RouterFunctionDsl.kt @@ -145,13 +145,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun String.nest(r: (RouterFunctionDsl.() -> Unit)) = path(this).nest(r) /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun GET(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.GET(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `GET` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun GET(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.GET(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `GET` * and the given `pattern` matches against the request path. @@ -160,13 +172,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern) /** - * Route to the given handler function if the given request predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun HEAD(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.HEAD(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `HEAD` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun HEAD(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.HEAD(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `HEAD` * and the given `pattern` matches against the request path. @@ -175,13 +199,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun HEAD(pattern: String): RequestPredicate = RequestPredicates.HEAD(pattern) /** - * Route to the given handler function if the given `POST` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun POST(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.POST(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `POST` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun POST(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.POST(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `POST` * and the given `pattern` matches against the request path. @@ -190,13 +226,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun POST(pattern: String): RequestPredicate = RequestPredicates.POST(pattern) /** - * Route to the given handler function if the given `PUT` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun PUT(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.PUT(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `PUT` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PUT(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.PUT(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `PUT` * and the given `pattern` matches against the request path. @@ -205,13 +253,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun PUT(pattern: String): RequestPredicate = RequestPredicates.PUT(pattern) /** - * Route to the given handler function if the given `PATCH` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun PATCH(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.PATCH(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `PATCH` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun PATCH(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.PATCH(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `PATCH` * and the given `pattern` matches against the request path. @@ -222,13 +282,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun PATCH(pattern: String): RequestPredicate = RequestPredicates.PATCH(pattern) /** - * Route to the given handler function if the given `DELETE` predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun DELETE(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.DELETE(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `DELETE` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun DELETE(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.DELETE(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `DELETE` * and the given `pattern` matches against the request path. @@ -239,13 +311,25 @@ class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) { fun DELETE(pattern: String): RequestPredicate = RequestPredicates.DELETE(pattern) /** - * Route to the given handler function if the given OPTIONS predicate applies. - * @see RouterFunctions.route + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to */ fun OPTIONS(pattern: String, f: (ServerRequest) -> ServerResponse) { builder.OPTIONS(pattern, HandlerFunction(f)) } + /** + * Adds a route to the given handler function that handles all HTTP `OPTIONS` requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @since 5.2 + */ + fun OPTIONS(pattern: String, predicate: RequestPredicate, f: (ServerRequest) -> ServerResponse) { + builder.OPTIONS(pattern, predicate, HandlerFunction(f)) + } + /** * Return a [RequestPredicate] that matches if request's HTTP method is `OPTIONS` * and the given `pattern` matches against the request path. diff --git a/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/function/RouterFunctionDslTests.kt b/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/function/RouterFunctionDslTests.kt index a5a432c6e9..39a79243f9 100644 --- a/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/function/RouterFunctionDslTests.kt +++ b/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/function/RouterFunctionDslTests.kt @@ -55,6 +55,15 @@ class RouterFunctionDslTests { assertThat(sampleRouter().route(request).isPresent).isTrue() } + @Test + fun acceptAndPOSTWithRequestPredicate() { + val servletRequest = MockHttpServletRequest("POST", "/api/bar/") + servletRequest.addHeader(ACCEPT, APPLICATION_JSON_VALUE) + servletRequest.addHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) + val request = DefaultServerRequest(servletRequest, emptyList()) + assertThat(sampleRouter().route(request).isPresent).isTrue() + } + @Test fun contentType() { val servletRequest = MockHttpServletRequest("GET", "/content") @@ -120,6 +129,7 @@ class RouterFunctionDslTests { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } "/api".nest { POST("/foo/", ::handleFromClass) + POST("/bar/", contentType(APPLICATION_JSON), ::handleFromClass) PUT("/foo/", :: handleFromClass) PATCH("/foo/") { ok().build() @@ -148,10 +158,10 @@ class RouterFunctionDslTests { path("/baz", ::handle) GET("/rendering") { RenderingResponse.create("index").build() } } + + @Suppress("UNUSED_PARAMETER") + private fun handleFromClass(req: ServerRequest) = ServerResponse.ok().build() } -@Suppress("UNUSED_PARAMETER") -private fun handleFromClass(req: ServerRequest) = ServerResponse.ok().build() - @Suppress("UNUSED_PARAMETER") private fun handle(req: ServerRequest) = ServerResponse.ok().build() diff --git a/src/docs/asciidoc/web/webflux-functional.adoc b/src/docs/asciidoc/web/webflux-functional.adoc index 07325af011..def7dd7cce 100644 --- a/src/docs/asciidoc/web/webflux-functional.adoc +++ b/src/docs/asciidoc/web/webflux-functional.adoc @@ -476,9 +476,9 @@ header: .Kotlin ---- val route = coRouter { - (GET("/hello-world") and accept(MediaType.TEXT_PLAIN)).invoke { - ServerResponse.ok().bodyValueAndAwait("Hello World") - } + GET("/hello-world", accept(TEXT_PLAIN)) { + ServerResponse.ok().bodyValueAndAwait("Hello World") + } } ---- @@ -553,9 +553,9 @@ RouterFunction route = route() val otherRoute: RouterFunction = coRouter { } val route = coRouter { - (GET("/person/{id}") and accept(APPLICATION_JSON)).invoke(handler::getPerson) // <1> - (GET("/person") and accept(APPLICATION_JSON)).invoke(handler::listPeople) // <2> - POST("/person").invoke(handler::createPerson) // <3> + GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1> + GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2> + POST("/person", handler::createPerson) // <3> }.and(otherRoute) // <4> ---- <1> `GET /person/{id}` with an `Accept` header that matches JSON is routed to @@ -595,9 +595,9 @@ RouterFunction route = route() ---- val route = coRouter { "/person".nest { - (GET("/{id}") and accept(APPLICATION_JSON)).invoke(handler::getPerson) - (GET("") and accept(APPLICATION_JSON)).invoke(handler::listPeople) - POST("/person").invoke(handler::createPerson) + GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) + GET("", accept(APPLICATION_JSON), handler::listPeople) + POST("/person", handler::createPerson) } } ---- @@ -622,7 +622,7 @@ We can further improve by using the `nest` method together with `accept`: .Kotlin ---- val route = coRouter { - "/person".nest{ + "/person".nest { accept(APPLICATION_JSON).nest { GET("/{id}", handler::getPerson) GET("", handler::listPeople)