Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 0 additions & 52 deletions .idea/icon.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,11 +35,13 @@
* 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> excludePath = null;


/**
Expand All @@ -47,11 +54,25 @@ public PathApiVersionResolver(int pathSegmentIndex) {
this.pathSegmentIndex = pathSegmentIndex;
}

/**
* Create a resolver instance.
* @param pathSegmentIndex the index of the path segment that contains the API version
* @param excludePath a {@link Predicate} that tests if the given path should be excluded
*/
public PathApiVersionResolver(int pathSegmentIndex, Predicate<RequestPath> excludePath) {
this(pathSegmentIndex);
this.excludePath = excludePath;
}


@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.excludePath != null && this.excludePath.test(path)) {
return null;
}
for (PathContainer.Element e : path.pathWithinApplication().elements()) {
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
return e.value();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -108,6 +109,20 @@ public ApiVersionConfigurer usePathSegment(int index) {
return this;
}

/**
* Add a resolver that extracts the API version from a path segment
* and that allows to exclude 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 excludePath a {@link Predicate} that allows to exclude certain paths
*/
public ApiVersionConfigurer usePathSegment(int index, Predicate<RequestPath> excludePath) {
this.versionResolvers.add(new PathApiVersionResolver(index, excludePath));
return this;
}

/**
* Add custom resolvers to resolve the API version.
* @param resolvers the resolvers to use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +32,7 @@
/**
* Unit tests for {@link org.springframework.web.accept.PathApiVersionResolver}.
* @author Rossen Stoyanchev
* @author Martin Mois
*/
public class PathApiVersionResolverTests {

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

@Test
void excludePathTrue() {
String requestUri = "/v3/api-docs";
testResolveWithExcludePath(requestUri, null);
}

@Test
void excludePathFalse() {
String requestUri = "/app/1.0/path";
testResolveWithExcludePath(requestUri, "1.0");
}

@Test
void excludePathFalseShortPath() {
String requestUri = "/app";
assertThatThrownBy(() -> testResolveWithExcludePath(requestUri, null)).isInstanceOf(InvalidApiVersionException.class);
}

private static void testResolveWithExcludePath(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("v3") &&
elements.get(2).value().equals("/") &&
elements.get(3).value().equals("api-docs");
}).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);
Expand Down