Skip to content

Commit 1aaef07

Browse files
committed
Apply API versioning path segment strategy last
This commit adapts the auto-configuration for API versioning to apply the path segment last as this strategy is not meant to yield. It also clarifies that configuration properties should not be used if ordering of the strategies is important. Closes gh-49800
1 parent 63fd497 commit 1aaef07

6 files changed

Lines changed: 75 additions & 8 deletions

File tree

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ The same `@Controller` path can be mapped multiple times to support different ve
284284
For more details see {url-spring-framework-docs}/web/webflux/controller/ann-requestmapping.html#webflux-ann-requestmapping-version[Spring Framework's reference documentation].
285285

286286
Once mappings have been added, you additionally need to configure Spring WebFlux so that it is able to use any version information sent with a request.
287-
Typically, versions are sent as HTTP headers, query parameters or as part of the path.
287+
Typically, versions are sent as HTTP headers, query parameters, media type parameters, or as part of the path.
288288

289289
To configure Spring WebFlux, you can either use a javadoc:org.springframework.web.reactive.config.WebFluxConfigurer[] bean and override the `configureApiVersioning(...)` method, or you can use properties.
290290

@@ -300,7 +300,9 @@ spring:
300300
header: X-Version
301301
----
302302

303-
For more complete control, you can also define javadoc:org.springframework.web.reactive.accept.ApiVersionResolver[], javadoc:org.springframework.web.accept.ApiVersionParser[] and javadoc:org.springframework.web.reactive.accept.ApiVersionDeprecationHandler[] beans which will be injected into the auto-configured Spring MVC configuration.
303+
NOTE: If your setup requires multiple strategies, such as header and query parameter, consider declaring the order programmatically by overriding the `configureApiVersioning` method.
304+
305+
For more complete control, you can also define javadoc:org.springframework.web.reactive.accept.ApiVersionResolver[], javadoc:org.springframework.web.accept.ApiVersionParser[] and javadoc:org.springframework.web.reactive.accept.ApiVersionDeprecationHandler[] beans which will be injected into the auto-configured Spring WebFlux configuration.
304306

305307
TIP: API versioning is also supported on the client-side with both `WebClient` and `RestClient`.
306308
See xref:io/rest-client.adoc#io.rest-client.apiversioning[] for details.

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,8 @@ The same `@Controller` path can be mapped multiple times to support different ve
474474

475475
For more details see {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-version[Spring Framework's reference documentation].
476476

477-
One mappings have been added, you additionally need to configure Spring MVC so that it is able to use any version information sent with a request.
478-
Typically, versions are sent as HTTP headers, query parameters or as part of the path.
477+
Once mappings have been added, you additionally need to configure Spring MVC so that it is able to use any version information sent with a request.
478+
Typically, versions are sent as HTTP headers, query parameters, media type parameters, or as part of the path.
479479

480480
To configure Spring MVC, you can either use a javadoc:org.springframework.web.servlet.config.annotation.WebMvcConfigurer[] bean and override the `configureApiVersioning(...)` method, or you can use properties.
481481

@@ -491,6 +491,8 @@ spring:
491491
header: X-Version
492492
----
493493

494+
NOTE: If your setup requires multiple strategies, such as header and query parameter, consider declaring the order programmatically by overriding the `configureApiVersioning` method.
495+
494496
For more complete control, you can also define javadoc:org.springframework.web.accept.ApiVersionResolver[], javadoc:org.springframework.web.accept.ApiVersionParser[] and javadoc:org.springframework.web.accept.ApiVersionDeprecationHandler[] beans which will be injected into the auto-configured Spring MVC configuration.
495497

496498
TIP: API versioning is also supported with both `WebClient` and `RestClient`.

module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,8 @@ private void configureApiVersioningUse(ApiVersionConfigurer configurer, Use use)
299299
PropertyMapper map = PropertyMapper.get();
300300
map.from(use::getHeader).whenHasText().to(configurer::useRequestHeader);
301301
map.from(use::getQueryParameter).whenHasText().to(configurer::useQueryParam);
302+
use.getMediaTypeParameter().forEach(configurer::useMediaTypeParameter);
302303
map.from(use::getPathSegment).to(configurer::usePathSegment);
303-
use.getMediaTypeParameter()
304-
.forEach((mediaType, parameterName) -> configurer.useMediaTypeParameter(mediaType, parameterName));
305304
}
306305

307306
}

module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,35 @@ void apiVersionUseMediaTypeParameterPropertyIsApplied() {
896896
});
897897
}
898898

899+
@Test
900+
void apiVersionUsesPathSegmentLast() {
901+
this.contextRunner
902+
.withPropertyValues("spring.webflux.apiversion.use.path-segment=1",
903+
"spring.webflux.apiversion.use.header=hv", "spring.webflux.apiversion.use.query-parameter=rpv",
904+
"spring.webflux.apiversion.use.media-type-parameter[application/json]=mtpv")
905+
.run((context) -> {
906+
DefaultApiVersionStrategy versionStrategy = context.getBean("webFluxApiVersionStrategy",
907+
DefaultApiVersionStrategy.class);
908+
909+
MockServerWebExchange requestWithHeader = MockServerWebExchange
910+
.from(MockServerHttpRequest.get("https://example.com/test/456").header("hv", "123"));
911+
assertThat(versionStrategy.resolveVersion(requestWithHeader)).isEqualTo("123");
912+
913+
MockServerWebExchange requestWithQueryParameter = MockServerWebExchange
914+
.from(MockServerHttpRequest.get("https://example.com?rpv=123"));
915+
assertThat(versionStrategy.resolveVersion(requestWithQueryParameter)).isEqualTo("123");
916+
917+
MockServerWebExchange requestWithMediaType = MockServerWebExchange
918+
.from(MockServerHttpRequest.get("https://example.com/test/456")
919+
.header("content-type", "application/json;mtpv=123"));
920+
assertThat(versionStrategy.resolveVersion(requestWithMediaType)).isEqualTo("123");
921+
922+
MockServerWebExchange requestFallbacksToApiSegment = MockServerWebExchange
923+
.from(MockServerHttpRequest.get("https://example.com/test/456"));
924+
assertThat(versionStrategy.resolveVersion(requestFallbacksToApiSegment)).isEqualTo("456");
925+
});
926+
}
927+
899928
@Test
900929
void apiVersionBeansAreInjected() {
901930
this.contextRunner.withUserConfiguration(ApiVersionConfiguration.class).run((context) -> {

module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,8 @@ private void configureApiVersioningUse(ApiVersionConfigurer configurer, Use use)
419419
PropertyMapper map = PropertyMapper.get();
420420
map.from(use::getHeader).whenHasText().to(configurer::useRequestHeader);
421421
map.from(use::getQueryParameter).whenHasText().to(configurer::useQueryParam);
422+
use.getMediaTypeParameter().forEach(configurer::useMediaTypeParameter);
422423
map.from(use::getPathSegment).to(configurer::usePathSegment);
423-
use.getMediaTypeParameter()
424-
.forEach((mediaType, parameterName) -> configurer.useMediaTypeParameter(mediaType, parameterName));
425424
}
426425

427426
@Bean

module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,42 @@ void apiVersionUseMediaTypeParameterPropertyIsApplied() {
11091109
});
11101110
}
11111111

1112+
@Test
1113+
void apiVersionUsesPathSegmentLast() {
1114+
this.contextRunner
1115+
.withPropertyValues("spring.mvc.apiversion.use.path-segment=1", "spring.mvc.apiversion.use.header=hv",
1116+
"spring.mvc.apiversion.use.query-parameter=rpv",
1117+
"spring.mvc.apiversion.use.media-type-parameter[application/json]=mtpv")
1118+
.run((context) -> {
1119+
ApiVersionStrategy versionStrategy = context.getBean("mvcApiVersionStrategy", ApiVersionStrategy.class);
1120+
1121+
MockHttpServletRequest requestWithHeader = new MockHttpServletRequest("GET",
1122+
"https://example.com/test/456");
1123+
requestWithHeader.addHeader("hv", "123");
1124+
ServletRequestPathUtils.setParsedRequestPath(RequestPath.parse("/test/456", "/"), requestWithHeader);
1125+
assertThat(versionStrategy.resolveVersion(requestWithHeader)).isEqualTo("123");
1126+
1127+
MockHttpServletRequest requestWithQueryParameter = new MockHttpServletRequest("GET",
1128+
"https://example.com/test/456");
1129+
requestWithQueryParameter.setQueryString("rpv=123");
1130+
ServletRequestPathUtils.setParsedRequestPath(RequestPath.parse("/test/456", "/"),
1131+
requestWithQueryParameter);
1132+
assertThat(versionStrategy.resolveVersion(requestWithQueryParameter)).isEqualTo("123");
1133+
1134+
MockHttpServletRequest requestWithMediaType = new MockHttpServletRequest("GET",
1135+
"https://example.com/test/456");
1136+
ServletRequestPathUtils.setParsedRequestPath(RequestPath.parse("/test/456", "/"), requestWithMediaType);
1137+
requestWithMediaType.addHeader(HttpHeaders.CONTENT_TYPE, "application/json;mtpv=123");
1138+
assertThat(versionStrategy.resolveVersion(requestWithMediaType)).isEqualTo("123");
1139+
1140+
MockHttpServletRequest requestFallbacksToApiSegment = new MockHttpServletRequest("GET",
1141+
"https://example.com/test/456");
1142+
ServletRequestPathUtils.setParsedRequestPath(RequestPath.parse("/test/456", "/"),
1143+
requestFallbacksToApiSegment);
1144+
assertThat(versionStrategy.resolveVersion(requestFallbacksToApiSegment)).isEqualTo("456");
1145+
});
1146+
}
1147+
11121148
@Test
11131149
void apiVersionBeansAreInjected() {
11141150
this.contextRunner.withUserConfiguration(ApiVersionConfiguration.class).run((context) -> {

0 commit comments

Comments
 (0)