Skip to content

Commit 283bc9e

Browse files
committed
Merge pull request #28569 from christophejan:fix/28567
* gh-28567: Polish Add minimal Kotlin DSL RouterFunction attributes support Add test case on nested RouterFunction attributes
2 parents 282c3bc + 216a266 commit 283bc9e

File tree

12 files changed

+362
-35
lines changed

12 files changed

+362
-35
lines changed

spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDsl.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -586,6 +586,30 @@ class CoRouterFunctionDsl internal constructor (private val init: (CoRouterFunct
586586
}
587587
}
588588

589+
/**
590+
* Add an attribute with the given name and value to the last route built with this builder.
591+
* @param name the attribute name
592+
* @param value the attribute value
593+
- * @since 6.0
594+
*/
595+
fun withAttribute(name: String, value: Any) {
596+
builder.withAttribute(name, value)
597+
}
598+
599+
/**
600+
* Manipulate the attributes of the last route built with the given consumer.
601+
*
602+
* The map provided to the consumer is "live", so that the consumer can be used
603+
* to [overwrite][MutableMap.put] existing attributes,
604+
* [remove][MutableMap.remove] attributes, or use any of the other
605+
* [MutableMap] methods.
606+
* @param attributesConsumer a function that consumes the attributes map
607+
* @since 6.0
608+
*/
609+
fun withAttributes(attributesConsumer: (MutableMap<String, Any>) -> Unit) {
610+
builder.withAttributes(attributesConsumer)
611+
}
612+
589613
/**
590614
* Return a composed routing function created from all the registered routes.
591615
*/

spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -702,6 +702,30 @@ class RouterFunctionDsl internal constructor (private val init: RouterFunctionDs
702702
builder.onError({it is E}, responseProvider)
703703
}
704704

705+
/**
706+
* Add an attribute with the given name and value to the last route built with this builder.
707+
* @param name the attribute name
708+
* @param value the attribute value
709+
* @since 6.0
710+
*/
711+
fun withAttribute(name: String, value: Any) {
712+
builder.withAttribute(name, value)
713+
}
714+
715+
/**
716+
* Manipulate the attributes of the last route built with the given consumer.
717+
*
718+
* The map provided to the consumer is "live", so that the consumer can be used
719+
* to [overwrite][MutableMap.put] existing attributes,
720+
* [remove][MutableMap.remove] attributes, or use any of the other
721+
* [MutableMap] methods.
722+
* @param attributesConsumer a function that consumes the attributes map
723+
* @since 6.0
724+
*/
725+
fun withAttributes(attributesConsumer: (MutableMap<String, Any>) -> Unit) {
726+
builder.withAttributes(attributesConsumer)
727+
}
728+
705729
/**
706730
* Return a composed routing function created from all the registered routes.
707731
* @since 5.1

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/AttributesTestVisitor.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,43 +16,60 @@
1616

1717
package org.springframework.web.reactive.function.server;
1818

19+
import java.util.Deque;
20+
import java.util.LinkedList;
21+
import java.util.List;
1922
import java.util.Map;
23+
import java.util.Objects;
24+
import java.util.Optional;
2025
import java.util.function.Function;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
2128

2229
import reactor.core.publisher.Mono;
2330

2431
import org.springframework.core.io.Resource;
2532
import org.springframework.lang.Nullable;
2633

27-
import static org.assertj.core.api.Assertions.assertThat;
28-
import static org.assertj.core.api.Assertions.entry;
29-
3034
/**
3135
* @author Arjen Poutsma
3236
*/
3337
class AttributesTestVisitor implements RouterFunctions.Visitor {
3438

39+
private Deque<Map<String, Object>> nestedAttributes = new LinkedList<>();
40+
3541
@Nullable
3642
private Map<String, Object> attributes;
3743

44+
private List<List<Map<String, Object>>> routerFunctionsAttributes = new LinkedList<>();
45+
3846
private int visitCount;
3947

48+
public List<List<Map<String, Object>>> routerFunctionsAttributes() {
49+
return this.routerFunctionsAttributes;
50+
}
51+
4052
public int visitCount() {
4153
return this.visitCount;
4254
}
4355

4456
@Override
4557
public void startNested(RequestPredicate predicate) {
58+
nestedAttributes.addFirst(attributes);
59+
attributes = null;
4660
}
4761

4862
@Override
4963
public void endNested(RequestPredicate predicate) {
64+
attributes = nestedAttributes.removeFirst();
5065
}
5166

5267
@Override
5368
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) {
54-
assertThat(this.attributes).isNotNull();
55-
this.attributes = null;
69+
Stream<Map<String, Object>> current = Optional.ofNullable(attributes).stream();
70+
Stream<Map<String, Object>> nested = nestedAttributes.stream().filter(Objects::nonNull);
71+
routerFunctionsAttributes.add(Stream.concat(current, nested).collect(Collectors.toUnmodifiableList()));
72+
attributes = null;
5673
}
5774

5875
@Override
@@ -61,7 +78,6 @@ public void resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
6178

6279
@Override
6380
public void attributes(Map<String, Object> attributes) {
64-
assertThat(attributes).containsExactly(entry("foo", "bar"), entry("baz", "qux"));
6581
this.attributes = attributes;
6682
this.visitCount++;
6783
}

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
1818

1919
import java.io.IOException;
2020
import java.util.Collections;
21+
import java.util.List;
22+
import java.util.Map;
2123
import java.util.concurrent.atomic.AtomicInteger;
2224

2325
import org.junit.jupiter.api.Test;
@@ -236,12 +238,28 @@ public void attributes() {
236238
atts.put("foo", "bar");
237239
atts.put("baz", "qux");
238240
})
241+
.path("/atts", b1 -> b1
242+
.GET("/3", request -> ServerResponse.ok().build())
243+
.withAttribute("foo", "bar")
244+
.GET("/4", request -> ServerResponse.ok().build())
245+
.withAttribute("baz", "qux")
246+
.path("/5", b2 -> b2
247+
.GET(request -> ServerResponse.ok().build())
248+
.withAttribute("foo", "n3"))
249+
.withAttribute("foo", "n2")
250+
)
251+
.withAttribute("foo", "n1")
239252
.build();
240253

241254
AttributesTestVisitor visitor = new AttributesTestVisitor();
242255
route.accept(visitor);
243-
assertThat(visitor.visitCount()).isEqualTo(2);
256+
assertThat(visitor.routerFunctionsAttributes()).containsExactly(
257+
List.of(Map.of("foo", "bar", "baz", "qux")),
258+
List.of(Map.of("foo", "bar", "baz", "qux")),
259+
List.of(Map.of("foo", "bar"), Map.of("foo", "n1")),
260+
List.of(Map.of("baz", "qux"), Map.of("foo", "n1")),
261+
List.of(Map.of("foo", "n3"), Map.of("foo", "n2"), Map.of("foo", "n1"))
262+
);
263+
assertThat(visitor.visitCount()).isEqualTo(7);
244264
}
245-
246-
247265
}

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionTests.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
1717
package org.springframework.web.reactive.function.server;
1818

1919
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Map;
2022

2123
import org.junit.jupiter.api.Test;
2224
import reactor.core.publisher.Mono;
@@ -26,7 +28,10 @@
2628
import org.springframework.web.testfixture.server.MockServerWebExchange;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.springframework.http.HttpMethod.GET;
2932
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
33+
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
34+
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
3035

3136
/**
3237
* @author Arjen Poutsma
@@ -137,11 +142,28 @@ public void attributes() {
137142
.withAttributes(atts -> {
138143
atts.put("foo", "bar");
139144
atts.put("baz", "qux");
140-
}));
145+
}))
146+
.and(RouterFunctions.nest(path("/atts"),
147+
RouterFunctions.route(GET("/3"), request -> ServerResponse.ok().build())
148+
.withAttribute("foo", "bar")
149+
.and(RouterFunctions.route(GET("/4"), request -> ServerResponse.ok().build())
150+
.withAttribute("baz", "qux"))
151+
.and(RouterFunctions.nest(path("/5"),
152+
RouterFunctions.route(method(GET), request -> ServerResponse.ok().build())
153+
.withAttribute("foo", "n3"))
154+
.withAttribute("foo", "n2")))
155+
.withAttribute("foo", "n1"));
141156

142157
AttributesTestVisitor visitor = new AttributesTestVisitor();
143158
route.accept(visitor);
144-
assertThat(visitor.visitCount()).isEqualTo(2);
159+
assertThat(visitor.routerFunctionsAttributes()).containsExactly(
160+
List.of(Map.of("foo", "bar", "baz", "qux")),
161+
List.of(Map.of("foo", "bar", "baz", "qux")),
162+
List.of(Map.of("foo", "bar"), Map.of("foo", "n1")),
163+
List.of(Map.of("baz", "qux"), Map.of("foo", "n1")),
164+
List.of(Map.of("foo", "n3"), Map.of("foo", "n2"), Map.of("foo", "n1"))
165+
);
166+
assertThat(visitor.visitCount()).isEqualTo(7);
145167
}
146168

147169

spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/CoRouterFunctionDslTests.kt

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.function.server
1818

19+
import org.assertj.core.api.Assertions.assertThat
1920
import org.assertj.core.api.Assertions.assertThatExceptionOfType
2021
import org.junit.jupiter.api.Test
2122
import org.springframework.core.io.ClassPathResource
@@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus
2526
import org.springframework.http.MediaType.*
2627
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.*
2728
import org.springframework.web.testfixture.server.MockServerWebExchange
29+
import org.springframework.web.reactive.function.server.AttributesTestVisitor
2830
import reactor.test.StepVerifier
2931

3032
/**
@@ -163,6 +165,20 @@ class CoRouterFunctionDslTests {
163165
.verifyComplete()
164166
}
165167

168+
@Test
169+
fun attributes() {
170+
val visitor = AttributesTestVisitor()
171+
attributesRouter.accept(visitor)
172+
assertThat(visitor.routerFunctionsAttributes()).containsExactly(
173+
listOf(mapOf("foo" to "bar", "baz" to "qux")),
174+
listOf(mapOf("foo" to "bar", "baz" to "qux")),
175+
listOf(mapOf("foo" to "bar"), mapOf("foo" to "n1")),
176+
listOf(mapOf("baz" to "qux"), mapOf("foo" to "n1")),
177+
listOf(mapOf("foo" to "n3"), mapOf("foo" to "n2"), mapOf("foo" to "n1"))
178+
);
179+
assertThat(visitor.visitCount()).isEqualTo(7);
180+
}
181+
166182
private fun sampleRouter() = coRouter {
167183
(GET("/foo/") or GET("/foos/")) { req -> handle(req) }
168184
"/api".nest {
@@ -231,6 +247,39 @@ class CoRouterFunctionDslTests {
231247
}
232248
}
233249

250+
private val attributesRouter = router {
251+
GET("/atts/1") {
252+
ok().build()
253+
}
254+
withAttribute("foo", "bar")
255+
withAttribute("baz", "qux")
256+
GET("/atts/2") {
257+
ok().build()
258+
}
259+
withAttributes { atts ->
260+
atts["foo"] = "bar"
261+
atts["baz"] = "qux"
262+
}
263+
"/atts".nest {
264+
GET("/3") {
265+
ok().build()
266+
}
267+
withAttribute("foo", "bar")
268+
GET("/4") {
269+
ok().build()
270+
}
271+
withAttribute("baz", "qux")
272+
"/5".nest {
273+
GET {
274+
ok().build()
275+
}
276+
withAttribute("foo", "n3")
277+
}
278+
withAttribute("foo", "n2")
279+
}
280+
withAttribute("foo", "n1")
281+
}
282+
234283
@Suppress("UNUSED_PARAMETER")
235284
private suspend fun handleFromClass(req: ServerRequest) = ServerResponse.ok().buildAndAwait()
236285
}

0 commit comments

Comments
 (0)