From 08ccf46399b76bffb4b9347a8848e25d424bf167 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 31 Jul 2025 14:23:49 +0100 Subject: [PATCH] Rename request param version strategy to query param Closes gh-35263 --- .../ROOT/pages/web/webflux-versioning.adoc | 4 +- .../ROOT/pages/web/webmvc-versioning.adoc | 4 +- .../server/samples/ApiVersionTests.java | 2 +- .../web/accept/QueryApiVersionResolver.java | 53 +++++++++++++++ .../accept/QueryApiVersionResolverTests.java | 65 +++++++++++++++++++ .../reactive/config/ApiVersionConfigurer.java | 4 +- .../annotation/ApiVersionConfigurer.java | 7 +- 7 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/accept/QueryApiVersionResolver.java create mode 100644 spring-web/src/test/java/org/springframework/web/accept/QueryApiVersionResolverTests.java diff --git a/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc index 6c446bcb142..917fcc95bdf 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc @@ -46,8 +46,8 @@ directly with it. [.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-resolver[See equivalent in the Servlet stack]# This strategy resolves the API version from a request. The WebFlux config provides built-in -options to resolve from a header, a request parameter, or from the URL path. -You can also use a custom `ApiVersionResolver`. +options to resolve from a header, query parameter, media type parameter, +or from the URL path. You can also use a custom `ApiVersionResolver`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc index 2be54cd8335..f9f0afd77cc 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc @@ -46,8 +46,8 @@ directly with it. [.small]#xref:web/webflux-versioning.adoc#webflux-versioning-resolver[See equivalent in the Reactive stack]# This strategy resolves the API version from a request. The MVC config provides built-in -options to resolve from a header, from a request parameter, or from the URL path. -You can also use a custom `ApiVersionResolver`. +options to resolve from a header, query parameter, media type parameter, +or from the URL path. You can also use a custom `ApiVersionResolver`. diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ApiVersionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ApiVersionTests.java index 5fd592ce501..6efcc987caf 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ApiVersionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ApiVersionTests.java @@ -54,7 +54,7 @@ public class ApiVersionTests { String param = "api-version"; Map result = performRequest( - configurer -> configurer.useRequestParam(param), + configurer -> configurer.useQueryParam(param), ApiVersionInserter.useQueryParam(param)); assertThat(result.get("query")).isEqualTo(param + "=1.2"); diff --git a/spring-web/src/main/java/org/springframework/web/accept/QueryApiVersionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/QueryApiVersionResolver.java new file mode 100644 index 00000000000..e9d7eb22759 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/accept/QueryApiVersionResolver.java @@ -0,0 +1,53 @@ +/* + * 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.accept; + + +import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; + +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * {@link ApiVersionResolver} that extract the version from a query parameter. + * + * @author Rossen Stoyanchev + * @since 7.0 + */ +public class QueryApiVersionResolver implements ApiVersionResolver { + + private final String queryParamName; + + + public QueryApiVersionResolver(String queryParamName) { + this.queryParamName = queryParamName; + } + + + @Override + public @Nullable String resolveVersion(HttpServletRequest request) { + String query = request.getQueryString(); + if (StringUtils.hasText(query)) { + UriComponents uri = UriComponentsBuilder.fromUriString("?" + query).build(); + return uri.getQueryParams().getFirst(this.queryParamName); + } + return null; + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/accept/QueryApiVersionResolverTests.java b/spring-web/src/test/java/org/springframework/web/accept/QueryApiVersionResolverTests.java new file mode 100644 index 00000000000..62886a78997 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/accept/QueryApiVersionResolverTests.java @@ -0,0 +1,65 @@ +/* + * 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.accept; + + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Test; + +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link QueryApiVersionResolver}. + * @author Rossen Stoyanchev + */ +public class QueryApiVersionResolverTests { + + private final String queryParamName = "api-version"; + + private final QueryApiVersionResolver resolver = new QueryApiVersionResolver(queryParamName); + + + @Test + void resolve() { + MockHttpServletRequest request = initRequest("q=foo&" + queryParamName + "=1.2"); + String version = resolver.resolveVersion(request); + assertThat(version).isEqualTo("1.2"); + } + + @Test + void noQueryString() { + MockHttpServletRequest request = initRequest(null); + String version = resolver.resolveVersion(request); + assertThat(version).isNull(); + } + + @Test + void noQueryParam() { + MockHttpServletRequest request = initRequest("q=foo"); + String version = resolver.resolveVersion(request); + assertThat(version).isNull(); + } + + private static MockHttpServletRequest initRequest(@Nullable String queryString) { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path"); + request.setQueryString(queryString); + return request; + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java index b6c351d9030..049069acfcd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ApiVersionConfigurer.java @@ -71,10 +71,10 @@ public class ApiVersionConfigurer { } /** - * Add a resolver that extracts the API version from a request parameter. + * Add a resolver that extracts the API version from a query string parameter. * @param paramName the parameter name to check */ - public ApiVersionConfigurer useRequestParam(String paramName) { + public ApiVersionConfigurer useQueryParam(String paramName) { this.versionResolvers.add(exchange -> exchange.getRequest().getQueryParams().getFirst(paramName)); return this; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java index d1aea97d8ac..5042170e6a7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ApiVersionConfigurer.java @@ -35,6 +35,7 @@ import org.springframework.web.accept.DefaultApiVersionStrategy; import org.springframework.web.accept.InvalidApiVersionException; import org.springframework.web.accept.MediaTypeParamApiVersionResolver; import org.springframework.web.accept.PathApiVersionResolver; +import org.springframework.web.accept.QueryApiVersionResolver; import org.springframework.web.accept.SemanticApiVersionParser; import org.springframework.web.accept.StandardApiVersionDeprecationHandler; @@ -71,11 +72,11 @@ public class ApiVersionConfigurer { } /** - * Add resolver to extract the version from a request parameter. + * Add resolver to extract the version from a query string parameter. * @param paramName the parameter name to check */ - public ApiVersionConfigurer useRequestParam(String paramName) { - this.versionResolvers.add(request -> request.getParameter(paramName)); + public ApiVersionConfigurer useQueryParam(String paramName) { + this.versionResolvers.add(new QueryApiVersionResolver(paramName)); return this; }