Skip to content

Commit 353acbd

Browse files
committed
new method usePathSegment(int index, Predicate<RequestPath> excludePath) in ApiVersionConfigurer to exclude certain paths Signed-off-by: Martin Mois <martin.mois@gmail.com>
Signed-off-by: Martin Mois <martin.mois@gmail.com>
1 parent 3bc55c7 commit 353acbd

4 files changed

Lines changed: 74 additions & 54 deletions

File tree

.idea/icon.svg

Lines changed: 0 additions & 52 deletions
This file was deleted.

spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616

1717
package org.springframework.web.reactive.accept;
1818

19+
import org.jspecify.annotations.Nullable;
1920
import org.springframework.http.server.PathContainer;
21+
import org.springframework.http.server.RequestPath;
2022
import org.springframework.util.Assert;
2123
import org.springframework.web.accept.InvalidApiVersionException;
2224
import org.springframework.web.server.ServerWebExchange;
2325

26+
import java.util.function.Predicate;
27+
2428
/**
2529
* {@link ApiVersionResolver} that extract the version from a path segment.
2630
*
@@ -30,11 +34,13 @@
3034
* cannot yield to other resolvers.
3135
*
3236
* @author Rossen Stoyanchev
37+
* @author Martin Mois
3338
* @since 7.0
3439
*/
3540
public class PathApiVersionResolver implements ApiVersionResolver {
3641

3742
private final int pathSegmentIndex;
43+
private @Nullable Predicate<RequestPath> excludePath = null;
3844

3945

4046
/**
@@ -47,11 +53,25 @@ public PathApiVersionResolver(int pathSegmentIndex) {
4753
this.pathSegmentIndex = pathSegmentIndex;
4854
}
4955

56+
/**
57+
* Create a resolver instance.
58+
* @param pathSegmentIndex the index of the path segment that contains the API version
59+
* @param excludePath a {@link Predicate} that tests if the given path should be excluded
60+
*/
61+
public PathApiVersionResolver(int pathSegmentIndex, Predicate<RequestPath> excludePath) {
62+
this(pathSegmentIndex);
63+
this.excludePath = excludePath;
64+
}
65+
5066

5167
@Override
52-
public String resolveVersion(ServerWebExchange exchange) {
68+
public @Nullable String resolveVersion(ServerWebExchange exchange) {
5369
int i = 0;
54-
for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) {
70+
RequestPath path = exchange.getRequest().getPath();
71+
if (this.excludePath != null && this.excludePath.test(path)) {
72+
return null;
73+
}
74+
for (PathContainer.Element e : path.pathWithinApplication().elements()) {
5575
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
5676
return e.value();
5777
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.jspecify.annotations.Nullable;
2828

2929
import org.springframework.http.MediaType;
30+
import org.springframework.http.server.RequestPath;
3031
import org.springframework.util.Assert;
3132
import org.springframework.web.accept.ApiVersionParser;
3233
import org.springframework.web.accept.InvalidApiVersionException;
@@ -108,6 +109,20 @@ public ApiVersionConfigurer usePathSegment(int index) {
108109
return this;
109110
}
110111

112+
/**
113+
* Add a resolver that extracts the API version from a path segment
114+
* and that allows to exclude certain paths based on the provided {@link Predicate}.
115+
* <p>Note that this resolver never returns {@code null}, and therefore
116+
* cannot yield to other resolvers, see {@link org.springframework.web.accept.PathApiVersionResolver}.
117+
* @param index the index of the path segment to check; e.g. for URL's like
118+
* {@code "/{version}/..."} use index 0, for {@code "/api/{version}/..."} index 1.
119+
* @param excludePath a {@link Predicate} that allows to exclude certain paths
120+
*/
121+
public ApiVersionConfigurer usePathSegment(int index, Predicate<RequestPath> excludePath) {
122+
this.versionResolvers.add(new PathApiVersionResolver(index, excludePath));
123+
return this;
124+
}
125+
111126
/**
112127
* Add custom resolvers to resolve the API version.
113128
* @param resolvers the resolvers to use

spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathApiVersionResolverTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.http.server.PathContainer;
2122
import org.springframework.web.accept.InvalidApiVersionException;
2223
import org.springframework.web.server.ServerWebExchange;
2324
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
2425
import org.springframework.web.testfixture.server.MockServerWebExchange;
2526

27+
import java.util.List;
28+
2629
import static org.assertj.core.api.Assertions.assertThat;
2730
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2831

2932
/**
3033
* Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}.
3134
* @author Rossen Stoyanchev
35+
* @author Martin Mois
3236
*/
3337
public class PathApiVersionResolverTests {
3438

@@ -43,6 +47,39 @@ void insufficientPathSegments() {
4347
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
4448
}
4549

50+
@Test
51+
void excludePathTrue() {
52+
String requestUri = "/v3/api-docs";
53+
testResolveWithExcludePath(requestUri, null);
54+
}
55+
56+
@Test
57+
void excludePathFalse() {
58+
String requestUri = "/app/1.0/path";
59+
testResolveWithExcludePath(requestUri, "1.0");
60+
}
61+
62+
@Test
63+
void excludePathFalseShortPath() {
64+
String requestUri = "/app";
65+
assertThatThrownBy(() -> testResolveWithExcludePath(requestUri, null)).isInstanceOf(InvalidApiVersionException.class);
66+
}
67+
68+
private static void testResolveWithExcludePath(String requestUri, String expected) {
69+
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
70+
String actual = new PathApiVersionResolver(1, requestPath -> {
71+
List<PathContainer.Element> elements = requestPath.elements();
72+
if (elements.size() < 4) {
73+
return false;
74+
}
75+
return elements.get(0).value().equals("/") &&
76+
elements.get(1).value().equals("v3") &&
77+
elements.get(2).value().equals("/") &&
78+
elements.get(3).value().equals("api-docs");
79+
}).resolveVersion(exchange);
80+
assertThat(actual).isEqualTo(expected);
81+
}
82+
4683
private static void testResolve(int index, String requestUri, String expected) {
4784
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
4885
String actual = new PathApiVersionResolver(index).resolveVersion(exchange);

0 commit comments

Comments
 (0)