Polish Kotlin routing DSL
This commit changes RouterFunctionDsl {...} (request)
to route(request) {...}, remove uneeded methods, add
missing ones and add tests useful to validate the
DSL syntax and behavior.
Issue: SPR-15065
			
			
This commit is contained in:
		
							parent
							
								
									ba3cc535f1
								
							
						
					
					
						commit
						02409f74b9
					
				|  | @ -1,14 +1,16 @@ | |||
| package org.springframework.web.reactive.function.server | ||||
| 
 | ||||
| import org.springframework.core.io.Resource | ||||
| import org.springframework.http.HttpMethod | ||||
| import org.springframework.http.MediaType | ||||
| import reactor.core.publisher.Mono | ||||
| 
 | ||||
| /** | ||||
|  * Provide a router DSL for [RouterFunctions] and [RouterFunction] in order to be able to | ||||
|  * Provide a routing DSL for [RouterFunctions] and [RouterFunction] in order to be able to | ||||
|  * write idiomatic Kotlin code as below: | ||||
|  * | ||||
|  * * ```kotlin | ||||
|  * fun route(request: ServerRequest) = RouterFunctionDsl { | ||||
|  * fun route(request: ServerRequest) = route(request) { | ||||
|  * 		accept(TEXT_HTML).apply { | ||||
|  * 			(GET("/user/") or GET("/users/")) { findAllView() } | ||||
|  * 			GET("/user/{login}") { findViewById() } | ||||
|  | @ -18,16 +20,19 @@ import reactor.core.publisher.Mono | |||
|  * 			POST("/api/user/") { create() } | ||||
|  * 			POST("/api/user/{login}") { findOne() } | ||||
|  * 		} | ||||
|  * 	} (request) | ||||
|  * 	} | ||||
|  * ``` | ||||
|  * | ||||
|  * @since 5.0 | ||||
|  * @author Sebastien Deleuze | ||||
|  * @author Yevhenii Melnyk | ||||
|  */ | ||||
| class RouterFunctionDsl { | ||||
| fun RouterFunction<*>.route(request: ServerRequest, configure: RouterDsl.() -> Unit) = | ||||
| 		RouterDsl().apply(configure).invoke(request) | ||||
| 
 | ||||
| 	val children = mutableListOf<RouterFunctionDsl>() | ||||
| class RouterDsl { | ||||
| 
 | ||||
| 	val children = mutableListOf<RouterDsl>() | ||||
| 	val routes = mutableListOf<RouterFunction<ServerResponse>>() | ||||
| 
 | ||||
| 	operator fun RequestPredicate.invoke(f: () -> HandlerFunction<ServerResponse>) { | ||||
|  | @ -40,66 +45,63 @@ class RouterFunctionDsl { | |||
| 
 | ||||
| 	operator fun RequestPredicate.not(): RequestPredicate = this.negate() | ||||
| 
 | ||||
| 	fun GET(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.GET(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun GET(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.GET(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun HEAD(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.HEAD(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun HEAD(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.HEAD(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun POST(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.POST(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun POST(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.POST(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun PUT(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.PUT(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun PUT(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.PUT(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun PATCH(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.PATCH(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun PATCH(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.PATCH(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun DELETE(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.DELETE(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun DELETE(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.DELETE(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun OPTIONS(pattern: String): RequestPredicate { | ||||
| 		return RequestPredicates.OPTIONS(pattern) | ||||
| 	} | ||||
| 
 | ||||
| 	fun OPTIONS(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.OPTIONS(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun accept(vararg mediaType: MediaType, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.accept(*mediaType), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun contentType(vararg mediaType: MediaType, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.contentType(*mediaType), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun headers(headerPredicate: (ServerRequest.Headers)->Boolean, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.headers(headerPredicate), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun method(httpMethod: HttpMethod, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.method(httpMethod), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun path(pattern: String, f: () -> HandlerFunction<ServerResponse>) { | ||||
| 		routes += RouterFunctions.route(RequestPredicates.path(pattern), f()) | ||||
| 	} | ||||
| 
 | ||||
| 	fun resources(path: String, location: Resource) { | ||||
| 		routes +=  RouterFunctions.resources(path, location) | ||||
| 	} | ||||
| 
 | ||||
| 	fun resources(lookupFunction: (ServerRequest) -> Mono<Resource>) { | ||||
| 		routes +=  RouterFunctions.resources(lookupFunction) | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	@Suppress("UNCHECKED_CAST") | ||||
| 	fun router(): RouterFunction<ServerResponse> { | ||||
| 		return routes().reduce(RouterFunction<*>::and) as RouterFunction<ServerResponse> | ||||
|  | @ -117,6 +119,5 @@ class RouterFunctionDsl { | |||
| 		} | ||||
| 		return allRoutes | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fun RouterFunctionDsl(configure: RouterFunctionDsl.()->Unit) = RouterFunctionDsl().apply(configure) | ||||
| } | ||||
|  | @ -0,0 +1,129 @@ | |||
| package org.springframework.web.reactive.function.server | ||||
| 
 | ||||
| import org.junit.Test | ||||
| import org.springframework.core.io.ClassPathResource | ||||
| import org.springframework.http.HttpHeaders.ACCEPT | ||||
| import org.springframework.http.HttpHeaders.CONTENT_TYPE | ||||
| import org.springframework.http.HttpMethod | ||||
| import org.springframework.http.HttpMethod.PATCH | ||||
| import org.springframework.http.HttpMethod.POST | ||||
| import org.springframework.http.MediaType.* | ||||
| import org.springframework.web.reactive.function.server.MockServerRequest.builder | ||||
| import org.springframework.web.reactive.function.server.RequestPredicates.* | ||||
| import org.springframework.web.reactive.function.server.ServerResponse.ok | ||||
| import reactor.core.publisher.Mono | ||||
| import reactor.test.StepVerifier | ||||
| import java.net.URI | ||||
| 
 | ||||
| class RouterFunctionExtensionsTests { | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun header() { | ||||
| 		val request = builder().header("bar", "bar").build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun accept() { | ||||
| 		val request = builder().header(ACCEPT, APPLICATION_ATOM_XML_VALUE).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun acceptAndPOST() { | ||||
| 		val request = builder().method(POST).uri(URI("/api/foo/")).header(ACCEPT, APPLICATION_JSON_VALUE).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun contentType() { | ||||
| 		val request = builder().header(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun resourceByPath() { | ||||
| 		val request = builder().uri(URI("/org/springframework/web/reactive/function/response.txt")).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun method() { | ||||
| 		val request = builder().method(PATCH).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun path() { | ||||
| 		val request = builder().uri(URI("/baz")).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun resource() { | ||||
| 		val request = builder().uri(URI("/response.txt")).build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.expectNextCount(1) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	fun noRoute() { | ||||
| 		val request = builder() | ||||
| 				.uri(URI("/bar")) | ||||
| 				.header(ACCEPT, APPLICATION_PDF_VALUE) | ||||
| 				.header(CONTENT_TYPE, APPLICATION_PDF_VALUE) | ||||
| 				.build() | ||||
| 		StepVerifier.create(FooController().route(request)) | ||||
| 				.verifyComplete() | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	class FooController : RouterFunction<ServerResponse> { | ||||
| 
 | ||||
| 		override fun route(request: ServerRequest) = route(request) { | ||||
| 			(GET("/foo/") or GET("/foos/")) { handle() } | ||||
| 			accept(APPLICATION_JSON).apply { | ||||
| 				POST("/api/foo/") { handle() } | ||||
| 				PUT("/api/foo/") { handle() } | ||||
| 				DELETE("/api/foo/") { handle() } | ||||
| 			} | ||||
| 			accept(APPLICATION_ATOM_XML) { handle() } | ||||
| 			contentType(APPLICATION_OCTET_STREAM) { handle() } | ||||
| 			method(HttpMethod.PATCH) { handle() } | ||||
| 			headers({ it.accept().contains(APPLICATION_JSON) }).apply { | ||||
| 				GET("/api/foo/") { handle() } | ||||
| 			} | ||||
| 			headers({ it.header("bar").isNotEmpty() }) { handle() } | ||||
| 			resources("/org/springframework/web/reactive/function/**", | ||||
| 					ClassPathResource("/org/springframework/web/reactive/function/response.txt")) | ||||
| 			resources { | ||||
| 				if (it.path() == "/response.txt") { | ||||
| 					Mono.just(ClassPathResource("/org/springframework/web/reactive/function/response.txt")) | ||||
| 				} | ||||
| 				else { | ||||
| 					Mono.empty() | ||||
| 				} | ||||
| 			} | ||||
| 			path("/baz") { handle() } | ||||
| 		} | ||||
| 
 | ||||
| 		fun handle() = HandlerFunction { ok().build() } | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue