23 changed files with 986 additions and 23 deletions
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
/* |
||||
* Copyright 2002-present 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.reactive.function.server.support; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Function; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.web.reactive.accept.DefaultApiVersionStrategy; |
||||
import org.springframework.web.reactive.function.server.HandlerFunction; |
||||
import org.springframework.web.reactive.function.server.RequestPredicate; |
||||
import org.springframework.web.reactive.function.server.RequestPredicates; |
||||
import org.springframework.web.reactive.function.server.RouterFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||
import org.springframework.web.reactive.function.server.ServerRequest; |
||||
|
||||
/** |
||||
* {@link RequestPredicates.Visitor} that discovers versions used in routes in |
||||
* order to add them to the list of supported versions. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 7.0 |
||||
*/ |
||||
final class SupportedVersionVisitor implements RouterFunctions.Visitor, RequestPredicates.Visitor { |
||||
|
||||
private final DefaultApiVersionStrategy versionStrategy; |
||||
|
||||
|
||||
SupportedVersionVisitor(DefaultApiVersionStrategy versionStrategy) { |
||||
this.versionStrategy = versionStrategy; |
||||
} |
||||
|
||||
|
||||
// RouterFunctions.Visitor
|
||||
|
||||
@Override |
||||
public void startNested(RequestPredicate predicate) { |
||||
predicate.accept(this); |
||||
} |
||||
|
||||
@Override |
||||
public void endNested(RequestPredicate predicate) { |
||||
} |
||||
|
||||
@Override |
||||
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) { |
||||
predicate.accept(this); |
||||
} |
||||
|
||||
@Override |
||||
public void resources(Function<ServerRequest, Mono<Resource>> lookupFunction) { |
||||
} |
||||
|
||||
@Override |
||||
public void attributes(Map<String, Object> attributes) { |
||||
} |
||||
|
||||
@Override |
||||
public void unknown(RouterFunction<?> routerFunction) { |
||||
} |
||||
|
||||
|
||||
// RequestPredicates.Visitor
|
||||
|
||||
@Override |
||||
public void method(Set<HttpMethod> methods) { |
||||
} |
||||
|
||||
@Override |
||||
public void path(String pattern) { |
||||
} |
||||
|
||||
@SuppressWarnings("removal") |
||||
@Override |
||||
public void pathExtension(String extension) { |
||||
} |
||||
|
||||
@Override |
||||
public void header(String name, String value) { |
||||
} |
||||
|
||||
@Override |
||||
public void queryParam(String name, String value) { |
||||
} |
||||
|
||||
@Override |
||||
public void version(String version) { |
||||
this.versionStrategy.addMappedVersion( |
||||
(version.endsWith("+") ? version.substring(0, version.length() - 1) : version)); |
||||
} |
||||
|
||||
@Override |
||||
public void startAnd() { |
||||
} |
||||
|
||||
@Override |
||||
public void and() { |
||||
} |
||||
|
||||
@Override |
||||
public void endAnd() { |
||||
} |
||||
|
||||
@Override |
||||
public void startOr() { |
||||
} |
||||
|
||||
@Override |
||||
public void or() { |
||||
} |
||||
|
||||
@Override |
||||
public void endOr() { |
||||
} |
||||
|
||||
@Override |
||||
public void startNegate() { |
||||
} |
||||
|
||||
@Override |
||||
public void endNegate() { |
||||
} |
||||
|
||||
@Override |
||||
public void unknown(RequestPredicate predicate) { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright 2002-present 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.reactive.function.server.support; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.web.reactive.config.ApiVersionConfigurer; |
||||
import org.springframework.web.reactive.config.EnableWebFlux; |
||||
import org.springframework.web.reactive.config.WebFluxConfigurer; |
||||
import org.springframework.web.reactive.function.server.HandlerFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunction; |
||||
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||
import org.springframework.web.reactive.function.server.ServerRequest; |
||||
import org.springframework.web.reactive.function.server.ServerResponse; |
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; |
||||
import org.springframework.web.testfixture.server.MockServerWebExchange; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.version; |
||||
|
||||
/** |
||||
* {@link RouterFunctionMapping} integration tests for API versioning. |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class RouterFunctionMappingVersionTests { |
||||
|
||||
private RouterFunctionMapping mapping; |
||||
|
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); |
||||
wac.register(WebConfig.class); |
||||
wac.refresh(); |
||||
|
||||
this.mapping = wac.getBean(RouterFunctionMapping.class); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
void mapVersion() { |
||||
testGetHandler("1.0", "none"); |
||||
testGetHandler("1.1", "none"); |
||||
testGetHandler("1.2", "1.2"); |
||||
testGetHandler("1.3", "1.2"); |
||||
testGetHandler("1.5", "1.5"); |
||||
} |
||||
|
||||
|
||||
private void testGetHandler(String version, String expectedBody) { |
||||
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from( |
||||
MockServerHttpRequest.get("/").header("X-API-Version", version)); |
||||
|
||||
Mono<?> result = this.mapping.getHandler(exchange); |
||||
|
||||
StepVerifier.create(result) |
||||
.consumeNextWith(handler -> assertThat(((TestHandler) handler).body()).isEqualTo(expectedBody)) |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
|
||||
@EnableWebFlux |
||||
private static class WebConfig implements WebFluxConfigurer { |
||||
|
||||
@Override |
||||
public void configureApiVersioning(ApiVersionConfigurer configurer) { |
||||
configurer.useRequestHeader("X-API-Version").addSupportedVersions("1", "1.1", "1.3"); |
||||
} |
||||
|
||||
@Bean |
||||
RouterFunction<?> routerFunction() { |
||||
return RouterFunctions.route() |
||||
.path("/", builder -> builder |
||||
.GET(version("1.5"), new TestHandler("1.5")) |
||||
.GET(version("1.2+"), new TestHandler("1.2")) |
||||
.GET(new TestHandler("none"))) |
||||
.build(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private record TestHandler(String body) implements HandlerFunction<ServerResponse> { |
||||
|
||||
@Override |
||||
public Mono<ServerResponse> handle(ServerRequest request) { |
||||
return ServerResponse.ok().bodyValue(body); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
/* |
||||
* Copyright 2002-present 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.support; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.Set; |
||||
import java.util.function.Function; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.web.accept.DefaultApiVersionStrategy; |
||||
import org.springframework.web.servlet.function.HandlerFunction; |
||||
import org.springframework.web.servlet.function.RequestPredicate; |
||||
import org.springframework.web.servlet.function.RequestPredicates; |
||||
import org.springframework.web.servlet.function.RouterFunction; |
||||
import org.springframework.web.servlet.function.RouterFunctions; |
||||
import org.springframework.web.servlet.function.ServerRequest; |
||||
|
||||
/** |
||||
* {@link RequestPredicates.Visitor} that discovers versions used in routes in |
||||
* order to add them to the list of supported versions. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 7.0 |
||||
*/ |
||||
final class SupportedVersionVisitor implements RouterFunctions.Visitor, RequestPredicates.Visitor { |
||||
|
||||
private final DefaultApiVersionStrategy versionStrategy; |
||||
|
||||
|
||||
SupportedVersionVisitor(DefaultApiVersionStrategy versionStrategy) { |
||||
this.versionStrategy = versionStrategy; |
||||
} |
||||
|
||||
|
||||
// RouterFunctions.Visitor
|
||||
|
||||
@Override |
||||
public void startNested(RequestPredicate predicate) { |
||||
predicate.accept(this); |
||||
} |
||||
|
||||
@Override |
||||
public void endNested(RequestPredicate predicate) { |
||||
} |
||||
|
||||
@Override |
||||
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) { |
||||
predicate.accept(this); |
||||
} |
||||
|
||||
@Override |
||||
public void resources(Function<ServerRequest, Optional<Resource>> lookupFunction) { |
||||
} |
||||
|
||||
@Override |
||||
public void attributes(Map<String, Object> attributes) { |
||||
} |
||||
|
||||
@Override |
||||
public void unknown(RouterFunction<?> routerFunction) { |
||||
} |
||||
|
||||
|
||||
// RequestPredicates.Visitor
|
||||
|
||||
@Override |
||||
public void method(Set<HttpMethod> methods) { |
||||
} |
||||
|
||||
@Override |
||||
public void path(String pattern) { |
||||
} |
||||
|
||||
@SuppressWarnings("removal") |
||||
@Override |
||||
public void pathExtension(String extension) { |
||||
} |
||||
|
||||
@Override |
||||
public void header(String name, String value) { |
||||
} |
||||
|
||||
@Override |
||||
public void param(String name, String value) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void version(String version) { |
||||
this.versionStrategy.addMappedVersion( |
||||
(version.endsWith("+") ? version.substring(0, version.length() - 1) : version)); |
||||
} |
||||
|
||||
@Override |
||||
public void startAnd() { |
||||
} |
||||
|
||||
@Override |
||||
public void and() { |
||||
} |
||||
|
||||
@Override |
||||
public void endAnd() { |
||||
} |
||||
|
||||
@Override |
||||
public void startOr() { |
||||
} |
||||
|
||||
@Override |
||||
public void or() { |
||||
} |
||||
|
||||
@Override |
||||
public void endOr() { |
||||
} |
||||
|
||||
@Override |
||||
public void startNegate() { |
||||
} |
||||
|
||||
@Override |
||||
public void endNegate() { |
||||
} |
||||
|
||||
@Override |
||||
public void unknown(RequestPredicate predicate) { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
/* |
||||
* Copyright 2002-present 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.support; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
||||
import org.springframework.web.servlet.config.annotation.ApiVersionConfigurer; |
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
import org.springframework.web.servlet.function.HandlerFunction; |
||||
import org.springframework.web.servlet.function.RouterFunction; |
||||
import org.springframework.web.servlet.function.RouterFunctions; |
||||
import org.springframework.web.servlet.function.ServerRequest; |
||||
import org.springframework.web.servlet.function.ServerResponse; |
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest; |
||||
import org.springframework.web.testfixture.servlet.MockServletContext; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.springframework.web.servlet.function.RequestPredicates.version; |
||||
|
||||
/** |
||||
* {@link RouterFunctionMapping} integration tests for API versioning. |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class RouterFunctionMappingVersionTests { |
||||
|
||||
private final MockServletContext servletContext = new MockServletContext(); |
||||
|
||||
private RouterFunctionMapping mapping; |
||||
|
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
AnnotationConfigWebApplicationContext wac = new AnnotationConfigWebApplicationContext(); |
||||
wac.setServletContext(this.servletContext); |
||||
wac.register(WebConfig.class); |
||||
wac.refresh(); |
||||
|
||||
this.mapping = wac.getBean(RouterFunctionMapping.class); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
void mapVersion() throws Exception { |
||||
testGetHandler("1.0", "none"); |
||||
testGetHandler("1.1", "none"); |
||||
testGetHandler("1.2", "1.2"); |
||||
testGetHandler("1.3", "1.2"); |
||||
testGetHandler("1.5", "1.5"); |
||||
} |
||||
|
||||
|
||||
private void testGetHandler(String version, String expectedBody) throws Exception { |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); |
||||
request.addHeader("X-API-Version", version); |
||||
HandlerFunction<?> handler = (HandlerFunction<?>) this.mapping.getHandler(request).getHandler(); |
||||
assertThat(((TestHandler) handler).body()).isEqualTo(expectedBody); |
||||
} |
||||
|
||||
|
||||
@EnableWebMvc |
||||
private static class WebConfig implements WebMvcConfigurer { |
||||
|
||||
@Override |
||||
public void configureApiVersioning(ApiVersionConfigurer configurer) { |
||||
configurer.useRequestHeader("X-API-Version").addSupportedVersions("1", "1.1", "1.3"); |
||||
} |
||||
|
||||
@Bean |
||||
RouterFunction<?> routerFunction() { |
||||
return RouterFunctions.route() |
||||
.path("/", builder -> builder |
||||
.GET(version("1.5"), new TestHandler("1.5")) |
||||
.GET(version("1.2+"), new TestHandler("1.2")) |
||||
.GET(new TestHandler("none"))) |
||||
.build(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private record TestHandler(String body) implements HandlerFunction<ServerResponse> { |
||||
|
||||
@Override |
||||
public ServerResponse handle(ServerRequest request) { |
||||
return ServerResponse.ok().body(body); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue