mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Add usePathSegment with Predicate<RequestPath>
See gh-36398 Signed-off-by: Martin Mois <martin.mois@gmail.com>
This commit is contained in:
+18
-1
@@ -16,13 +16,17 @@
|
||||
|
||||
package org.springframework.web.accept;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
|
||||
|
||||
/**
|
||||
* {@link ApiVersionResolver} that extract the version from a path segment.
|
||||
*
|
||||
@@ -37,6 +41,7 @@ import org.springframework.web.util.ServletRequestPathUtils;
|
||||
public class PathApiVersionResolver implements ApiVersionResolver {
|
||||
|
||||
private final int pathSegmentIndex;
|
||||
private @Nullable Predicate<RequestPath> includePath;
|
||||
|
||||
|
||||
/**
|
||||
@@ -49,13 +54,25 @@ public class PathApiVersionResolver implements ApiVersionResolver {
|
||||
this.pathSegmentIndex = pathSegmentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver instance.
|
||||
* @param pathSegmentIndex the index of the path segment that contains the API version
|
||||
* @param includePath a {@link Predicate} that tests if the given path should be included
|
||||
*/
|
||||
public PathApiVersionResolver(int pathSegmentIndex, Predicate<RequestPath> includePath) {
|
||||
this(pathSegmentIndex);
|
||||
this.includePath = includePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveVersion(HttpServletRequest request) {
|
||||
public @Nullable String resolveVersion(HttpServletRequest request) {
|
||||
if (!ServletRequestPathUtils.hasParsedRequestPath(request)) {
|
||||
throw new IllegalStateException("Expected parsed request path");
|
||||
}
|
||||
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
|
||||
if (this.includePath != null && !this.includePath.test(path)) {
|
||||
return null;
|
||||
}
|
||||
int i = 0;
|
||||
for (PathContainer.Element element : path.pathWithinApplication().elements()) {
|
||||
if (element instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
|
||||
|
||||
+56
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.web.accept;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
|
||||
@@ -41,6 +44,59 @@ public class PathApiVersionResolverTests {
|
||||
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathFalse() {
|
||||
String requestUri = "/v3/api-docs";
|
||||
testResolveWithIncludePath(requestUri, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathTrue() {
|
||||
String requestUri = "/app/1.0/path";
|
||||
testResolveWithIncludePath(requestUri, "1.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathFalseShortPath() {
|
||||
String requestUri = "/app";
|
||||
testResolveWithIncludePath(requestUri, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathInsufficientPathSegments() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/app");
|
||||
try {
|
||||
ServletRequestPathUtils.parseAndCache(request);
|
||||
assertThatThrownBy(() -> new PathApiVersionResolver(1, requestPath -> true)
|
||||
.resolveVersion(request))
|
||||
.isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
finally {
|
||||
ServletRequestPathUtils.clearParsedRequestPath(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testResolveWithIncludePath(String requestUri, String expected) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
try {
|
||||
ServletRequestPathUtils.parseAndCache(request);
|
||||
String actual = new PathApiVersionResolver(1, requestPath -> {
|
||||
List<PathContainer.Element> elements = requestPath.elements();
|
||||
if (elements.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
return elements.get(0).value().equals("/") &&
|
||||
elements.get(1).value().equals("app") &&
|
||||
elements.get(2).value().equals("/") &&
|
||||
elements.get(3).value().equals("1.0");
|
||||
}).resolveVersion(request);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
finally {
|
||||
ServletRequestPathUtils.clearParsedRequestPath(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testResolve(int index, String requestUri, String expected) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
try {
|
||||
|
||||
+23
-2
@@ -16,7 +16,12 @@
|
||||
|
||||
package org.springframework.web.reactive.accept;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.accept.InvalidApiVersionException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -30,11 +35,13 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
* cannot yield to other resolvers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Martin Mois
|
||||
* @since 7.0
|
||||
*/
|
||||
public class PathApiVersionResolver implements ApiVersionResolver {
|
||||
|
||||
private final int pathSegmentIndex;
|
||||
private @Nullable Predicate<RequestPath> includePath = null;
|
||||
|
||||
|
||||
/**
|
||||
@@ -47,11 +54,25 @@ public class PathApiVersionResolver implements ApiVersionResolver {
|
||||
this.pathSegmentIndex = pathSegmentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver instance.
|
||||
* @param pathSegmentIndex the index of the path segment that contains the API version
|
||||
* @param includePath a {@link Predicate} that tests if the given path should be included
|
||||
*/
|
||||
public PathApiVersionResolver(int pathSegmentIndex, Predicate<RequestPath> includePath) {
|
||||
this(pathSegmentIndex);
|
||||
this.includePath = includePath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String resolveVersion(ServerWebExchange exchange) {
|
||||
public @Nullable String resolveVersion(ServerWebExchange exchange) {
|
||||
int i = 0;
|
||||
for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) {
|
||||
RequestPath path = exchange.getRequest().getPath();
|
||||
if (this.includePath != null && !this.includePath.test(path)) {
|
||||
return null;
|
||||
}
|
||||
for (PathContainer.Element e : path.pathWithinApplication().elements()) {
|
||||
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
|
||||
return e.value();
|
||||
}
|
||||
|
||||
+15
@@ -27,6 +27,7 @@ import java.util.function.Predicate;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.accept.ApiVersionParser;
|
||||
import org.springframework.web.accept.InvalidApiVersionException;
|
||||
@@ -108,6 +109,20 @@ public class ApiVersionConfigurer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a resolver that extracts the API version from a path segment
|
||||
* and that allows to include only certain paths based on the provided {@link Predicate}.
|
||||
* <p>Note that this resolver never returns {@code null}, and therefore
|
||||
* cannot yield to other resolvers, see {@link org.springframework.web.accept.PathApiVersionResolver}.
|
||||
* @param index the index of the path segment to check; e.g. for URL's like
|
||||
* {@code "/{version}/..."} use index 0, for {@code "/api/{version}/..."} index 1.
|
||||
* @param includePath a {@link Predicate} that allows to include a certain path
|
||||
*/
|
||||
public ApiVersionConfigurer usePathSegment(int index, Predicate<RequestPath> includePath) {
|
||||
this.versionResolvers.add(new PathApiVersionResolver(index, includePath));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom resolvers to resolve the API version.
|
||||
* @param resolvers the resolvers to use
|
||||
|
||||
+44
-1
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.web.reactive.accept;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.web.accept.InvalidApiVersionException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||
@@ -29,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Martin Mois
|
||||
*/
|
||||
public class PathApiVersionResolverTests {
|
||||
|
||||
@@ -43,10 +47,49 @@ public class PathApiVersionResolverTests {
|
||||
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathFalse() {
|
||||
String requestUri = "/v3/api-docs";
|
||||
testResolveWithIncludePath(requestUri, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathTrue() {
|
||||
String requestUri = "/app/1.0/path";
|
||||
testResolveWithIncludePath(requestUri, "1.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathFalseShortPath() {
|
||||
String requestUri = "/app";
|
||||
testResolveWithIncludePath(requestUri, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includePathInsufficientPathSegments() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/too-short"));
|
||||
assertThatThrownBy(() -> new PathApiVersionResolver(1, requestPath -> true).resolveVersion(exchange))
|
||||
.isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
private static void testResolveWithIncludePath(String requestUri, String expected) {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
|
||||
String actual = new PathApiVersionResolver(1, requestPath -> {
|
||||
List<PathContainer.Element> elements = requestPath.elements();
|
||||
if (elements.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
return elements.get(0).value().equals("/") &&
|
||||
elements.get(1).value().equals("app") &&
|
||||
elements.get(2).value().equals("/") &&
|
||||
elements.get(3).value().equals("1.0");
|
||||
}).resolveVersion(exchange);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private static void testResolve(int index, String requestUri, String expected) {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
|
||||
String actual = new PathApiVersionResolver(index).resolveVersion(exchange);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -27,6 +27,7 @@ import java.util.function.Predicate;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.accept.ApiVersionDeprecationHandler;
|
||||
import org.springframework.web.accept.ApiVersionParser;
|
||||
@@ -108,6 +109,20 @@ public class ApiVersionConfigurer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a resolver that extracts the API version from a path segment
|
||||
* and that allows to include only certain paths based on the provided {@link Predicate}.
|
||||
* <p>Note that this resolver never returns {@code null}, and therefore
|
||||
* cannot yield to other resolvers, see {@link org.springframework.web.accept.PathApiVersionResolver}.
|
||||
* @param index the index of the path segment to check; e.g. for URL's like
|
||||
* {@code "/{version}/..."} use index 0, for {@code "/api/{version}/..."} index 1.
|
||||
* @param includePath a {@link Predicate} that allows to include a certain path
|
||||
*/
|
||||
public ApiVersionConfigurer usePathSegment(int index, Predicate<RequestPath> includePath) {
|
||||
this.versionResolvers.add(new PathApiVersionResolver(index, includePath));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom resolvers to resolve the API version.
|
||||
* @param resolvers the resolvers to use
|
||||
|
||||
Reference in New Issue
Block a user