From e5d4d7c13cfe3ca7ecfc48a6d63cdf3002a7efe2 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 6 May 2025 20:36:56 +0100 Subject: [PATCH] Refactor DefaultApiVersionInserter --- .../web/client/ApiVersionInserter.java | 65 +++++++++++ .../web/client/DefaultApiVersionInserter.java | 108 +----------------- .../DefaultApiVersionInserterBuilder.java | 94 +++++++++++++++ .../web/client/RestClientVersionTests.java | 14 +-- .../support/RestClientAdapterTests.java | 4 +- .../client/WebClientVersionTests.java | 15 ++- 6 files changed, 179 insertions(+), 121 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserterBuilder.java diff --git a/spring-web/src/main/java/org/springframework/web/client/ApiVersionInserter.java b/spring-web/src/main/java/org/springframework/web/client/ApiVersionInserter.java index 7dbe29e154b..d9c373f5a60 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ApiVersionInserter.java +++ b/spring-web/src/main/java/org/springframework/web/client/ApiVersionInserter.java @@ -18,6 +18,8 @@ package org.springframework.web.client; import java.net.URI; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpHeaders; /** @@ -49,4 +51,67 @@ public interface ApiVersionInserter { default void insertVersion(Object version, HttpHeaders headers) { } + + /** + * Create a builder for an inserter that sets a header. + * @param header the name of a header to hold the version + */ + static Builder fromHeader(@Nullable String header) { + return new DefaultApiVersionInserterBuilder(header, null, null); + } + + /** + * Create a builder for an inserter that sets a query parameter. + * @param queryParam the name of a query parameter to hold the version + */ + static Builder fromQueryParam(@Nullable String queryParam) { + return new DefaultApiVersionInserterBuilder(null, queryParam, null); + } + + /** + * Create a builder for an inserter that inserts a path segment. + * @param pathSegmentIndex the index of the path segment to hold the version + */ + static Builder fromPathSegment(@Nullable Integer pathSegmentIndex) { + return new DefaultApiVersionInserterBuilder(null, null, pathSegmentIndex); + } + + + /** + * Builder for {@link ApiVersionInserter}. + */ + interface Builder { + + /** + * Configure the inserter to set a header. + * @param header the name of the header to hold the version + */ + Builder fromHeader(@Nullable String header); + + /** + * Configure the inserter to set a query parameter. + * @param queryParam the name of the query parameter to hold the version + */ + Builder fromQueryParam(@Nullable String queryParam); + + /** + * Configure the inserter to insert a path segment. + * @param pathSegmentIndex the index of the path segment to hold the version + */ + Builder fromPathSegment(@Nullable Integer pathSegmentIndex); + + /** + * Format the version Object into a String using the given {@link ApiVersionFormatter}. + *

By default, the version is formatted with {@link Object#toString()}. + * @param versionFormatter the formatter to use + */ + Builder withVersionFormatter(ApiVersionFormatter versionFormatter); + + /** + * Build the {@link ApiVersionInserter} instance. + */ + ApiVersionInserter build(); + + } + } diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserter.java b/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserter.java index e6119f3a5ba..107f2b13049 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserter.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserter.java @@ -27,15 +27,13 @@ import org.springframework.util.Assert; import org.springframework.web.util.UriComponentsBuilder; /** - * Default implementation of {@link ApiVersionInserter} to insert the version - * into a request header, query parameter, or the URL path. - * - *

Use {@link #builder()} to create an instance. + * Default implementation of {@link ApiVersionInserter}. * * @author Rossen Stoyanchev * @since 7.0 + * @see DefaultApiVersionInserterBuilder */ -public final class DefaultApiVersionInserter implements ApiVersionInserter { +final class DefaultApiVersionInserter implements ApiVersionInserter { private final @Nullable String header; @@ -46,7 +44,7 @@ public final class DefaultApiVersionInserter implements ApiVersionInserter { private final ApiVersionFormatter versionFormatter; - private DefaultApiVersionInserter( + DefaultApiVersionInserter( @Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex, @Nullable ApiVersionFormatter formatter) { @@ -92,102 +90,4 @@ public final class DefaultApiVersionInserter implements ApiVersionInserter { } } - - /** - * Create a builder for an inserter that sets a header. - * @param header the name of a header to hold the version - */ - public static Builder fromHeader(@Nullable String header) { - return new Builder(header, null, null); - } - - /** - * Create a builder for an inserter that sets a query parameter. - * @param queryParam the name of a query parameter to hold the version - */ - public static Builder fromQueryParam(@Nullable String queryParam) { - return new Builder(null, queryParam, null); - } - - /** - * Create a builder for an inserter that inserts a path segment. - * @param pathSegmentIndex the index of the path segment to hold the version - */ - public static Builder fromPathSegment(@Nullable Integer pathSegmentIndex) { - return new Builder(null, null, pathSegmentIndex); - } - - /** - * Create a builder. - */ - public static Builder builder() { - return new Builder(null, null, null); - } - - - /** - * A builder for {@link DefaultApiVersionInserter}. - */ - public static final class Builder { - - private @Nullable String header; - - private @Nullable String queryParam; - - private @Nullable Integer pathSegmentIndex; - - private @Nullable ApiVersionFormatter versionFormatter; - - private Builder(@Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex) { - this.header = header; - this.queryParam = queryParam; - this.pathSegmentIndex = pathSegmentIndex; - } - - /** - * Configure the inserter to set a header. - * @param header the name of the header to hold the version - */ - public Builder fromHeader(@Nullable String header) { - this.header = header; - return this; - } - - /** - * Configure the inserter to set a query parameter. - * @param queryParam the name of the query parameter to hold the version - */ - public Builder fromQueryParam(@Nullable String queryParam) { - this.queryParam = queryParam; - return this; - } - - /** - * Configure the inserter to insert a path segment. - * @param pathSegmentIndex the index of the path segment to hold the version - */ - public Builder fromPathSegment(@Nullable Integer pathSegmentIndex) { - this.pathSegmentIndex = pathSegmentIndex; - return this; - } - - /** - * Format the version Object into a String using the given {@link ApiVersionFormatter}. - *

By default, the version is formatted with {@link Object#toString()}. - * @param versionFormatter the formatter to use - */ - public Builder withVersionFormatter(ApiVersionFormatter versionFormatter) { - this.versionFormatter = versionFormatter; - return this; - } - - /** - * Build the inserter. - */ - public ApiVersionInserter build() { - return new DefaultApiVersionInserter( - this.header, this.queryParam, this.pathSegmentIndex, this.versionFormatter); - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserterBuilder.java b/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserterBuilder.java new file mode 100644 index 00000000000..07dc40ba6af --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserterBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2025 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.client; + +import org.jspecify.annotations.Nullable; + +/** + * Default implementation of {@link ApiVersionInserter.Builder}. + * + * @author Rossen Stoyanchev + * @since 7.0 + * @see ApiVersionInserter#fromHeader(String) + * @see ApiVersionInserter#fromQueryParam(String) + * @see ApiVersionInserter#fromPathSegment(Integer) + */ +final class DefaultApiVersionInserterBuilder implements ApiVersionInserter.Builder { + + private @Nullable String header; + + private @Nullable String queryParam; + + private @Nullable Integer pathSegmentIndex; + + private @Nullable ApiVersionFormatter versionFormatter; + + + DefaultApiVersionInserterBuilder( + @Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex) { + + this.header = header; + this.queryParam = queryParam; + this.pathSegmentIndex = pathSegmentIndex; + } + + /** + * Configure the inserter to set a header. + * @param header the name of the header to hold the version + */ + public ApiVersionInserter.Builder fromHeader(@Nullable String header) { + this.header = header; + return this; + } + + /** + * Configure the inserter to set a query parameter. + * @param queryParam the name of the query parameter to hold the version + */ + public ApiVersionInserter.Builder fromQueryParam(@Nullable String queryParam) { + this.queryParam = queryParam; + return this; + } + + /** + * Configure the inserter to insert a path segment. + * @param pathSegmentIndex the index of the path segment to hold the version + */ + public ApiVersionInserter.Builder fromPathSegment(@Nullable Integer pathSegmentIndex) { + this.pathSegmentIndex = pathSegmentIndex; + return this; + } + + /** + * Format the version Object into a String using the given {@link ApiVersionFormatter}. + *

By default, the version is formatted with {@link Object#toString()}. + * @param versionFormatter the formatter to use + */ + public ApiVersionInserter.Builder withVersionFormatter(ApiVersionFormatter versionFormatter) { + this.versionFormatter = versionFormatter; + return this; + } + + /** + * Build the inserter. + */ + public ApiVersionInserter build() { + return new DefaultApiVersionInserter( + this.header, this.queryParam, this.pathSegmentIndex, this.versionFormatter); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientVersionTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientVersionTests.java index d607f182b3a..27d905e072b 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientVersionTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientVersionTests.java @@ -59,45 +59,45 @@ public class RestClientVersionTests { @Test void header() { - performRequest(DefaultApiVersionInserter.fromHeader("X-API-Version")); + performRequest(ApiVersionInserter.fromHeader("X-API-Version")); expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2")); } @Test void queryParam() { - performRequest(DefaultApiVersionInserter.fromQueryParam("api-version")); + performRequest(ApiVersionInserter.fromQueryParam("api-version")); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path?api-version=1.2")); } @Test void pathSegmentIndexLessThanSize() { - performRequest(DefaultApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v)); + performRequest(ApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v)); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/v1.2/path")); } @Test void pathSegmentIndexEqualToSize() { - performRequest(DefaultApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v)); + performRequest(ApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v)); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path/v1.2")); } @Test void pathSegmentIndexGreaterThanSize() { assertThatIllegalStateException() - .isThrownBy(() -> performRequest(DefaultApiVersionInserter.fromPathSegment(2))) + .isThrownBy(() -> performRequest(ApiVersionInserter.fromPathSegment(2))) .withMessage("Cannot insert version into '/path' at path segment index 2"); } @Test void defaultVersion() { - ApiVersionInserter inserter = DefaultApiVersionInserter.fromHeader("X-API-Version").build(); + ApiVersionInserter inserter = ApiVersionInserter.fromHeader("X-API-Version").build(); RestClient restClient = restClientBuilder.defaultApiVersion(1.2).apiVersionInserter(inserter).build(); restClient.get().uri("/path").retrieve().body(String.class); expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2")); } - private void performRequest(DefaultApiVersionInserter.Builder builder) { + private void performRequest(ApiVersionInserter.Builder builder) { ApiVersionInserter versionInserter = builder.build(); RestClient restClient = restClientBuilder.apiVersionInserter(versionInserter).build(); diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java index 87047968949..6519abb89cd 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java @@ -49,7 +49,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.client.DefaultApiVersionInserter; +import org.springframework.web.client.ApiVersionInserter; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -274,7 +274,7 @@ class RestClientAdapterTests { void apiVersion() throws Exception { RestClient restClient = RestClient.builder() .baseUrl(anotherServer.url("/").toString()) - .apiVersionInserter(DefaultApiVersionInserter.fromHeader("X-API-Version").build()) + .apiVersionInserter(ApiVersionInserter.fromHeader("X-API-Version").build()) .build(); RestClientAdapter adapter = RestClientAdapter.create(restClient); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientVersionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientVersionTests.java index 035cce64a00..20d54a8c85a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientVersionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientVersionTests.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.web.client.ApiVersionInserter; -import org.springframework.web.client.DefaultApiVersionInserter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -59,45 +58,45 @@ public class WebClientVersionTests { @Test void header() { - performRequest(DefaultApiVersionInserter.fromHeader("X-API-Version")); + performRequest(ApiVersionInserter.fromHeader("X-API-Version")); expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2")); } @Test void queryParam() { - performRequest(DefaultApiVersionInserter.fromQueryParam("api-version")); + performRequest(ApiVersionInserter.fromQueryParam("api-version")); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path?api-version=1.2")); } @Test void pathSegmentIndexLessThanSize() { - performRequest(DefaultApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v)); + performRequest(ApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v)); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/v1.2/path")); } @Test void pathSegmentIndexEqualToSize() { - performRequest(DefaultApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v)); + performRequest(ApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v)); expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path/v1.2")); } @Test void pathSegmentIndexGreaterThanSize() { assertThatIllegalStateException() - .isThrownBy(() -> performRequest(DefaultApiVersionInserter.fromPathSegment(2))) + .isThrownBy(() -> performRequest(ApiVersionInserter.fromPathSegment(2))) .withMessage("Cannot insert version into '/path' at path segment index 2"); } @Test void defaultVersion() { - ApiVersionInserter inserter = DefaultApiVersionInserter.fromHeader("X-API-Version").build(); + ApiVersionInserter inserter = ApiVersionInserter.fromHeader("X-API-Version").build(); WebClient webClient = webClientBuilder.defaultApiVersion(1.2).apiVersionInserter(inserter).build(); webClient.get().uri("/path").retrieve().bodyToMono(String.class).block(); expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2")); } - private void performRequest(DefaultApiVersionInserter.Builder builder) { + private void performRequest(ApiVersionInserter.Builder builder) { ApiVersionInserter versionInserter = builder.build(); WebClient webClient = webClientBuilder.apiVersionInserter(versionInserter).build(); webClient.get().uri("/path").apiVersion(1.2).retrieve().bodyToMono(String.class).block();