mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Polishing contribution
See gh-36084
This commit is contained in:
+10
-9
@@ -34,20 +34,21 @@ interface ApiVersionResolver {
|
||||
|
||||
/**
|
||||
* Resolve the version for the given exchange.
|
||||
* @param exchange the current exchange
|
||||
* @return the version value, or {@code null} if not found
|
||||
*/
|
||||
@Nullable String resolveVersion(ServerWebExchange exchange);
|
||||
|
||||
/**
|
||||
* Asynchronously resolve the version for the given request exchange.
|
||||
* This method wraps the synchronous {@code resolveVersion} method
|
||||
* and provides a reactive alternative.
|
||||
* @param exchange the current request exchange
|
||||
* @return a {@code Mono} emitting the version value, or an empty {@code Mono} if no version is found
|
||||
* @param exchange the current exchange
|
||||
* @return {@code Mono} emitting the version value, or an empty {@code Mono}
|
||||
* @since 7.0.3
|
||||
*/
|
||||
default Mono<String> resolveVersionAsync(ServerWebExchange exchange){
|
||||
return Mono.justOrEmpty(this.resolveVersion(exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the version for the given exchange.
|
||||
* @param exchange the current exchange
|
||||
* @return the version value, or {@code null} if not found
|
||||
*/
|
||||
@Nullable String resolveVersion(ServerWebExchange exchange);
|
||||
|
||||
}
|
||||
|
||||
+46
-47
@@ -34,29 +34,29 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*/
|
||||
public interface ApiVersionStrategy {
|
||||
|
||||
/**
|
||||
* Resolve the version value from a request.
|
||||
* @param exchange the current exchange
|
||||
* @return a {@code Mono} emitting the raw version as a {@code String},
|
||||
* or an empty {@code Mono} if no version is found
|
||||
* @since 7.0.3
|
||||
* @see ApiVersionResolver
|
||||
*/
|
||||
default Mono<String> resolveVersionAsync(ServerWebExchange exchange) {
|
||||
return Mono.justOrEmpty(resolveVersion(exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the version value from a request, e.g. from a request header.
|
||||
* @param exchange the current exchange
|
||||
* @return the version, if present or {@code null}
|
||||
* @see ApiVersionResolver
|
||||
* @deprecated as of 7.0.3, in favor of
|
||||
* {@link #resolveVersionAsync(ServerWebExchange)}
|
||||
* @deprecated as of 7.0.3, in favor of {@link #resolveVersionAsync(ServerWebExchange)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0.3")
|
||||
@Deprecated(since = "7.0.3", forRemoval = true)
|
||||
@Nullable
|
||||
String resolveVersion(ServerWebExchange exchange);
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the version value from a request asynchronously.
|
||||
* @param exchange the current server exchange containing the request details
|
||||
* @return a {@code Mono} emitting the resolved version as a {@code String},
|
||||
* or an empty {@code Mono} if no version is resolved
|
||||
*/
|
||||
default Mono<String> resolveVersionAsync(ServerWebExchange exchange) {
|
||||
return Mono.justOrEmpty(this.resolveVersion(exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the version of a request into an Object.
|
||||
* @param version the value to parse
|
||||
@@ -75,6 +75,36 @@ public interface ApiVersionStrategy {
|
||||
void validateVersion(@Nullable Comparable<?> requestVersion, ServerWebExchange exchange)
|
||||
throws MissingApiVersionException, InvalidApiVersionException;
|
||||
|
||||
/**
|
||||
* Return a default version to use for requests that don't specify one.
|
||||
*/
|
||||
@Nullable Comparable<?> getDefaultVersion();
|
||||
|
||||
/**
|
||||
* Convenience method to resolve, parse, and validate the request version,
|
||||
* or return the default version if configured.
|
||||
* @param exchange the current exchange
|
||||
* @return a {@code Mono} emitting the request version, a validation error,
|
||||
* or an empty {@code Mono} if there is no version
|
||||
* @since 7.0.3
|
||||
*/
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
default Mono<Comparable<?>> resolveParseAndValidate(ServerWebExchange exchange) {
|
||||
return this.resolveVersionAsync(exchange)
|
||||
.switchIfEmpty(Mono.justOrEmpty(this.getDefaultVersion())
|
||||
.mapNotNull(comparable -> comparable.toString()))
|
||||
.<Comparable<?>>handle((version, sink) -> {
|
||||
try {
|
||||
sink.next(this.parseVersion(version));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
sink.error(new InvalidApiVersionException(version, null, ex));
|
||||
}
|
||||
})
|
||||
.flatMap(version -> this.validateVersionAsync(version, exchange))
|
||||
.switchIfEmpty(this.validateVersionAsync(null, exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided request version against the requirements and constraints
|
||||
* of the API. If the validation succeeds, the version is returned, otherwise an
|
||||
@@ -94,19 +124,14 @@ public interface ApiVersionStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a default version to use for requests that don't specify one.
|
||||
*/
|
||||
@Nullable Comparable<?> getDefaultVersion();
|
||||
|
||||
/**
|
||||
* Convenience method to return the parsed and validated request version,
|
||||
* or the default version if configured.
|
||||
* @param exchange the current exchange
|
||||
* @return the parsed request version, or the default version
|
||||
* @deprecated in favor of {@link #resolveParseAndValidate(ServerWebExchange)}
|
||||
*/
|
||||
@SuppressWarnings({"DeprecatedIsStillUsed", "DuplicatedCode"})
|
||||
@Deprecated(forRemoval = true, since = "7.0.3")
|
||||
@Deprecated(since = "7.0.3", forRemoval = true)
|
||||
default @Nullable Comparable<?> resolveParseAndValidateVersion(ServerWebExchange exchange) {
|
||||
String value = resolveVersion(exchange);
|
||||
Comparable<?> version;
|
||||
@@ -125,32 +150,6 @@ public interface ApiVersionStrategy {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to return the API version from the given request exchange, parse and validate
|
||||
* the version, and return the result as a reactive {@code Mono} stream. If no version
|
||||
* is resolved, the default version is used.
|
||||
* @param exchange the current server exchange containing the request details
|
||||
* @return a {@code Mono} emitting the resolved, parsed, and validated version as a {@code Comparable<?>},
|
||||
* or an error in case parsing or validation fails
|
||||
*/
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
default Mono<Comparable<?>> resolveParseAndValidateVersionAsync(ServerWebExchange exchange) {
|
||||
return this.resolveVersionAsync(exchange)
|
||||
.switchIfEmpty(Mono.justOrEmpty(this.getDefaultVersion())
|
||||
.mapNotNull(comparable -> comparable.toString()))
|
||||
.<Comparable<?>>handle((version, sink) -> {
|
||||
try {
|
||||
sink.next(this.parseVersion(version));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
sink.error(new InvalidApiVersionException(version, null, ex));
|
||||
}
|
||||
})
|
||||
.flatMap(version -> this.validateVersionAsync(version, exchange))
|
||||
.switchIfEmpty(this.validateVersionAsync(null, exchange));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the requested API version is deprecated, and if so handle it
|
||||
* accordingly, e.g. by setting response headers to signal the deprecation,
|
||||
|
||||
+1
-3
@@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -170,8 +169,7 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||
@Override
|
||||
public Mono<String> resolveVersionAsync(ServerWebExchange exchange) {
|
||||
return Flux.fromIterable(this.versionResolvers)
|
||||
.mapNotNull(resolver -> resolver.resolveVersionAsync(exchange))
|
||||
.flatMap(Function.identity())
|
||||
.flatMap(resolver -> resolver.resolveVersionAsync(exchange))
|
||||
.next();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
||||
if (this.apiVersionStrategy != null) {
|
||||
if (exchange.getAttribute(API_VERSION_ATTRIBUTE) == null) {
|
||||
return this.apiVersionStrategy
|
||||
.resolveParseAndValidateVersionAsync(exchange)
|
||||
.resolveParseAndValidate(exchange)
|
||||
.doOnNext(version -> exchange.getAttributes()
|
||||
.put(API_VERSION_ATTRIBUTE, version));
|
||||
}
|
||||
|
||||
+41
-127
@@ -21,6 +21,7 @@ import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.web.accept.InvalidApiVersionException;
|
||||
@@ -31,7 +32,6 @@ import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.web.accept.DefaultApiVersionStrategy}.
|
||||
@@ -51,91 +51,80 @@ public class DefaultApiVersionStrategiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersionReactively() {
|
||||
|
||||
testValidateReactively(null, apiVersionStrategy())
|
||||
.expectErrorSatisfies(throwable ->
|
||||
assertThat(throwable).isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage(("400 BAD_REQUEST \"API version is " +
|
||||
"required.\"")
|
||||
))
|
||||
void missingRequiredVersion() {
|
||||
StepVerifier.create(testValidate(null, apiVersionStrategy()))
|
||||
.expectErrorSatisfies(ex -> assertThat(ex)
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage(("400 BAD_REQUEST \"API version is required.\"")))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersionReactively() {
|
||||
void validateSupportedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy();
|
||||
strategy.addSupportedVersion(version);
|
||||
testValidateReactively(version, strategy)
|
||||
.expectNextMatches(next -> next.toString().equals("1.2.0"))
|
||||
.verifyComplete();
|
||||
Mono<String> result = testValidate(version, strategy);
|
||||
StepVerifier.create(result).expectNext("1.2.0").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersionForDefaultVersionReactively() {
|
||||
void validateSupportedVersionForDefaultVersion() {
|
||||
String defaultVersion = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(defaultVersion, false, null);
|
||||
|
||||
testValidateReactively(defaultVersion, strategy)
|
||||
.expectNextMatches(next -> next.toString().equals("1.2.0"))
|
||||
.verifyComplete();
|
||||
Mono<String> result = testValidate(defaultVersion, strategy);
|
||||
StepVerifier.create(result).expectNext("1.2.0").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedVersionReactively() {
|
||||
testValidateReactively("1.2", apiVersionStrategy())
|
||||
.expectErrorSatisfies(throwable ->
|
||||
assertThat(throwable).isInstanceOf(InvalidApiVersionException.class)
|
||||
.hasMessage(("400 BAD_REQUEST \"Invalid API " +
|
||||
"version: '1.2.0'.\"")
|
||||
))
|
||||
void validateUnsupportedVersion() {
|
||||
StepVerifier.create(testValidate("1.2", apiVersionStrategy()))
|
||||
.expectErrorSatisfies(ex -> assertThat(ex)
|
||||
.isInstanceOf(InvalidApiVersionException.class)
|
||||
.hasMessage(("400 BAD_REQUEST \"Invalid API version: '1.2.0'.\"")))
|
||||
.verify();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateDetectedVersionReactively() {
|
||||
void validateDetectedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true, null);
|
||||
strategy.addMappedVersion(version);
|
||||
testValidateReactively(version, strategy)
|
||||
.expectNextMatches(next -> next.toString().equals("1.2.0"))
|
||||
.verifyComplete();
|
||||
Mono<String> result = testValidate(version, strategy);
|
||||
StepVerifier.create(result).expectNext("1.2.0").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenDetectedVersionOffReactively() {
|
||||
void validateWhenDetectedVersionOff() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy();
|
||||
strategy.addMappedVersion(version);
|
||||
testValidateReactively(version, strategy)
|
||||
.expectError(InvalidApiVersionException.class)
|
||||
.verify();
|
||||
Mono<String> result = testValidate(version, strategy);
|
||||
StepVerifier.create(result).expectError(InvalidApiVersionException.class).verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedWithPredicateReactively() {
|
||||
void validateSupportedWithPredicate() {
|
||||
SemanticApiVersionParser.Version parsedVersion = parser.parseVersion("1.2");
|
||||
testValidateReactively("1.2", apiVersionStrategy(null, false, version -> version.equals(parsedVersion)))
|
||||
.expectNextMatches(next -> next.toString().equals("1.2.0"))
|
||||
.verifyComplete();
|
||||
ApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals(parsedVersion));
|
||||
Mono<String> result = testValidate("1.2", strategy);
|
||||
StepVerifier.create(result).expectNext("1.2.0").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedWithPredicateReactively() {
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals("1.2"));
|
||||
testValidateReactively("1.2", strategy)
|
||||
.verifyError(InvalidApiVersionException.class);
|
||||
void validateUnsupportedWithPredicate() {
|
||||
ApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals("1.2"));
|
||||
Mono<?> result = testValidate("1.2", strategy);
|
||||
StepVerifier.create(result).verifyError(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionRequiredAndDefaultVersionSetReactively() {
|
||||
void versionRequiredAndDefaultVersionSet() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() ->
|
||||
new org.springframework.web.accept.DefaultApiVersionStrategy(
|
||||
List.of(request -> request.getParameter("api-version")), new SemanticApiVersionParser(),
|
||||
true, "1.2", true, version -> true, null))
|
||||
.isThrownBy(() -> {
|
||||
ApiVersionResolver resolver = new QueryApiVersionResolver("api-version");
|
||||
new DefaultApiVersionStrategy(List.of(resolver), parser, true, "1.2", true, v -> true, null);
|
||||
})
|
||||
.withMessage("versionRequired cannot be set to true if a defaultVersion is also configured");
|
||||
}
|
||||
|
||||
@@ -152,88 +141,13 @@ public class DefaultApiVersionStrategiesTests {
|
||||
parser, null, defaultVersion, detectSupportedVersions, supportedVersionPredicate, null);
|
||||
}
|
||||
|
||||
private StepVerifier.FirstStep<Comparable<?>> testValidateReactively(@Nullable String version, DefaultApiVersionStrategy strategy) {
|
||||
MockServerHttpRequest.BaseBuilder<?> requestBuilder = MockServerHttpRequest.get("/");
|
||||
private Mono<String> testValidate(@Nullable String version, ApiVersionStrategy strategy) {
|
||||
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest.get("/");
|
||||
if (version != null) {
|
||||
requestBuilder.queryParam("api-version", version);
|
||||
builder.queryParam("api-version", version);
|
||||
}
|
||||
return StepVerifier.create(strategy.resolveParseAndValidateVersionAsync(MockServerWebExchange.builder(requestBuilder).build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredVersion() {
|
||||
assertThatThrownBy(() -> testValidate(null, apiVersionStrategy()))
|
||||
.isInstanceOf(MissingApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"API version is required.\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy();
|
||||
strategy.addSupportedVersion(version);
|
||||
testValidate(version, strategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedVersionForDefaultVersion() {
|
||||
String defaultVersion = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(defaultVersion, false, null);
|
||||
testValidate(defaultVersion, strategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedVersion() {
|
||||
assertThatThrownBy(() -> testValidate("1.2", apiVersionStrategy()))
|
||||
.isInstanceOf(InvalidApiVersionException.class)
|
||||
.hasMessage("400 BAD_REQUEST \"Invalid API version: '1.2.0'.\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateDetectedVersion() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, true, null);
|
||||
strategy.addMappedVersion(version);
|
||||
testValidate(version, strategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenDetectedVersionOff() {
|
||||
String version = "1.2";
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy();
|
||||
strategy.addMappedVersion(version);
|
||||
assertThatThrownBy(() -> testValidate(version, strategy)).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSupportedWithPredicate() {
|
||||
SemanticApiVersionParser.Version parsedVersion = parser.parseVersion("1.2");
|
||||
testValidate("1.2", apiVersionStrategy(null, false, version -> version.equals(parsedVersion)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateUnsupportedWithPredicate() {
|
||||
DefaultApiVersionStrategy strategy = apiVersionStrategy(null, false, version -> version.equals("1.2"));
|
||||
assertThatThrownBy(() -> testValidate("1.2", strategy)).isInstanceOf(InvalidApiVersionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionRequiredAndDefaultVersionSet() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() ->
|
||||
new org.springframework.web.accept.DefaultApiVersionStrategy(
|
||||
List.of(request -> request.getParameter("api-version")), new SemanticApiVersionParser(),
|
||||
true, "1.2", true, version -> true, null))
|
||||
.withMessage("versionRequired cannot be set to true if a defaultVersion is also configured");
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private void testValidate(@Nullable String version, DefaultApiVersionStrategy strategy) {
|
||||
MockServerHttpRequest.BaseBuilder<?> requestBuilder = MockServerHttpRequest.get("/");
|
||||
if (version != null) {
|
||||
requestBuilder.queryParam("api-version", version);
|
||||
}
|
||||
strategy.resolveParseAndValidateVersion(MockServerWebExchange.builder(requestBuilder).build());
|
||||
MockServerWebExchange exchange = MockServerWebExchange.builder(builder).build();
|
||||
return strategy.resolveParseAndValidate(exchange).map(Object::toString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user