Add Kotlin router DSL and extensions for WebMvc.fn
Closes gh-22697
This commit is contained in:
parent
92d5f6395e
commit
e6171fb47d
|
@ -0,0 +1,508 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* Allow to create easily a WebMvc.fn `RouterFunction<ServerResponse>` from a Kotlin router
|
||||
* DSL leveraging WebMvc.fn the Java API ([RouterFunction], [RequestPredicate],
|
||||
* [HandlerFunction]).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* class ApplicationRoutes(val userHandler: UserHandler) {
|
||||
*
|
||||
* @Bean
|
||||
* fun mainRouter() = router {
|
||||
* accept(TEXT_HTML).nest {
|
||||
* (GET("/user/") or GET("/users/")).invoke(userHandler::findAllView)
|
||||
* GET("/users/{login}", userHandler::findViewById)
|
||||
* }
|
||||
* accept(APPLICATION_JSON).nest {
|
||||
* (GET("/api/user/") or GET("/api/users/")).invoke(userHandler::findAll)
|
||||
* POST("/api/users/", userHandler::create)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
* @author Sebastien Deleuze
|
||||
* @see RouterFunctionDsl
|
||||
* @see RouterFunctions.Builder
|
||||
* @since 5.2
|
||||
*/
|
||||
fun router(routes: (RouterFunctionDsl.() -> Unit)) = RouterFunctionDsl(routes).build()
|
||||
|
||||
/**
|
||||
* Provide a WebMvc.fn [RouterFunction] Kotlin DSL in order to be able to write idiomatic Kotlin code.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
class RouterFunctionDsl(private val init: (RouterFunctionDsl.() -> Unit)) {
|
||||
|
||||
private val builder = RouterFunctions.route()
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate AND
|
||||
* the [other] predicate (String processed as a path predicate). When evaluating the
|
||||
* composed predicate, if this predicate is `false`, then the [other] predicate is not
|
||||
* evaluated.
|
||||
* @see RequestPredicate.and
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
infix fun RequestPredicate.and(other: String): RequestPredicate = this.and(path(other))
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate OR
|
||||
* the [other] predicate (String processed as a path predicate). When evaluating the
|
||||
* composed predicate, if this predicate is `true`, then the [other] predicate is not
|
||||
* evaluated.
|
||||
* @see RequestPredicate.or
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
infix fun RequestPredicate.or(other: String): RequestPredicate = this.or(path(other))
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate (String
|
||||
* processed as a path predicate) AND the [other] predicate. When evaluating the
|
||||
* composed predicate, if this predicate is `false`, then the [other] predicate is not
|
||||
* evaluated.
|
||||
* @see RequestPredicate.and
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
infix fun String.and(other: RequestPredicate): RequestPredicate = path(this).and(other)
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate (String
|
||||
* processed as a path predicate) OR the [other] predicate. When evaluating the
|
||||
* composed predicate, if this predicate is `true`, then the [other] predicate is not
|
||||
* evaluated.
|
||||
* @see RequestPredicate.or
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
infix fun String.or(other: RequestPredicate): RequestPredicate = path(this).or(other)
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate AND
|
||||
* the [other] predicate. When evaluating the composed predicate, if this
|
||||
* predicate is `false`, then the [other] predicate is not evaluated.
|
||||
* @see RequestPredicate.and
|
||||
*/
|
||||
infix fun RequestPredicate.and(other: RequestPredicate): RequestPredicate = this.and(other)
|
||||
|
||||
/**
|
||||
* Return a composed request predicate that tests against both this predicate OR
|
||||
* the [other] predicate. When evaluating the composed predicate, if this
|
||||
* predicate is `true`, then the [other] predicate is not evaluated.
|
||||
* @see RequestPredicate.or
|
||||
*/
|
||||
infix fun RequestPredicate.or(other: RequestPredicate): RequestPredicate = this.or(other)
|
||||
|
||||
/**
|
||||
* Return a predicate that represents the logical negation of this predicate.
|
||||
*/
|
||||
operator fun RequestPredicate.not(): RequestPredicate = this.negate()
|
||||
|
||||
/**
|
||||
* Route to the given router function if the given request predicate applies. This
|
||||
* method can be used to create *nested routes*, where a group of routes share a
|
||||
* common path (prefix), header, or other request predicate.
|
||||
* @see RouterFunctions.nest
|
||||
*/
|
||||
fun RequestPredicate.nest(r: (RouterFunctionDsl.() -> Unit)) {
|
||||
builder.nest(this, Supplier(RouterFunctionDsl(r)::build))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Route to the given router function if the given request predicate (String
|
||||
* processed as a path predicate) applies. This method can be used to create
|
||||
* *nested routes*, where a group of routes share a common path
|
||||
* (prefix), header, or other request predicate.
|
||||
* @see RouterFunctions.nest
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
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
|
||||
*/
|
||||
fun GET(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.GET(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun HEAD(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.HEAD(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun POST(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.POST(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun PUT(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.PUT(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun PATCH(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.PATCH(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun PATCH(pattern: String): RequestPredicate = RequestPredicates.PATCH(pattern)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given `DELETE` predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun DELETE(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.DELETE(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun DELETE(pattern: String): RequestPredicate = RequestPredicates.DELETE(pattern)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given OPTIONS predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun OPTIONS(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.OPTIONS(pattern, HandlerFunction(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun OPTIONS(pattern: String): RequestPredicate = RequestPredicates.OPTIONS(pattern)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given accept predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun accept(mediaType: MediaType, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.accept(mediaType), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mediaType 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(vararg mediaType: MediaType): RequestPredicate = RequestPredicates.accept(*mediaType)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given contentType predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun contentType(mediaType: MediaType, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.contentType(mediaType), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(vararg mediaTypes: MediaType): RequestPredicate = RequestPredicates.contentType(*mediaTypes)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given headers predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun headers(headersPredicate: (ServerRequest.Headers) -> Boolean, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.headers(headersPredicate), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun headers(headersPredicate: (ServerRequest.Headers) -> Boolean): RequestPredicate =
|
||||
RequestPredicates.headers(headersPredicate)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given method predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun method(httpMethod: HttpMethod, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.method(httpMethod), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun method(httpMethod: HttpMethod): RequestPredicate = RequestPredicates.method(httpMethod)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given path predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun path(pattern: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.path(pattern), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a [RequestPredicate] that tests the request path against the given path pattern.
|
||||
* @see RequestPredicates.path
|
||||
*/
|
||||
fun path(pattern: String): RequestPredicate = RequestPredicates.path(pattern)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given pathExtension predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun pathExtension(extension: String, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.pathExtension(extension), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun pathExtension(extension: String): RequestPredicate = RequestPredicates.pathExtension(extension)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given pathExtension predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun pathExtension(predicate: (String) -> Boolean, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.pathExtension(predicate), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a [RequestPredicate] that matches if the request's path matches the given
|
||||
* predicate.
|
||||
* @see RequestPredicates.pathExtension
|
||||
*/
|
||||
fun pathExtension(predicate: (String) -> Boolean): RequestPredicate =
|
||||
RequestPredicates.pathExtension(predicate)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given queryParam predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
fun param(name: String, predicate: (String) -> Boolean, f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.param(name, predicate), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return a predicate that matches the given predicate against the query parameter of the given name
|
||||
* @see ServerRequest#queryParam
|
||||
*/
|
||||
fun param(name: String, predicate: (String) -> Boolean): RequestPredicate =
|
||||
RequestPredicates.param(name, predicate)
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given request predicate applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
operator fun RequestPredicate.invoke(f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(this, HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given predicate (String
|
||||
* processed as a path predicate) applies.
|
||||
* @see RouterFunctions.route
|
||||
*/
|
||||
operator fun String.invoke(f: (ServerRequest) -> ServerResponse) {
|
||||
builder.add(RouterFunctions.route(RequestPredicates.path(this), HandlerFunction(f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requests that match the given pattern to resources relative to the given root location.
|
||||
* @see RouterFunctions.resources
|
||||
*/
|
||||
fun resources(path: String, location: Resource) {
|
||||
builder.resources(path, location)
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to resources using the provided lookup function. If the lookup function provides a
|
||||
* [Resource] for the given request, it will be it will be exposed using a
|
||||
* [HandlerFunction] that handles GET, HEAD, and OPTIONS requests.
|
||||
*/
|
||||
fun resources(lookupFunction: (ServerRequest) -> Resource?) {
|
||||
builder.resources {
|
||||
Optional.ofNullable(lookupFunction.invoke(it))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a composed routing function created from all the registered routes.
|
||||
*/
|
||||
internal fun build(): RouterFunction<ServerResponse> {
|
||||
init()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ServerResponse.from
|
||||
*/
|
||||
fun from(other: ServerResponse) =
|
||||
ServerResponse.from(other)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.created
|
||||
*/
|
||||
fun created(location: URI) =
|
||||
ServerResponse.created(location)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.ok
|
||||
*/
|
||||
fun ok() = ServerResponse.ok()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.noContent
|
||||
*/
|
||||
fun noContent() = ServerResponse.noContent()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.accepted
|
||||
*/
|
||||
fun accepted() = ServerResponse.accepted()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.permanentRedirect
|
||||
*/
|
||||
fun permanentRedirect(location: URI) = ServerResponse.permanentRedirect(location)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.temporaryRedirect
|
||||
*/
|
||||
fun temporaryRedirect(location: URI) = ServerResponse.temporaryRedirect(location)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.seeOther
|
||||
*/
|
||||
fun seeOther(location: URI) = ServerResponse.seeOther(location)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.badRequest
|
||||
*/
|
||||
fun badRequest() = ServerResponse.badRequest()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.notFound
|
||||
*/
|
||||
fun notFound() = ServerResponse.notFound()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.unprocessableEntity
|
||||
*/
|
||||
fun unprocessableEntity() = ServerResponse.unprocessableEntity()
|
||||
|
||||
/**
|
||||
* @see ServerResponse.status
|
||||
*/
|
||||
fun status(status: HttpStatus) = ServerResponse.status(status)
|
||||
|
||||
/**
|
||||
* @see ServerResponse.status
|
||||
*/
|
||||
fun status(status: Int) = ServerResponse.status(status)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to [RouterFunction.and].
|
||||
*/
|
||||
operator fun <T: ServerResponse> RouterFunction<T>.plus(other: RouterFunction<T>) =
|
||||
this.and(other)
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
import org.springframework.http.MediaType
|
||||
import java.net.InetSocketAddress
|
||||
import java.security.Principal
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.remoteAddress]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.remoteAddressOrNull(): InetSocketAddress? = remoteAddress().orElse(null)
|
||||
|
||||
/**
|
||||
* Extension for [ServerRequest.body] providing a `body<Foo>()` variant
|
||||
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||
* erasure and retains actual generic type arguments.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
inline fun <reified T : Any> ServerRequest.body(): T = body(object : ParameterizedTypeReference<T>() {})
|
||||
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.attribute]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.attributeOrNull(name: String): Any? = attribute(name).orElse(null)
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.param]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.paramOrNull(name: String): String? = param(name).orElse(null)
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.param]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.principalOrNull(): Principal? = principal().orElse(null)
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.Headers.contentLength]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.Headers.contentLengthOrNull(): Long? = contentLength().let { if (it.isPresent) it.asLong else null }
|
||||
|
||||
/**
|
||||
* Nullable variant of [ServerRequest.Headers.contentType]
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
fun ServerRequest.Headers.contentTypeOrNull(): MediaType? = contentType().orElse(null)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
|
||||
/**
|
||||
* Extension for [ServerResponse.BodyBuilder.body] providing a variant
|
||||
* leveraging Kotlin reified type parameters.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
*/
|
||||
inline fun <reified T: Any> ServerResponse.BodyBuilder.bodyWithType(body: T) =
|
||||
body(body, object : ParameterizedTypeReference<T>() {})
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.HttpHeaders.*
|
||||
import org.springframework.http.HttpMethod.*
|
||||
import org.springframework.http.MediaType.*
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest
|
||||
|
||||
/**
|
||||
* Tests for WebMvc.fn [RouterFunctionDsl].
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class RouterFunctionDslTests {
|
||||
|
||||
@Test
|
||||
fun header() {
|
||||
val servletRequest = MockHttpServletRequest()
|
||||
servletRequest.addHeader("bar", "bar")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun accept() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/content")
|
||||
servletRequest.addHeader(ACCEPT, APPLICATION_ATOM_XML_VALUE)
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun acceptAndPOST() {
|
||||
val servletRequest = MockHttpServletRequest("POST", "/api/foo/")
|
||||
servletRequest.addHeader(ACCEPT, APPLICATION_JSON_VALUE)
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentType() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/content")
|
||||
servletRequest.addHeader(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE)
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resourceByPath() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/org/springframework/web/servlet/function/response.txt")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun method() {
|
||||
val servletRequest = MockHttpServletRequest("PATCH", "/")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun path() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/baz")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resource() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/response.txt")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noRoute() {
|
||||
|
||||
val servletRequest = MockHttpServletRequest("GET", "/bar")
|
||||
servletRequest.addHeader(ACCEPT, APPLICATION_PDF_VALUE)
|
||||
servletRequest.addHeader(CONTENT_TYPE, APPLICATION_PDF_VALUE)
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertFalse(sampleRouter().route(request).isPresent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rendering() {
|
||||
val servletRequest = MockHttpServletRequest("GET", "/rendering")
|
||||
val request = DefaultServerRequest(servletRequest, emptyList())
|
||||
assertTrue(sampleRouter().route(request).get().handle(request) is RenderingResponse)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun emptyRouter() {
|
||||
router { }
|
||||
}
|
||||
|
||||
|
||||
private fun sampleRouter() = router {
|
||||
(GET("/foo/") or GET("/foos/")) { req -> handle(req) }
|
||||
"/api".nest {
|
||||
POST("/foo/", ::handleFromClass)
|
||||
PUT("/foo/", :: handleFromClass)
|
||||
PATCH("/foo/") {
|
||||
ok().build()
|
||||
}
|
||||
"/foo/" { handleFromClass(it) }
|
||||
}
|
||||
"/content".nest {
|
||||
accept(APPLICATION_ATOM_XML, ::handle)
|
||||
contentType(APPLICATION_OCTET_STREAM, ::handle)
|
||||
}
|
||||
method(PATCH, ::handle)
|
||||
headers { it.accept().contains(APPLICATION_JSON) }.nest {
|
||||
GET("/api/foo/", ::handle)
|
||||
}
|
||||
headers({ it.header("bar").isNotEmpty() }, ::handle)
|
||||
resources("/org/springframework/web/servlet/function/**",
|
||||
ClassPathResource("/org/springframework/web/servlet/function/response.txt"))
|
||||
resources {
|
||||
if (it.path() == "/response.txt") {
|
||||
ClassPathResource("/org/springframework/web/servlet/function/response.txt")
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
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 handle(req: ServerRequest) = ServerResponse.ok().build()
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
import org.springframework.http.MediaType
|
||||
import java.net.InetSocketAddress
|
||||
import java.security.Principal
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for WebMvc.fn [ServerRequest] extensions.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class ServerRequestExtensionsTests {
|
||||
|
||||
val request = mockk<ServerRequest>()
|
||||
|
||||
val headers = mockk<ServerRequest.Headers>()
|
||||
|
||||
@Test
|
||||
fun `remoteAddressOrNull with value`() {
|
||||
val remoteAddress = mockk<InetSocketAddress>()
|
||||
every { request.remoteAddress() } returns Optional.of(remoteAddress)
|
||||
assertEquals(remoteAddress, request.remoteAddressOrNull())
|
||||
verify { request.remoteAddress() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remoteAddressOrNull with null`() {
|
||||
every { request.remoteAddress() } returns Optional.empty()
|
||||
assertNull(request.remoteAddressOrNull())
|
||||
verify { request.remoteAddress() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun body() {
|
||||
val body = Arrays.asList("foo", "bar")
|
||||
val typeReference = object: ParameterizedTypeReference<List<String>>() {}
|
||||
every { request.body(typeReference) } returns body
|
||||
assertEquals(body, request.body<List<String>>())
|
||||
verify { request.body(typeReference) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `attributeOrNull with value`() {
|
||||
val attribute = mockk<Any>()
|
||||
every { request.attribute("foo") } returns Optional.of(attribute)
|
||||
assertEquals(attribute, request.attributeOrNull("foo"))
|
||||
verify { request.attribute("foo") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `attributeOrNull with null`() {
|
||||
every { request.attribute("foo") } returns Optional.empty()
|
||||
assertNull(request.attributeOrNull("foo"))
|
||||
verify { request.attribute("foo") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `paramOrNull with value`() {
|
||||
val param = "bar"
|
||||
every { request.param("foo") } returns Optional.of(param)
|
||||
assertEquals(param, request.paramOrNull("foo"))
|
||||
verify { request.param("foo") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `paramOrNull with null`() {
|
||||
every { request.param("foo") } returns Optional.empty()
|
||||
assertNull(request.paramOrNull("foo"))
|
||||
verify { request.param("foo") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `principalOrNull with value`() {
|
||||
val principal = mockk<Principal>()
|
||||
every { request.principal() } returns Optional.of(principal)
|
||||
assertEquals(principal, request.principalOrNull())
|
||||
verify { request.principal() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `principalOrNull with null`() {
|
||||
every { request.principal() } returns Optional.empty()
|
||||
assertNull(request.principalOrNull())
|
||||
verify { request.principal() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contentLengthOrNull with value`() {
|
||||
val contentLength: Long = 123
|
||||
every { headers.contentLength() } returns OptionalLong.of(contentLength)
|
||||
assertEquals(contentLength, headers.contentLengthOrNull())
|
||||
verify { headers.contentLength() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contentLengthOrNull with null`() {
|
||||
every { headers.contentLength() } returns OptionalLong.empty()
|
||||
assertNull(headers.contentLengthOrNull())
|
||||
verify { headers.contentLength() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contentTypeOrNull with value`() {
|
||||
val contentType = mockk<MediaType>()
|
||||
every { headers.contentType() } returns Optional.of(contentType)
|
||||
assertEquals(contentType, headers.contentTypeOrNull())
|
||||
verify { headers.contentType() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contentTypeOrNull with null`() {
|
||||
val contentType = mockk<MediaType>()
|
||||
every { headers.contentType() } returns Optional.empty()
|
||||
assertNull(headers.contentTypeOrNull())
|
||||
verify { headers.contentType() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.function
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for WebMvc.fn [ServerResponse] extensions.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class ServerResponseExtensionsTests {
|
||||
|
||||
@Test
|
||||
fun bodyWithType() {
|
||||
val builder = mockk<ServerResponse.BodyBuilder>()
|
||||
val response = mockk<ServerResponse>()
|
||||
val body = Arrays.asList("foo", "bar")
|
||||
val typeReference = object: ParameterizedTypeReference<List<String>>() {}
|
||||
every { builder.body(body, typeReference) } returns response
|
||||
Assert.assertEquals(response, builder.bodyWithType<List<String>>(body))
|
||||
verify { builder.body(body, typeReference) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue