From bbabf8d855cc8d48757bdc86f2f2c426a7438f0a Mon Sep 17 00:00:00 2001 From: christophejan <69955393+christophejan@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:51:15 +0200 Subject: [PATCH] Add minimal Kotlin DSL RouterFunction attributes support Closes gh-28567 --- .../function/server/CoRouterFunctionDsl.kt | 26 +++++++++- .../function/server/RouterFunctionDsl.kt | 26 +++++++++- .../server/CoRouterFunctionDslTests.kt | 51 ++++++++++++++++++- .../function/server/RouterFunctionDslTests.kt | 50 +++++++++++++++++- .../web/servlet/function/RouterFunctionDsl.kt | 26 +++++++++- .../function/RouterFunctionDslTests.kt | 49 +++++++++++++++++- 6 files changed, 222 insertions(+), 6 deletions(-) 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 f82a6b8bd9e..ed845816019 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -586,6 +586,30 @@ class CoRouterFunctionDsl internal constructor (private val init: (CoRouterFunct } } + /** + * Add an attribute with the given name and value to the last route built with this builder. + * @param name the attribute name + * @param value the attribute value +- * @since 5.3 + */ + fun withAttribute(name: String, value: Any) { + builder.withAttribute(name, value) + } + + /** + * Manipulate the attributes of the last route built with the given consumer. + * + * The map provided to the consumer is "live", so that the consumer can be used + * to [overwrite][Map.put] existing attributes, + * [remove][Map.remove] attributes, or use any of the other + * [Map] methods. + * @param attributesConsumer a function that consumes the attributes map + * @since 5.3 + */ + fun withAttributes(attributesConsumer: (MutableMap) -> Unit) { + builder.withAttributes(attributesConsumer) + } + /** * Return a composed routing function created from all the registered routes. */ 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 38e2fbe8771..c1df5a1b364 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -702,6 +702,30 @@ class RouterFunctionDsl internal constructor (private val init: RouterFunctionDs builder.onError({it is E}, responseProvider) } + /** + * Add an attribute with the given name and value to the last route built with this builder. + * @param name the attribute name + * @param value the attribute value + * @since 5.3 + */ + fun withAttribute(name: String, value: Any) { + builder.withAttribute(name, value) + } + + /** + * Manipulate the attributes of the last route built with the given consumer. + * + * The map provided to the consumer is "live", so that the consumer can be used + * to [overwrite][Map.put] existing attributes, + * [remove][Map.remove] attributes, or use any of the other + * [Map] methods. + * @param attributesConsumer a function that consumes the attributes map + * @since 5.3 + */ + fun withAttributes(attributesConsumer: (MutableMap) -> Unit) { + builder.withAttributes(attributesConsumer) + } + /** * Return a composed routing function created from all the registered routes. * @since 5.1 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 bdeae8b00af..041956cefa3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,7 @@ package org.springframework.web.reactive.function.server +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.io.ClassPathResource @@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.MediaType.* import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.* import org.springframework.web.testfixture.server.MockServerWebExchange +import org.springframework.web.reactive.function.server.AttributesTestVisitor import reactor.test.StepVerifier /** @@ -163,6 +165,20 @@ class CoRouterFunctionDslTests { .verifyComplete() } + @Test + fun attributes() { + val visitor = AttributesTestVisitor() + attributesRouter.accept(visitor) + assertThat(visitor.routerFunctionsAttributes()).containsExactly( + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar"), mapOf("foo" to "n1")), + listOf(mapOf("baz" to "qux"), mapOf("foo" to "n1")), + listOf(mapOf("foo" to "n3"), mapOf("foo" to "n2"), mapOf("foo" to "n1")) + ); + assertThat(visitor.visitCount()).isEqualTo(7); + } + private fun sampleRouter() = coRouter { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } "/api".nest { @@ -231,6 +247,39 @@ class CoRouterFunctionDslTests { } } + private val attributesRouter = router { + GET("/atts/1") { + ok().build() + } + withAttribute("foo", "bar") + withAttribute("baz", "qux") + GET("/atts/2") { + ok().build() + } + withAttributes { atts -> + atts["foo"] = "bar" + atts["baz"] = "qux" + } + "/atts".nest { + GET("/3") { + ok().build() + } + withAttribute("foo", "bar") + GET("/4") { + ok().build() + } + withAttribute("baz", "qux") + "/5".nest { + GET { + ok().build() + } + withAttribute("foo", "n3") + } + withAttribute("foo", "n2") + } + withAttribute("foo", "n1") + } + @Suppress("UNUSED_PARAMETER") private suspend fun handleFromClass(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 a050776f48a..4392b04fbf1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,7 @@ package org.springframework.web.reactive.function.server +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.io.ClassPathResource @@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.MediaType.* import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.* import org.springframework.web.testfixture.server.MockServerWebExchange +import org.springframework.web.reactive.function.server.AttributesTestVisitor import reactor.core.publisher.Mono import reactor.test.StepVerifier @@ -153,6 +155,19 @@ class RouterFunctionDslTests { } } + @Test + fun attributes() { + val visitor = AttributesTestVisitor() + attributesRouter.accept(visitor) + assertThat(visitor.routerFunctionsAttributes()).containsExactly( + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar"), mapOf("foo" to "n1")), + listOf(mapOf("baz" to "qux"), mapOf("foo" to "n1")), + listOf(mapOf("foo" to "n3"), mapOf("foo" to "n2"), mapOf("foo" to "n1")) + ); + assertThat(visitor.visitCount()).isEqualTo(7); + } private fun sampleRouter() = router { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } @@ -210,6 +225,39 @@ class RouterFunctionDslTests { } } + private val attributesRouter = router { + GET("/atts/1") { + ok().build() + } + withAttribute("foo", "bar") + withAttribute("baz", "qux") + GET("/atts/2") { + ok().build() + } + withAttributes { atts -> + atts["foo"] = "bar" + atts["baz"] = "qux" + } + "/atts".nest { + GET("/3") { + ok().build() + } + withAttribute("foo", "bar") + GET("/4") { + ok().build() + } + withAttribute("baz", "qux") + "/5".nest { + GET { + ok().build() + } + withAttribute("foo", "n3") + } + withAttribute("foo", "n2") + } + withAttribute("foo", "n1") + } + @Suppress("UNUSED_PARAMETER") private fun handleFromClass(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 391e824cb2b..d5c0ecba007 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -699,6 +699,30 @@ class RouterFunctionDsl internal constructor (private val init: (RouterFunctionD builder.onError({it is E}, responseProvider) } + /** + * Add an attribute with the given name and value to the last route built with this builder. + * @param name the attribute name + * @param value the attribute value + * @since 5.3 + */ + fun withAttribute(name: String, value: Any) { + builder.withAttribute(name, value) + } + + /** + * Manipulate the attributes of the last route built with the given consumer. + * + * The map provided to the consumer is "live", so that the consumer can be used + * to [overwrite][Map.put] existing attributes, + * [remove][Map.remove] attributes, or use any of the other + * [Map] methods. + * @param attributesConsumer a function that consumes the attributes map + * @since 5.3 + */ + fun withAttributes(attributesConsumer: (MutableMap) -> Unit) { + builder.withAttributes(attributesConsumer) + } + /** * Return a composed routing function created from all the registered routes. */ 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 750d05d01e3..ccfb300b39e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -134,6 +134,20 @@ class RouterFunctionDslTests { assertThat(sampleRouter().route(request).get().handle(request).headers().getFirst("foo")).isEqualTo("bar") } + @Test + fun attributes() { + val visitor = AttributesTestVisitor() + attributesRouter.accept(visitor) + assertThat(visitor.routerFunctionsAttributes()).containsExactly( + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar", "baz" to "qux")), + listOf(mapOf("foo" to "bar"), mapOf("foo" to "n1")), + listOf(mapOf("baz" to "qux"), mapOf("foo" to "n1")), + listOf(mapOf("foo" to "n3"), mapOf("foo" to "n2"), mapOf("foo" to "n1")) + ); + assertThat(visitor.visitCount()).isEqualTo(7); + } + private fun sampleRouter() = router { (GET("/foo/") or GET("/foos/")) { req -> handle(req) } "/api".nest { @@ -202,6 +216,39 @@ class RouterFunctionDslTests { } } + private val attributesRouter = router { + GET("/atts/1") { + ok().build() + } + withAttribute("foo", "bar") + withAttribute("baz", "qux") + GET("/atts/2") { + ok().build() + } + withAttributes { atts -> + atts["foo"] = "bar" + atts["baz"] = "qux" + } + "/atts".nest { + GET("/3") { + ok().build() + } + withAttribute("foo", "bar") + GET("/4") { + ok().build() + } + withAttribute("baz", "qux") + "/5".nest { + GET { + ok().build() + } + withAttribute("foo", "n3") + } + withAttribute("foo", "n2") + } + withAttribute("foo", "n1") + } + @Suppress("UNUSED_PARAMETER") private fun handleFromClass(req: ServerRequest) = ServerResponse.ok().build() }