Browse Source

PathApiVersionResolver is not nullable

Closes gh-35265
pull/34146/merge
rstoyanchev 5 months ago
parent
commit
87838aa4c5
  1. 5
      framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc
  2. 5
      framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc
  3. 25
      spring-web/src/main/java/org/springframework/web/accept/PathApiVersionResolver.java
  4. 6
      spring-web/src/test/java/org/springframework/web/accept/PathApiVersionResolverTests.java
  5. 12
      spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java
  6. 22
      spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java
  7. 7
      spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathApiVersionResolverTests.java
  8. 22
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java

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

@ -49,6 +49,11 @@ This strategy resolves the API version from a request. The WebFlux config provid
options to resolve from a header, query parameter, media type parameter, options to resolve from a header, query parameter, media type parameter,
or from the URL path. You can also use a custom `ApiVersionResolver`. or from the URL path. You can also use a custom `ApiVersionResolver`.
NOTE: The path resolver always resolves the version from the specified path segment, or
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
resolvers.

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

@ -49,6 +49,11 @@ This strategy resolves the API version from a request. The MVC config provides b
options to resolve from a header, query parameter, media type parameter, options to resolve from a header, query parameter, media type parameter,
or from the URL path. You can also use a custom `ApiVersionResolver`. or from the URL path. You can also use a custom `ApiVersionResolver`.
NOTE: The path resolver always resolves the version from the specified path segment, or
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
resolvers.

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

@ -17,7 +17,6 @@
package org.springframework.web.accept; package org.springframework.web.accept;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.http.server.PathContainer; import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath; import org.springframework.http.server.RequestPath;
@ -27,6 +26,11 @@ import org.springframework.web.util.ServletRequestPathUtils;
/** /**
* {@link ApiVersionResolver} that extract the version from a path segment. * {@link ApiVersionResolver} that extract the version from a path segment.
* *
* <p>Note that this resolver will either resolve the version from the specified
* path segment, or raise an {@link InvalidApiVersionException}, e.g. if there
* are not enough path segments. It never returns {@code null}, and therefore
* cannot yield to other resolvers.
*
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 7.0 * @since 7.0
*/ */
@ -47,17 +51,18 @@ public class PathApiVersionResolver implements ApiVersionResolver {
@Override @Override
public @Nullable String resolveVersion(HttpServletRequest request) { public String resolveVersion(HttpServletRequest request) {
if (ServletRequestPathUtils.hasParsedRequestPath(request)) { if (!ServletRequestPathUtils.hasParsedRequestPath(request)) {
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); throw new IllegalStateException("Expected parsed request path");
int i = 0; }
for (PathContainer.Element e : path.pathWithinApplication().elements()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) { int i = 0;
return e.value(); for (PathContainer.Element element : path.pathWithinApplication().elements()) {
} if (element instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
return element.value();
} }
} }
return null; throw new InvalidApiVersionException("No path segment at index " + this.pathSegmentIndex);
} }
} }

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

@ -22,6 +22,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.util.ServletRequestPathUtils; import org.springframework.web.util.ServletRequestPathUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** /**
* Unit tests for {@link PathApiVersionResolver}. * Unit tests for {@link PathApiVersionResolver}.
@ -35,6 +36,11 @@ public class PathApiVersionResolverTests {
testResolve(1, "/app/1.1/path", "1.1"); testResolve(1, "/app/1.1/path", "1.1");
} }
@Test
void insufficientPathSegments() {
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
}
private static void testResolve(int index, String requestUri, String expected) { private static void testResolve(int index, String requestUri, String expected) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
try { try {

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

@ -16,15 +16,19 @@
package org.springframework.web.reactive.accept; package org.springframework.web.reactive.accept;
import org.jspecify.annotations.Nullable;
import org.springframework.http.server.PathContainer; import org.springframework.http.server.PathContainer;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.accept.InvalidApiVersionException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
/** /**
* {@link ApiVersionResolver} that extract the version from a path segment. * {@link ApiVersionResolver} that extract the version from a path segment.
* *
* <p>Note that this resolver will either resolve the version from the specified
* path segment, or raise an {@link InvalidApiVersionException}, e.g. if there
* are not enough path segments. It never returns {@code null}, and therefore
* cannot yield to other resolvers.
*
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 7.0 * @since 7.0
*/ */
@ -45,14 +49,14 @@ public class PathApiVersionResolver implements ApiVersionResolver {
@Override @Override
public @Nullable String resolveVersion(ServerWebExchange exchange) { public String resolveVersion(ServerWebExchange exchange) {
int i = 0; int i = 0;
for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) { for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) {
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) { if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
return e.value(); return e.value();
} }
} }
return null; throw new InvalidApiVersionException("No path segment at index " + this.pathSegmentIndex);
} }
} }

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

@ -79,16 +79,6 @@ public class ApiVersionConfigurer {
return this; return this;
} }
/**
* Add a resolver that extracts the API version from a path segment.
* @param index the index of the path segment to check; e.g. for URL's like
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
*/
public ApiVersionConfigurer usePathSegment(int index) {
this.versionResolvers.add(new PathApiVersionResolver(index));
return this;
}
/** /**
* Add resolver to extract the version from a media type parameter found in * Add resolver to extract the version from a media type parameter found in
* the Accept or Content-Type headers. * the Accept or Content-Type headers.
@ -101,6 +91,18 @@ public class ApiVersionConfigurer {
return this; return this;
} }
/**
* Add a resolver that extracts the API version from a path segment.
* <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
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
*/
public ApiVersionConfigurer usePathSegment(int index) {
this.versionResolvers.add(new PathApiVersionResolver(index));
return this;
}
/** /**
* Add custom resolvers to resolve the API version. * Add custom resolvers to resolve the API version.
* @param resolvers the resolvers to use * @param resolvers the resolvers to use

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

@ -18,11 +18,13 @@ package org.springframework.web.reactive.accept;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.web.accept.InvalidApiVersionException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.server.MockServerWebExchange; import org.springframework.web.testfixture.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** /**
* Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}. * Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}.
@ -36,6 +38,11 @@ public class PathApiVersionResolverTests {
testResolve(1, "/app/1.1/path", "1.1"); testResolve(1, "/app/1.1/path", "1.1");
} }
@Test
void insufficientPathSegments() {
assertThatThrownBy(() -> testResolve(0, "/", "1.0")).isInstanceOf(InvalidApiVersionException.class);
}
private static void testResolve(int index, String requestUri, String expected) { private static void testResolve(int index, String requestUri, String expected) {
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri)); ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(requestUri));
String actual = new PathApiVersionResolver(index).resolveVersion(exchange); String actual = new PathApiVersionResolver(index).resolveVersion(exchange);

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

@ -80,16 +80,6 @@ public class ApiVersionConfigurer {
return this; return this;
} }
/**
* Add resolver to extract the version from a path segment.
* @param index the index of the path segment to check; e.g. for URL's like
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
*/
public ApiVersionConfigurer usePathSegment(int index) {
this.versionResolvers.add(new PathApiVersionResolver(index));
return this;
}
/** /**
* Add resolver to extract the version from a media type parameter found in * Add resolver to extract the version from a media type parameter found in
* the Accept or Content-Type headers. * the Accept or Content-Type headers.
@ -102,6 +92,18 @@ public class ApiVersionConfigurer {
return this; return this;
} }
/**
* Add resolver to extract the version from a path segment.
* <p>Note that this resolver never returns {@code null}, and therefore
* cannot yield to other resolvers, see {@link PathApiVersionResolver}.
* @param index the index of the path segment to check; e.g. for URL's like
* "/{version}/..." use index 0, for "/api/{version}/..." index 1.
*/
public ApiVersionConfigurer usePathSegment(int index) {
this.versionResolvers.add(new PathApiVersionResolver(index));
return this;
}
/** /**
* Add custom resolvers to resolve the API version. * Add custom resolvers to resolve the API version.
* @param resolvers the resolvers to use * @param resolvers the resolvers to use

Loading…
Cancel
Save