Prepare to support API versioning for fn
Add default method to resolve, parse, and validate version Simplify tests
This commit is contained in:
parent
d045f44693
commit
224f1af08e
|
@ -62,6 +62,27 @@ public interface ApiVersionStrategy {
|
||||||
*/
|
*/
|
||||||
@Nullable Comparable<?> getDefaultVersion();
|
@Nullable Comparable<?> getDefaultVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to return the parsed and validated request version,
|
||||||
|
* or the default version if configured.
|
||||||
|
* @param request the current request
|
||||||
|
* @return the parsed request version, or the default version
|
||||||
|
*/
|
||||||
|
default @Nullable Comparable<?> resolveParseAndValidateVersion(HttpServletRequest request) {
|
||||||
|
String value = resolveVersion(request);
|
||||||
|
if (value == null) {
|
||||||
|
return getDefaultVersion();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Comparable<?> version = parseVersion(value);
|
||||||
|
validateVersion(version, request);
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new InvalidApiVersionException(value, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the requested API version is deprecated, and if so handle it
|
* Check if the requested API version is deprecated, and if so handle it
|
||||||
* accordingly, e.g. by setting response headers to signal the deprecation,
|
* accordingly, e.g. by setting response headers to signal the deprecation,
|
||||||
|
|
|
@ -64,6 +64,27 @@ public interface ApiVersionStrategy {
|
||||||
*/
|
*/
|
||||||
@Nullable Comparable<?> getDefaultVersion();
|
@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
|
||||||
|
*/
|
||||||
|
default @Nullable Comparable<?> resolveParseAndValidateVersion(ServerWebExchange exchange) {
|
||||||
|
String value = resolveVersion(exchange);
|
||||||
|
if (value == null) {
|
||||||
|
return getDefaultVersion();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Comparable<?> version = parseVersion(value);
|
||||||
|
validateVersion(version, exchange);
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new InvalidApiVersionException(value, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the requested API version is deprecated, and if so handle it
|
* Check if the requested API version is deprecated, and if so handle it
|
||||||
* accordingly, e.g. by setting response headers to signal the deprecation,
|
* accordingly, e.g. by setting response headers to signal the deprecation,
|
||||||
|
|
|
@ -93,6 +93,16 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy {
|
||||||
return this.defaultVersion;
|
return this.defaultVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the strategy is configured to detect supported versions.
|
||||||
|
* If this is set to {@code false} then {@link #addMappedVersion} is ignored
|
||||||
|
* and the list of supported versions can be built explicitly through calls
|
||||||
|
* to {@link #addSupportedVersion}.
|
||||||
|
*/
|
||||||
|
public boolean detectSupportedVersions() {
|
||||||
|
return this.detectSupportedVersions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add to the list of supported versions to check against in
|
* Add to the list of supported versions to check against in
|
||||||
* {@link ApiVersionStrategy#validateVersion} before raising
|
* {@link ApiVersionStrategy#validateVersion} before raising
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
import org.springframework.web.accept.InvalidApiVersionException;
|
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -180,7 +179,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
if (this.apiVersionStrategy != null) {
|
if (this.apiVersionStrategy != null) {
|
||||||
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
version = getApiVersion(exchange, this.apiVersionStrategy);
|
version = this.apiVersionStrategy.resolveParseAndValidateVersion(exchange);
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
exchange.getAttributes().put(API_VERSION_ATTRIBUTE, version);
|
exchange.getAttributes().put(API_VERSION_ATTRIBUTE, version);
|
||||||
}
|
}
|
||||||
|
@ -189,21 +188,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
return super.getHandlerInternal(exchange);
|
return super.getHandlerInternal(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Comparable<?> getApiVersion(ServerWebExchange exchange, ApiVersionStrategy strategy) {
|
|
||||||
String value = strategy.resolveVersion(exchange);
|
|
||||||
if (value == null) {
|
|
||||||
return strategy.getDefaultVersion();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Comparable<?> version = strategy.parseVersion(value);
|
|
||||||
strategy.validateVersion(version, exchange);
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new InvalidApiVersionException(value, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
||||||
* and {@link HttpExchange @HttpExchange} annotations to create the
|
* and {@link HttpExchange @HttpExchange} annotations to create the
|
||||||
|
|
|
@ -50,29 +50,24 @@ public class RequestMappingVersionIntegrationTests extends AbstractRequestMappin
|
||||||
|
|
||||||
|
|
||||||
@ParameterizedHttpServerTest
|
@ParameterizedHttpServerTest
|
||||||
void initialVersion(HttpServer httpServer) throws Exception {
|
void mapVersion(HttpServer httpServer) throws Exception {
|
||||||
startServer(httpServer);
|
startServer(httpServer);
|
||||||
|
|
||||||
assertThat(exchangeWithVersion("1.0").getBody()).isEqualTo("none");
|
assertThat(exchangeWithVersion("1.0").getBody()).isEqualTo("none");
|
||||||
assertThat(exchangeWithVersion("1.1").getBody()).isEqualTo("none");
|
assertThat(exchangeWithVersion("1.1").getBody()).isEqualTo("none");
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedHttpServerTest
|
|
||||||
void baselineVersion(HttpServer httpServer) throws Exception {
|
|
||||||
startServer(httpServer);
|
|
||||||
assertThat(exchangeWithVersion("1.2").getBody()).isEqualTo("1.2");
|
assertThat(exchangeWithVersion("1.2").getBody()).isEqualTo("1.2");
|
||||||
assertThat(exchangeWithVersion("1.3").getBody()).isEqualTo("1.2");
|
assertThat(exchangeWithVersion("1.3").getBody()).isEqualTo("1.2");
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedHttpServerTest
|
|
||||||
void fixedVersion(HttpServer httpServer) throws Exception {
|
|
||||||
startServer(httpServer);
|
|
||||||
assertThat(exchangeWithVersion("1.5").getBody()).isEqualTo("1.5");
|
assertThat(exchangeWithVersion("1.5").getBody()).isEqualTo("1.5");
|
||||||
assertThatThrownBy(() -> exchangeWithVersion("1.6")).isInstanceOf(HttpClientErrorException.BadRequest.class);
|
|
||||||
|
assertThatThrownBy(() -> exchangeWithVersion("1.6"))
|
||||||
|
.as("Should reject if highest supported below request version is fixed")
|
||||||
|
.isInstanceOf(HttpClientErrorException.BadRequest.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedHttpServerTest
|
@ParameterizedHttpServerTest
|
||||||
void deprecation(HttpServer httpServer) throws Exception {
|
void deprecation(HttpServer httpServer) throws Exception {
|
||||||
startServer(httpServer);
|
startServer(httpServer);
|
||||||
|
|
||||||
assertThat(exchangeWithVersion("1").getHeaders().getFirst("Link"))
|
assertThat(exchangeWithVersion("1").getHeaders().getFirst("Link"))
|
||||||
.isEqualTo("<https://example.org/deprecation>; rel=\"deprecation\"; type=\"text/html\"");
|
.isEqualTo("<https://example.org/deprecation>; rel=\"deprecation\"; type=\"text/html\"");
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ import org.springframework.util.StringValueResolver;
|
||||||
import org.springframework.web.accept.ApiVersionStrategy;
|
import org.springframework.web.accept.ApiVersionStrategy;
|
||||||
import org.springframework.web.accept.ContentNegotiationManager;
|
import org.springframework.web.accept.ContentNegotiationManager;
|
||||||
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
||||||
import org.springframework.web.accept.InvalidApiVersionException;
|
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -207,7 +206,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
if (this.apiVersionStrategy != null) {
|
if (this.apiVersionStrategy != null) {
|
||||||
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
version = getApiVersion(request, this.apiVersionStrategy);
|
version = this.apiVersionStrategy.resolveParseAndValidateVersion(request);
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
request.setAttribute(API_VERSION_ATTRIBUTE, version);
|
request.setAttribute(API_VERSION_ATTRIBUTE, version);
|
||||||
}
|
}
|
||||||
|
@ -216,21 +215,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
return super.getHandlerInternal(request);
|
return super.getHandlerInternal(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Comparable<?> getApiVersion(HttpServletRequest request, ApiVersionStrategy strategy) {
|
|
||||||
String value = strategy.resolveVersion(request);
|
|
||||||
if (value == null) {
|
|
||||||
return strategy.getDefaultVersion();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Comparable<?> version = strategy.parseVersion(value);
|
|
||||||
strategy.validateVersion(version, request);
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new InvalidApiVersionException(value, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
||||||
* and {@link HttpExchange @HttpExchange} annotations to create the
|
* and {@link HttpExchange @HttpExchange} annotations to create the
|
||||||
|
|
|
@ -59,23 +59,17 @@ public class RequestMappingVersionHandlerMethodTests {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void initialVersion() throws Exception {
|
void mapVersion() throws Exception {
|
||||||
assertThat(requestWithVersion("1.0").getContentAsString()).isEqualTo("none");
|
assertThat(requestWithVersion("1.0").getContentAsString()).isEqualTo("none");
|
||||||
assertThat(requestWithVersion("1.1").getContentAsString()).isEqualTo("none");
|
assertThat(requestWithVersion("1.1").getContentAsString()).isEqualTo("none");
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void baselineVersion() throws Exception {
|
|
||||||
assertThat(requestWithVersion("1.2").getContentAsString()).isEqualTo("1.2");
|
assertThat(requestWithVersion("1.2").getContentAsString()).isEqualTo("1.2");
|
||||||
assertThat(requestWithVersion("1.3").getContentAsString()).isEqualTo("1.2");
|
assertThat(requestWithVersion("1.3").getContentAsString()).isEqualTo("1.2");
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fixedVersion() throws Exception {
|
|
||||||
assertThat(requestWithVersion("1.5").getContentAsString()).isEqualTo("1.5");
|
assertThat(requestWithVersion("1.5").getContentAsString()).isEqualTo("1.5");
|
||||||
|
|
||||||
MockHttpServletResponse response = requestWithVersion("1.6");
|
MockHttpServletResponse response = requestWithVersion("1.6");
|
||||||
assertThat(response.getStatus()).isEqualTo(400);
|
assertThat(response.getStatus())
|
||||||
|
.as("Should reject if highest supported below request version is fixed")
|
||||||
|
.isEqualTo(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue