From 8202052b389b47dbf3be1678f3e22291b99b8334 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 28 Jun 2018 11:04:34 +0200 Subject: [PATCH] Introduce RouterFunction builder This commit introduces RouterFunctions.Builder, a new way to build router functions that does not require static imports, thus being more discoverable and convenient. Issue: SPR-16953 --- .../server/RouterFunctionBuilder.java | 212 +++++++++++ .../function/server/RouterFunctions.java | 330 ++++++++++++++++++ .../server/RouterFunctionBuilderTests.java | 163 +++++++++ 3 files changed, 705 insertions(+) create mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java create mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java new file mode 100644 index 0000000000..d88ac793d0 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java @@ -0,0 +1,212 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.reactive.function.server; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import reactor.core.publisher.Mono; + +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; + +/** + * Default implementation of {@link RouterFunctions.Builder}. + * @author Arjen Poutsma + * @since 5.1 + */ +class RouterFunctionBuilder implements RouterFunctions.Builder { + + private List> routerFunctions = new ArrayList<>(); + + private List> filterFunctions = new ArrayList<>(); + + + @Override + public RouterFunctions.Builder route(RequestPredicate predicate, + HandlerFunction handlerFunction) { + this.routerFunctions.add(RouterFunctions.route(predicate, handlerFunction)); + return this; + } + + @Override + public RouterFunctions.Builder routeGet(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.GET), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeGet(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.GET(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeHead(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.HEAD), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeHead(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.HEAD(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePost(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.POST), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePost(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.POST(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePut(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.PUT), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePut(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.PUT(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePatch(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.PATCH), handlerFunction); + } + + @Override + public RouterFunctions.Builder routePatch(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.PATCH(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeDelete(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.DELETE), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeDelete(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.DELETE(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeOptions(HandlerFunction handlerFunction) { + return route(RequestPredicates.method(HttpMethod.OPTIONS), handlerFunction); + } + + @Override + public RouterFunctions.Builder routeOptions(String pattern, HandlerFunction handlerFunction) { + return route(RequestPredicates.OPTIONS(pattern), handlerFunction); + } + + @Override + public RouterFunctions.Builder nest(RequestPredicate predicate, + Consumer builderConsumer) { + + Assert.notNull(builderConsumer, "'builderConsumer' must not be null"); + + RouterFunctionBuilder nestedBuilder = new RouterFunctionBuilder(); + builderConsumer.accept(nestedBuilder); + RouterFunction nestedRoute = nestedBuilder.build(); + + this.routerFunctions.add(RouterFunctions.nest(predicate, nestedRoute)); + return this; + } + + @Override + public RouterFunctions.Builder nest(RequestPredicate predicate, + Supplier> routerFunctionSupplier) { + + Assert.notNull(routerFunctionSupplier, "'routerFunctionSupplier' must not be null"); + + RouterFunction nestedRoute = routerFunctionSupplier.get(); + + this.routerFunctions.add(RouterFunctions.nest(predicate, nestedRoute)); + return this; + } + + @Override + public RouterFunctions.Builder nestPath(String pattern, + Consumer builderConsumer) { + return nest(RequestPredicates.path(pattern), builderConsumer); + } + + @Override + public RouterFunctions.Builder nestPath(String pattern, + Supplier> routerFunctionSupplier) { + return nest(RequestPredicates.path(pattern), routerFunctionSupplier); + } + + @Override + public RouterFunctions.Builder filter(HandlerFilterFunction filterFunction) { + Assert.notNull(filterFunction, "'filterFunction' must not be null"); + + this.filterFunctions.add(filterFunction); + return this; + } + + @Override + public RouterFunctions.Builder before( + Function> requestProcessor) { + + Assert.notNull(requestProcessor, "Function must not be null"); + return filter((request, next) -> requestProcessor.apply(request).flatMap(next::handle)); + } + + @Override + public RouterFunctions.Builder after( + BiFunction> responseProcessor) { + return filter((request, next) -> next.handle(request) + .flatMap(serverResponse -> responseProcessor.apply(request, serverResponse))); + } + + @Override + public RouterFunctions.Builder exception( + Class exceptionType, + BiFunction> fallback) { + Assert.notNull(exceptionType, "'exceptionType' must not be null"); + Assert.notNull(fallback, "'fallback' must not be null"); + + return filter((request, next) -> next.handle(request) + .onErrorResume(exceptionType, t -> fallback.apply(t, request))); + } + + @Override + public RouterFunction build() { + + RouterFunction result = this.routerFunctions.stream() + .reduce(RouterFunction::and) + .orElseThrow(IllegalStateException::new); + + if (this.filterFunctions.isEmpty()) { + return result; + } + else { + HandlerFilterFunction filter = + this.filterFunctions.stream() + .reduce(HandlerFilterFunction::andThen) + .orElseThrow(IllegalStateException::new); + + return result.filter(filter); + } + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index 93589ba88d..2b24b923c1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -69,6 +71,14 @@ public abstract class RouterFunctions { private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build(); + /** + * Return a {@linkplain Builder builder} that offers a discoverable way to create router + * functions. + * @return a router function builder + */ + public static Builder builder() { + return new RouterFunctionBuilder(); + } /** * Route to the given handler function if the given request predicate applies. @@ -270,6 +280,326 @@ public abstract class RouterFunctions { return (HandlerFunction) handlerFunction; } + /** + * Represents a builder for router functions. + *

Each invocation of {@code route} creates a new {@link RouterFunction} that is + * {@linkplain RouterFunction#and(RouterFunction) composed} with any previously built functions. + * @since 5.1 + */ + public interface Builder { + + /** + * Adds a route to the given handler function that matches if the given request predicate + * applies. + *

For instance, the following example routes GET requests for "/user" to the + * {@code listUsers} method in {@code userController}: + *

+		 * RouterFunction<ServerResponse> route =
+		 *     RouterFunctions.builder()
+		 *     .route(RequestPredicates.GET("/user"), userController::listUsers)
+		 *     .build();
+		 * 
+ * @param predicate the predicate to test + * @param handlerFunction the handler function to route to if the predicate applies + * @return this builder + * @see RequestPredicates + */ + Builder route(RequestPredicate predicate, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code GET} requests. + * @param handlerFunction the handler function to handle all {@code GET} requests + * @return this builder + */ + Builder routeGet(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code GET} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code GET} requests that + * match {@code pattern} + * @return this builder + */ + Builder routeGet(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests. + * @param handlerFunction the handler function to handle all {@code HEAD} requests + * @return this builder + */ + Builder routeHead(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code HEAD} requests that + * match {@code pattern} + * @return this builder + */ + Builder routeHead(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code POST} requests. + * @param handlerFunction the handler function to handle all {@code POST} requests + * @return this builder + */ + Builder routePost(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code POST} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code POST} requests that + * match {@code pattern} + * @return this builder + */ + Builder routePost(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code PUT} requests. + * @param handlerFunction the handler function to handle all {@code PUT} requests + * @return this builder + */ + Builder routePut(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code PUT} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code PUT} requests that + * match {@code pattern} + * @return this builder + */ + Builder routePut(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests. + * @param handlerFunction the handler function to handle all {@code PATCH} requests + * @return this builder + */ + Builder routePatch(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code PATCH} requests that + * match {@code pattern} + * @return this builder + */ + Builder routePatch(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests. + * @param handlerFunction the handler function to handle all {@code DELETE} requests + * @return this builder + */ + Builder routeDelete(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code DELETE} requests that + * match {@code pattern} + * @return this builder + */ + Builder routeDelete(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests. + * @param handlerFunction the handler function to handle all {@code OPTIONS} requests + * @return this builder + */ + Builder routeOptions(HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests + * that match the given pattern. + * @param pattern the pattern to match to + * @param handlerFunction the handler function to handle all {@code OPTIONS} requests that + * match {@code pattern} + * @return this builder + */ + Builder routeOptions(String pattern, HandlerFunction handlerFunction); + + /** + * Route to the supplied 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. + *

For instance, the following example creates a nested route with a "/user" path + * predicate, so that GET requests for "/user" will list users, + * and POST request for "/user" will create a new user. + *

+		 * RouterFunction<ServerResponse> nestedRoute =
+		 *   RouterFunctions.builder()
+		 *     .nest(RequestPredicates.path("/user"), () ->
+		 *       RouterFunctions.builder()
+		 *       .routeGet(this::listUsers)
+		 *       .routePost(this::createUser);
+		 *       .build();
+		 *     )
+		 *     .build();
+		 * 
+ * @param predicate the predicate to test + * @param routerFunctionSupplier supplier for the nested router function to delegate to if + * the predicate applies + * @return this builder + * @see RequestPredicates + */ + Builder nest(RequestPredicate predicate, Supplier> routerFunctionSupplier); + + /** + * Route to a built 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. + *

For instance, the following example creates a nested route with a "/user" path + * predicate, so that GET requests for "/user" will list users, + * and POST request for "/user" will create a new user. + *

+		 * RouterFunction<ServerResponse> nestedRoute =
+		 *   RouterFunctions.builder()
+		 *     .nest(RequestPredicates.path("/user"), builder ->
+		 *       builder.routeGet(this::listUsers)
+		 *              .routePost(this::createUser);
+		 *     )
+		 *     .build();
+		 * 
+ * @param predicate the predicate to test + * @param builderConsumer consumer for a {@code Builder} that provides the nested router + * function + * @return this builder + * @see RequestPredicates + */ + Builder nest(RequestPredicate predicate, Consumer builderConsumer); + + /** + * Route to the supplied router function if the given path prefix pattern applies. This method + * can be used to create nested routes, where a group of routes share a + * common path (prefix). + *

For instance, the following example creates a nested route with a "/user" path + * predicate, so that GET requests for "/user" will list users, + * and POST request for "/user" will create a new user. + *

+		 * RouterFunction<ServerResponse> nestedRoute =
+		 *   RouterFunctions.builder()
+		 *     .nestPath("/user", () ->
+		 *       RouterFunctions.builder()
+		 *       .routeGet(this::listUsers)
+		 *       .routePost(this::createUser);
+		 *       .build();
+		 *     )
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param routerFunctionSupplier supplier for the nested router function to delegate to if + * the pattern matches + * @return this builder + */ + Builder nestPath(String pattern, Supplier> routerFunctionSupplier); + + /** + * Route to a built router function if the given path prefix pattern 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. + *

For instance, the following example creates a nested route with a "/user" path + * predicate, so that GET requests for "/user" will list users, + * and POST request for "/user" will create a new user. + *

+		 * RouterFunction<ServerResponse> nestedRoute =
+		 *   RouterFunctions.builder()
+		 *     .nestPath("/user", builder ->
+		 *       builder.routeGet(this::listUsers)
+		 *              .routePost(this::createUser);
+		 *     )
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param builderConsumer consumer for a {@code Builder} that provides the nested router + * function + * @return this builder + */ + Builder nestPath(String pattern, Consumer builderConsumer); + + /** + * Filters all routes created by this builder with the given filter function. Filter + * functions are typically used to address cross-cutting concerns, such as logging, + * security, etc. + *

For instance, the following example creates a filter that logs the request before + * the handler function executes, and logs the response after. + *

+		 * RouterFunction<ServerResponse> filteredRoute =
+		 *   RouterFunctions.builder()
+		 *     .routeGet("/user", this::listUsers)
+		 *     .filter((request, next) -> {
+		 *       log(request);
+         *       Mono<ServerResponse> responseMono = next.handle(request);
+		 *       return responseMono.doOnNext(response -> log(response);
+		 *     })
+		 *     .build();
+		 * 
+ * @param filterFunction the function to filter all routes built by this builder + * @return this builder + */ + Builder filter(HandlerFilterFunction filterFunction); + + /** + * Filters the request for all routes created by this builder with the given request + * processing function. Filters are typically used to address cross-cutting concerns, such + * as logging, security, etc. + *

For instance, the following example creates a filter that logs the request before + * the handler function executes. + *

+		 * RouterFunction<ServerResponse> filteredRoute =
+		 *   RouterFunctions.builder()
+		 *     .routeGet("/user", this::listUsers)
+		 *     .before(request -> {
+		 *       log(request);
+		 *       return Mono.just(request);
+		 *     })
+		 *     .build();
+		 * 
+ * @param requestProcessor a function that transforms the request + * @return this builder + */ + Builder before(Function> requestProcessor); + + /** + * Filters the response for all routes created by this builder with the given response + * processing function. Filters are typically used to address cross-cutting concerns, such + * as logging, security, etc. + *

For instance, the following example creates a filter that logs the response after + * the handler function executes. + *

+		 * RouterFunction<ServerResponse> filteredRoute =
+		 *   RouterFunctions.builder()
+		 *     .routeGet("/user", this::listUsers)
+		 *     .after((request, response) -> {
+		 *       log(response);
+		 *       return Mono.just(response);
+		 *     })
+		 *     .build();
+		 * 
+ * @param responseProcessor a function that transforms the response + * @return this builder + */ + Builder after(BiFunction> responseProcessor); + + Builder exception(Class exceptionType, + BiFunction> fallback); + + /** + * Builds the {@code RouterFunction}. All created routes are + * {@linkplain RouterFunction#and(RouterFunction) composed} with one another, and filters + * (f any) are applied to the result. + * @return the built router function + */ + RouterFunction build(); + + } + /** * Receives notifications from the logical structure of router functions. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java new file mode 100644 index 0000000000..b0b58f8683 --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.reactive.function.server; + +import java.net.URI; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; + +import static org.junit.Assert.*; + +/** + * @author Arjen Poutsma + */ +public class RouterFunctionBuilderTests { + + private RouterFunctionBuilder builder = new RouterFunctionBuilder(); + + @Test + public void route() { + RouterFunction route = this.builder + .routeGet("/foo", request -> ServerResponse.ok().build()) + .routePost(request -> ServerResponse.noContent().build()) + .build(); + + MockServerRequest fooRequest = MockServerRequest.builder(). + method(HttpMethod.GET). + uri(URI.create("http://localhost/foo")) + .build(); + + Mono responseMono = route.route(fooRequest) + .flatMap(handlerFunction -> handlerFunction.handle(fooRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .expectNext(200) + .verifyComplete(); + + MockServerRequest barRequest = MockServerRequest.builder(). + method(HttpMethod.POST). + uri(URI.create("http://localhost")) + .build(); + + responseMono = route.route(barRequest) + .flatMap(handlerFunction -> handlerFunction.handle(barRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .expectNext(204) + .verifyComplete(); + + } + + @Test + public void nest() { + RouterFunction route = this.builder + .nestPath("/foo", builder -> { + builder.nestPath("/bar", + () -> RouterFunctions.builder() + .routeGet("/baz", request -> ServerResponse.ok().build()) + .build()); + }) + .build(); + + MockServerRequest fooRequest = MockServerRequest.builder(). + method(HttpMethod.GET). + uri(URI.create("http://localhost/foo/bar/baz")) + .build(); + + Mono responseMono = route.route(fooRequest) + .flatMap(handlerFunction -> handlerFunction.handle(fooRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .expectNext(200) + .verifyComplete(); + } + + @Test + public void filters() { + AtomicInteger filterCount = new AtomicInteger(); + + RouterFunction route = this.builder + .routeGet("/foo", request -> ServerResponse.ok().build()) + .routeGet("/bar", request -> Mono.error(new IllegalStateException())) + .before(request -> { + int count = filterCount.getAndIncrement(); + assertEquals(0, count); + return Mono.just(request); + }) + .after((request, response) -> { + int count = filterCount.getAndIncrement(); + assertEquals(3, count); + return Mono.just(response); + }) + .filter((request, next) -> { + int count = filterCount.getAndIncrement(); + assertEquals(1, count); + Mono responseMono = next.handle(request); + count = filterCount.getAndIncrement(); + assertEquals(2, count); + return responseMono; + }) + .exception(IllegalStateException.class, (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()) + .build(); + + MockServerRequest fooRequest = MockServerRequest.builder(). + method(HttpMethod.GET). + uri(URI.create("http://localhost/foo")) + .build(); + + Mono fooResponseMono = route.route(fooRequest) + .flatMap(handlerFunction -> handlerFunction.handle(fooRequest)); + + + StepVerifier.create(fooResponseMono) + .consumeNextWith(serverResponse -> { + assertEquals(4, filterCount.get()); + }) + .verifyComplete(); + + filterCount.set(0); + + MockServerRequest barRequest = MockServerRequest.builder(). + method(HttpMethod.GET). + uri(URI.create("http://localhost/bar")) + .build(); + + + Mono barResponseMono = route.route(barRequest) + .flatMap(handlerFunction -> handlerFunction.handle(barRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(barResponseMono) + .expectNext(500) + .verifyComplete(); + + } + +} \ No newline at end of file