diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java index 020c8ce1ecc..0d468b3357f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java @@ -29,8 +29,7 @@ import org.springframework.web.server.ServerWebExchange; * @since 7.0 */ @FunctionalInterface -public -interface ApiVersionResolver { +public interface ApiVersionResolver { /** * Resolve the version for the given exchange. @@ -40,15 +39,17 @@ interface ApiVersionResolver { * @return {@code Mono} emitting the version value, or an empty {@code Mono} * @since 7.0.3 */ - default Mono resolveApiVersion(ServerWebExchange exchange){ - return Mono.justOrEmpty(this.resolveVersion(exchange)); - } + Mono resolveApiVersion(ServerWebExchange exchange); /** * Resolve the version for the given exchange. * @param exchange the current exchange * @return the version value, or {@code null} if not found + * @deprecated in favor of {@link #resolveApiVersion(ServerWebExchange)} */ - @Nullable String resolveVersion(ServerWebExchange exchange); + @Deprecated(since = "7.0.3", forRemoval = true) + default @Nullable String resolveVersion(ServerWebExchange exchange) { + return null; + } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionStrategy.java index 2e084aa7a29..fc71a6c5257 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionStrategy.java @@ -88,40 +88,31 @@ public interface ApiVersionStrategy { * or an empty {@code Mono} if there is no version * @since 7.0.3 */ - @SuppressWarnings("Convert2MethodRef") default Mono> resolveParseAndValidateApiVersion(ServerWebExchange exchange) { - return this.resolveApiVersion(exchange) - .switchIfEmpty(Mono.justOrEmpty(this.getDefaultVersion()) - .mapNotNull(comparable -> comparable.toString())) - .>handle((version, sink) -> { + + Mono> result = resolveApiVersion(exchange) + .map(value -> { + Comparable version; try { - sink.next(this.parseVersion(version)); + version = parseVersion(value); } catch (Exception ex) { - sink.error(new InvalidApiVersionException(version, null, ex)); + throw new InvalidApiVersionException(value, null, ex); } - }) - .flatMap(version -> this.validateVersionAsync(version, exchange)) - .switchIfEmpty(this.validateVersionAsync(null, exchange)); - } + validateVersion(version, exchange); + return version; + }); - /** - * Validates the provided request version against the requirements and constraints - * of the API. If the validation succeeds, the version is returned, otherwise an - * error is emitted. - * @param requestVersion the version to validate, which may be null - * @param exchange the current server exchange representing the HTTP request and response - * @return a {@code Mono} emitting the validated request version as a {@code Comparable}, - * or an error if validation fails - */ - default Mono> validateVersionAsync(@Nullable Comparable requestVersion, ServerWebExchange exchange) { - try { - this.validateVersion(requestVersion, exchange); - return Mono.justOrEmpty(requestVersion); - } - catch (MissingApiVersionException | InvalidApiVersionException ex) { - return Mono.error(ex); - } + return result.switchIfEmpty(Mono.fromSupplier(() -> { + Comparable defaultVersion = getDefaultVersion(); + if (defaultVersion != null) { + return defaultVersion; + } + else { + validateVersion(null, exchange); + return null; + } + })); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java index 4d7871dec63..07863cefd91 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java @@ -26,7 +26,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class HeaderApiVersionResolver implements ApiVersionResolver { +public class HeaderApiVersionResolver implements SyncApiVersionResolver { private final String headerName; @@ -37,7 +37,7 @@ public class HeaderApiVersionResolver implements ApiVersionResolver { @Override - public @Nullable String resolveVersion(ServerWebExchange exchange) { + public @Nullable String resolveVersionValue(ServerWebExchange exchange) { return exchange.getRequest().getHeaders().getFirst(this.headerName); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java index c9967091088..db9b9acd3b5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java @@ -29,7 +29,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class MediaTypeParamApiVersionResolver implements ApiVersionResolver { +public class MediaTypeParamApiVersionResolver implements SyncApiVersionResolver { private final MediaType compatibleMediaType; @@ -49,7 +49,7 @@ public class MediaTypeParamApiVersionResolver implements ApiVersionResolver { @Override - public @Nullable String resolveVersion(ServerWebExchange exchange) { + public @Nullable String resolveVersionValue(ServerWebExchange exchange) { HttpHeaders headers = exchange.getRequest().getHeaders(); for (MediaType mediaType : headers.getAccept()) { if (this.compatibleMediaType.isCompatibleWith(mediaType)) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java index 2da6819498b..06de39d20be 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java @@ -32,7 +32,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class PathApiVersionResolver implements ApiVersionResolver { +public class PathApiVersionResolver implements SyncApiVersionResolver { private final int pathSegmentIndex; @@ -49,7 +49,7 @@ public class PathApiVersionResolver implements ApiVersionResolver { @Override - public String resolveVersion(ServerWebExchange exchange) { + public String resolveVersionValue(ServerWebExchange exchange) { int i = 0; for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) { if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java index 16606c75cbc..f81f832bfea 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java @@ -26,7 +26,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class QueryApiVersionResolver implements ApiVersionResolver { +public class QueryApiVersionResolver implements SyncApiVersionResolver { private final String queryParamName; @@ -37,7 +37,7 @@ public class QueryApiVersionResolver implements ApiVersionResolver { @Override - public @Nullable String resolveVersion(ServerWebExchange exchange) { + public @Nullable String resolveVersionValue(ServerWebExchange exchange) { return exchange.getRequest().getQueryParams().getFirst(this.queryParamName); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java new file mode 100644 index 00000000000..bca707274d2 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.reactive.accept; + +import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Mono; + +import org.springframework.web.server.ServerWebExchange; + +/** + * An extension of {@link ApiVersionResolver}s for implementations that can + * resolve the version in an imperative way without blocking. + * + * @author Rossen Stoyanchev + * @since 7.0.3 + */ +@FunctionalInterface +public interface SyncApiVersionResolver extends ApiVersionResolver { + + /** + * {@inheritDoc} + *

This method delegates to the synchronous + * {@link #resolveVersionValue} and wraps the result as {@code Mono}. + */ + @Override + default Mono resolveApiVersion(ServerWebExchange exchange) { + return Mono.justOrEmpty(resolveVersionValue(exchange)); + } + + /** + * Resolve the version for the given exchange imperatively without blocking. + * @param exchange the current exchange + * @return the version value, or {@code null} if not found + */ + @Nullable String resolveVersionValue(ServerWebExchange exchange); + + @SuppressWarnings("removal") + @Override + default @Nullable String resolveVersion(ServerWebExchange exchange) { + return resolveVersionValue(exchange); + } + +} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java index c2be3b1fe8c..99015f12f41 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java @@ -137,7 +137,7 @@ public class DefaultApiVersionStrategiesTests { @Nullable Predicate> supportedVersionPredicate) { return new DefaultApiVersionStrategy( - List.of(exchange -> exchange.getRequest().getQueryParams().getFirst("api-version")), + List.of(new QueryApiVersionResolver("api-version")), parser, null, defaultVersion, detectSupportedVersions, supportedVersionPredicate, null); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java index ec779c637f9..c06b7c4f784 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.function.Function; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -380,7 +381,7 @@ class RequestPredicatesTests { private static DefaultApiVersionStrategy apiVersionStrategy() { return new DefaultApiVersionStrategy( - List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null, null); + List.of(exchange -> Mono.empty()), new SemanticApiVersionParser(), true, null, false, null, null); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/VersionRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/VersionRequestConditionTests.java index e658ec41da0..06acba7573d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/VersionRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/VersionRequestConditionTests.java @@ -28,6 +28,7 @@ import org.springframework.web.accept.NotAcceptableApiVersionException; import org.springframework.web.accept.SemanticApiVersionParser; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.DefaultApiVersionStrategy; +import org.springframework.web.reactive.accept.QueryApiVersionResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.server.MockServerWebExchange; @@ -51,7 +52,7 @@ public class VersionRequestConditionTests { private static DefaultApiVersionStrategy initVersionStrategy(@Nullable String defaultVersion) { return new DefaultApiVersionStrategy( - List.of(exchange -> exchange.getRequest().getQueryParams().getFirst("api-version")), + List.of(new QueryApiVersionResolver("api-version")), new SemanticApiVersionParser(), null, defaultVersion, false, null, null); }