Skip to content

Commit 87838aa

Browse files
committed
PathApiVersionResolver is not nullable
Closes gh-35265
1 parent da36169 commit 87838aa

File tree

8 files changed

+70
-34
lines changed

8 files changed

+70
-34
lines changed

framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ This strategy resolves the API version from a request. The WebFlux config provid
4949
options to resolve from a header, query parameter, media type parameter,
5050
or from the URL path. You can also use a custom `ApiVersionResolver`.
5151

52+
NOTE: The path resolver always resolves the version from the specified path segment, or
53+
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
54+
resolvers.
55+
56+
5257

5358

5459

framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ This strategy resolves the API version from a request. The MVC config provides b
4949
options to resolve from a header, query parameter, media type parameter,
5050
or from the URL path. You can also use a custom `ApiVersionResolver`.
5151

52+
NOTE: The path resolver always resolves the version from the specified path segment, or
53+
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
54+
resolvers.
55+
56+
5257

5358

5459

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.web.accept;
1818

1919
import jakarta.servlet.http.HttpServletRequest;
20-
import org.jspecify.annotations.Nullable;
2120

2221
import org.springframework.http.server.PathContainer;
2322
import org.springframework.http.server.RequestPath;
@@ -27,6 +26,11 @@
2726
/**
2827
* {@link ApiVersionResolver} that extract the version from a path segment.
2928
*
29+
* <p>Note that this resolver will either resolve the version from the specified
30+
* path segment, or raise an {@link InvalidApiVersionException}, e.g. if there
31+
* are not enough path segments. It never returns {@code null}, and therefore
32+
* cannot yield to other resolvers.
33+
*
3034
* @author Rossen Stoyanchev
3135
* @since 7.0
3236
*/
@@ -47,17 +51,18 @@ public PathApiVersionResolver(int pathSegmentIndex) {
4751

4852

4953
@Override
50-
public @Nullable String resolveVersion(HttpServletRequest request) {
51-
if (ServletRequestPathUtils.hasParsedRequestPath(request)) {
52-
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
53-
int i = 0;
54-
for (PathContainer.Element e : path.pathWithinApplication().elements()) {
55-
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
56-
return e.value();
57-
}
54+
public String resolveVersion(HttpServletRequest request) {
55+
if (!ServletRequestPathUtils.hasParsedRequestPath(request)) {
56+
throw new IllegalStateException("Expected parsed request path");
57+
}
58+
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
59+
int i = 0;
60+
for (PathContainer.Element element : path.pathWithinApplication().elements()) {
61+
if (element instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
62+
return element.value();
5863
}
5964
}
60-
return null;
65+
throw new InvalidApiVersionException("No path segment at index " + this.pathSegmentIndex);
6166
}
6267

6368
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.web.util.ServletRequestPathUtils;
2323

2424
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2526

2627
/**
2728
* Unit tests for {@link PathApiVersionResolver}.
@@ -35,6 +36,11 @@ void resolve() {
3536
testResolve(1, "/app/1.1/path", "1.1");
3637
}
3738

39+
@Test
40+
void insufficientPathSegments() {
41+
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
42+
}
43+
3844
private static void testResolve(int index, String requestUri, String expected) {
3945
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
4046
try {

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616

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

19-
import org.jspecify.annotations.Nullable;
20-
2119
import org.springframework.http.server.PathContainer;
2220
import org.springframework.util.Assert;
21+
import org.springframework.web.accept.InvalidApiVersionException;
2322
import org.springframework.web.server.ServerWebExchange;
2423

2524
/**
2625
* {@link ApiVersionResolver} that extract the version from a path segment.
2726
*
27+
* <p>Note that this resolver will either resolve the version from the specified
28+
* path segment, or raise an {@link InvalidApiVersionException}, e.g. if there
29+
* are not enough path segments. It never returns {@code null}, and therefore
30+
* cannot yield to other resolvers.
31+
*
2832
* @author Rossen Stoyanchev
2933
* @since 7.0
3034
*/
@@ -45,14 +49,14 @@ public PathApiVersionResolver(int pathSegmentIndex) {
4549

4650

4751
@Override
48-
public @Nullable String resolveVersion(ServerWebExchange exchange) {
52+
public String resolveVersion(ServerWebExchange exchange) {
4953
int i = 0;
5054
for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) {
5155
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
5256
return e.value();
5357
}
5458
}
55-
return null;
59+
throw new InvalidApiVersionException("No path segment at index " + this.pathSegmentIndex);
5660
}
5761

5862
}

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,6 @@ public ApiVersionConfigurer useQueryParam(String paramName) {
7979
return this;
8080
}
8181

82-
/**
83-
* Add a resolver that extracts the API version from a path segment.
84-
* @param index the index of the path segment to check; e.g. for URL's like
85-
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
86-
*/
87-
public ApiVersionConfigurer usePathSegment(int index) {
88-
this.versionResolvers.add(new PathApiVersionResolver(index));
89-
return this;
90-
}
91-
9282
/**
9383
* Add resolver to extract the version from a media type parameter found in
9484
* the Accept or Content-Type headers.
@@ -101,6 +91,18 @@ public ApiVersionConfigurer useMediaTypeParameter(MediaType compatibleMediaType,
10191
return this;
10292
}
10393

94+
/**
95+
* Add a resolver that extracts the API version from a path segment.
96+
* <p>Note that this resolver never returns {@code null}, and therefore
97+
* cannot yield to other resolvers, see {@link org.springframework.web.accept.PathApiVersionResolver}.
98+
* @param index the index of the path segment to check; e.g. for URL's like
99+
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
100+
*/
101+
public ApiVersionConfigurer usePathSegment(int index) {
102+
this.versionResolvers.add(new PathApiVersionResolver(index));
103+
return this;
104+
}
105+
104106
/**
105107
* Add custom resolvers to resolve the API version.
106108
* @param resolvers the resolvers to use

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

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

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

2526
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2628

2729
/**
2830
* Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}.
@@ -36,6 +38,11 @@ void resolve() {
3638
testResolve(1, "/app/1.1/path", "1.1");
3739
}
3840

41+
@Test
42+
void insufficientPathSegments() {
43+
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
44+
}
45+
3946
private static void testResolve(int index, String requestUri, String expected) {
4047
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
4148
String actual = new PathApiVersionResolver(index).resolveVersion(exchange);

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,6 @@ public ApiVersionConfigurer useQueryParam(String paramName) {
8080
return this;
8181
}
8282

83-
/**
84-
* Add resolver to extract the version from a path segment.
85-
* @param index the index of the path segment to check; e.g. for URL's like
86-
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
87-
*/
88-
public ApiVersionConfigurer usePathSegment(int index) {
89-
this.versionResolvers.add(new PathApiVersionResolver(index));
90-
return this;
91-
}
92-
9383
/**
9484
* Add resolver to extract the version from a media type parameter found in
9585
* the Accept or Content-Type headers.
@@ -102,6 +92,18 @@ public ApiVersionConfigurer useMediaTypeParameter(MediaType compatibleMediaType,
10292
return this;
10393
}
10494

95+
/**
96+
* Add resolver to extract the version from a path segment.
97+
* <p>Note that this resolver never returns {@code null}, and therefore
98+
* cannot yield to other resolvers, see {@link PathApiVersionResolver}.
99+
* @param index the index of the path segment to check; e.g. for URL's like
100+
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
101+
*/
102+
public ApiVersionConfigurer usePathSegment(int index) {
103+
this.versionResolvers.add(new PathApiVersionResolver(index));
104+
return this;
105+
}
106+
105107
/**
106108
* Add custom resolvers to resolve the API version.
107109
* @param resolvers the resolvers to use

0 commit comments

Comments
 (0)