Skip to content

Commit e5d4d7c

Browse files
committed
Refactor DefaultApiVersionInserter
1 parent 22e7f24 commit e5d4d7c

File tree

6 files changed

+179
-121
lines changed

6 files changed

+179
-121
lines changed

spring-web/src/main/java/org/springframework/web/client/ApiVersionInserter.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.net.URI;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.http.HttpHeaders;
2224

2325
/**
@@ -49,4 +51,67 @@ default URI insertVersion(Object version, URI uri) {
4951
default void insertVersion(Object version, HttpHeaders headers) {
5052
}
5153

54+
55+
/**
56+
* Create a builder for an inserter that sets a header.
57+
* @param header the name of a header to hold the version
58+
*/
59+
static Builder fromHeader(@Nullable String header) {
60+
return new DefaultApiVersionInserterBuilder(header, null, null);
61+
}
62+
63+
/**
64+
* Create a builder for an inserter that sets a query parameter.
65+
* @param queryParam the name of a query parameter to hold the version
66+
*/
67+
static Builder fromQueryParam(@Nullable String queryParam) {
68+
return new DefaultApiVersionInserterBuilder(null, queryParam, null);
69+
}
70+
71+
/**
72+
* Create a builder for an inserter that inserts a path segment.
73+
* @param pathSegmentIndex the index of the path segment to hold the version
74+
*/
75+
static Builder fromPathSegment(@Nullable Integer pathSegmentIndex) {
76+
return new DefaultApiVersionInserterBuilder(null, null, pathSegmentIndex);
77+
}
78+
79+
80+
/**
81+
* Builder for {@link ApiVersionInserter}.
82+
*/
83+
interface Builder {
84+
85+
/**
86+
* Configure the inserter to set a header.
87+
* @param header the name of the header to hold the version
88+
*/
89+
Builder fromHeader(@Nullable String header);
90+
91+
/**
92+
* Configure the inserter to set a query parameter.
93+
* @param queryParam the name of the query parameter to hold the version
94+
*/
95+
Builder fromQueryParam(@Nullable String queryParam);
96+
97+
/**
98+
* Configure the inserter to insert a path segment.
99+
* @param pathSegmentIndex the index of the path segment to hold the version
100+
*/
101+
Builder fromPathSegment(@Nullable Integer pathSegmentIndex);
102+
103+
/**
104+
* Format the version Object into a String using the given {@link ApiVersionFormatter}.
105+
* <p>By default, the version is formatted with {@link Object#toString()}.
106+
* @param versionFormatter the formatter to use
107+
*/
108+
Builder withVersionFormatter(ApiVersionFormatter versionFormatter);
109+
110+
/**
111+
* Build the {@link ApiVersionInserter} instance.
112+
*/
113+
ApiVersionInserter build();
114+
115+
}
116+
52117
}

spring-web/src/main/java/org/springframework/web/client/DefaultApiVersionInserter.java

Lines changed: 4 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,13 @@
2727
import org.springframework.web.util.UriComponentsBuilder;
2828

2929
/**
30-
* Default implementation of {@link ApiVersionInserter} to insert the version
31-
* into a request header, query parameter, or the URL path.
32-
*
33-
* <p>Use {@link #builder()} to create an instance.
30+
* Default implementation of {@link ApiVersionInserter}.
3431
*
3532
* @author Rossen Stoyanchev
3633
* @since 7.0
34+
* @see DefaultApiVersionInserterBuilder
3735
*/
38-
public final class DefaultApiVersionInserter implements ApiVersionInserter {
36+
final class DefaultApiVersionInserter implements ApiVersionInserter {
3937

4038
private final @Nullable String header;
4139

@@ -46,7 +44,7 @@ public final class DefaultApiVersionInserter implements ApiVersionInserter {
4644
private final ApiVersionFormatter versionFormatter;
4745

4846

49-
private DefaultApiVersionInserter(
47+
DefaultApiVersionInserter(
5048
@Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex,
5149
@Nullable ApiVersionFormatter formatter) {
5250

@@ -92,102 +90,4 @@ public void insertVersion(Object version, HttpHeaders headers) {
9290
}
9391
}
9492

95-
96-
/**
97-
* Create a builder for an inserter that sets a header.
98-
* @param header the name of a header to hold the version
99-
*/
100-
public static Builder fromHeader(@Nullable String header) {
101-
return new Builder(header, null, null);
102-
}
103-
104-
/**
105-
* Create a builder for an inserter that sets a query parameter.
106-
* @param queryParam the name of a query parameter to hold the version
107-
*/
108-
public static Builder fromQueryParam(@Nullable String queryParam) {
109-
return new Builder(null, queryParam, null);
110-
}
111-
112-
/**
113-
* Create a builder for an inserter that inserts a path segment.
114-
* @param pathSegmentIndex the index of the path segment to hold the version
115-
*/
116-
public static Builder fromPathSegment(@Nullable Integer pathSegmentIndex) {
117-
return new Builder(null, null, pathSegmentIndex);
118-
}
119-
120-
/**
121-
* Create a builder.
122-
*/
123-
public static Builder builder() {
124-
return new Builder(null, null, null);
125-
}
126-
127-
128-
/**
129-
* A builder for {@link DefaultApiVersionInserter}.
130-
*/
131-
public static final class Builder {
132-
133-
private @Nullable String header;
134-
135-
private @Nullable String queryParam;
136-
137-
private @Nullable Integer pathSegmentIndex;
138-
139-
private @Nullable ApiVersionFormatter versionFormatter;
140-
141-
private Builder(@Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex) {
142-
this.header = header;
143-
this.queryParam = queryParam;
144-
this.pathSegmentIndex = pathSegmentIndex;
145-
}
146-
147-
/**
148-
* Configure the inserter to set a header.
149-
* @param header the name of the header to hold the version
150-
*/
151-
public Builder fromHeader(@Nullable String header) {
152-
this.header = header;
153-
return this;
154-
}
155-
156-
/**
157-
* Configure the inserter to set a query parameter.
158-
* @param queryParam the name of the query parameter to hold the version
159-
*/
160-
public Builder fromQueryParam(@Nullable String queryParam) {
161-
this.queryParam = queryParam;
162-
return this;
163-
}
164-
165-
/**
166-
* Configure the inserter to insert a path segment.
167-
* @param pathSegmentIndex the index of the path segment to hold the version
168-
*/
169-
public Builder fromPathSegment(@Nullable Integer pathSegmentIndex) {
170-
this.pathSegmentIndex = pathSegmentIndex;
171-
return this;
172-
}
173-
174-
/**
175-
* Format the version Object into a String using the given {@link ApiVersionFormatter}.
176-
* <p>By default, the version is formatted with {@link Object#toString()}.
177-
* @param versionFormatter the formatter to use
178-
*/
179-
public Builder withVersionFormatter(ApiVersionFormatter versionFormatter) {
180-
this.versionFormatter = versionFormatter;
181-
return this;
182-
}
183-
184-
/**
185-
* Build the inserter.
186-
*/
187-
public ApiVersionInserter build() {
188-
return new DefaultApiVersionInserter(
189-
this.header, this.queryParam, this.pathSegmentIndex, this.versionFormatter);
190-
}
191-
}
192-
19393
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.client;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
/**
22+
* Default implementation of {@link ApiVersionInserter.Builder}.
23+
*
24+
* @author Rossen Stoyanchev
25+
* @since 7.0
26+
* @see ApiVersionInserter#fromHeader(String)
27+
* @see ApiVersionInserter#fromQueryParam(String)
28+
* @see ApiVersionInserter#fromPathSegment(Integer)
29+
*/
30+
final class DefaultApiVersionInserterBuilder implements ApiVersionInserter.Builder {
31+
32+
private @Nullable String header;
33+
34+
private @Nullable String queryParam;
35+
36+
private @Nullable Integer pathSegmentIndex;
37+
38+
private @Nullable ApiVersionFormatter versionFormatter;
39+
40+
41+
DefaultApiVersionInserterBuilder(
42+
@Nullable String header, @Nullable String queryParam, @Nullable Integer pathSegmentIndex) {
43+
44+
this.header = header;
45+
this.queryParam = queryParam;
46+
this.pathSegmentIndex = pathSegmentIndex;
47+
}
48+
49+
/**
50+
* Configure the inserter to set a header.
51+
* @param header the name of the header to hold the version
52+
*/
53+
public ApiVersionInserter.Builder fromHeader(@Nullable String header) {
54+
this.header = header;
55+
return this;
56+
}
57+
58+
/**
59+
* Configure the inserter to set a query parameter.
60+
* @param queryParam the name of the query parameter to hold the version
61+
*/
62+
public ApiVersionInserter.Builder fromQueryParam(@Nullable String queryParam) {
63+
this.queryParam = queryParam;
64+
return this;
65+
}
66+
67+
/**
68+
* Configure the inserter to insert a path segment.
69+
* @param pathSegmentIndex the index of the path segment to hold the version
70+
*/
71+
public ApiVersionInserter.Builder fromPathSegment(@Nullable Integer pathSegmentIndex) {
72+
this.pathSegmentIndex = pathSegmentIndex;
73+
return this;
74+
}
75+
76+
/**
77+
* Format the version Object into a String using the given {@link ApiVersionFormatter}.
78+
* <p>By default, the version is formatted with {@link Object#toString()}.
79+
* @param versionFormatter the formatter to use
80+
*/
81+
public ApiVersionInserter.Builder withVersionFormatter(ApiVersionFormatter versionFormatter) {
82+
this.versionFormatter = versionFormatter;
83+
return this;
84+
}
85+
86+
/**
87+
* Build the inserter.
88+
*/
89+
public ApiVersionInserter build() {
90+
return new DefaultApiVersionInserter(
91+
this.header, this.queryParam, this.pathSegmentIndex, this.versionFormatter);
92+
}
93+
94+
}

spring-web/src/test/java/org/springframework/web/client/RestClientVersionTests.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,45 +59,45 @@ void shutdown() throws IOException {
5959

6060
@Test
6161
void header() {
62-
performRequest(DefaultApiVersionInserter.fromHeader("X-API-Version"));
62+
performRequest(ApiVersionInserter.fromHeader("X-API-Version"));
6363
expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2"));
6464
}
6565

6666
@Test
6767
void queryParam() {
68-
performRequest(DefaultApiVersionInserter.fromQueryParam("api-version"));
68+
performRequest(ApiVersionInserter.fromQueryParam("api-version"));
6969
expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path?api-version=1.2"));
7070
}
7171

7272
@Test
7373
void pathSegmentIndexLessThanSize() {
74-
performRequest(DefaultApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v));
74+
performRequest(ApiVersionInserter.fromPathSegment(0).withVersionFormatter(v -> "v" + v));
7575
expectRequest(request -> assertThat(request.getPath()).isEqualTo("/v1.2/path"));
7676
}
7777

7878
@Test
7979
void pathSegmentIndexEqualToSize() {
80-
performRequest(DefaultApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v));
80+
performRequest(ApiVersionInserter.fromPathSegment(1).withVersionFormatter(v -> "v" + v));
8181
expectRequest(request -> assertThat(request.getPath()).isEqualTo("/path/v1.2"));
8282
}
8383

8484
@Test
8585
void pathSegmentIndexGreaterThanSize() {
8686
assertThatIllegalStateException()
87-
.isThrownBy(() -> performRequest(DefaultApiVersionInserter.fromPathSegment(2)))
87+
.isThrownBy(() -> performRequest(ApiVersionInserter.fromPathSegment(2)))
8888
.withMessage("Cannot insert version into '/path' at path segment index 2");
8989
}
9090

9191
@Test
9292
void defaultVersion() {
93-
ApiVersionInserter inserter = DefaultApiVersionInserter.fromHeader("X-API-Version").build();
93+
ApiVersionInserter inserter = ApiVersionInserter.fromHeader("X-API-Version").build();
9494
RestClient restClient = restClientBuilder.defaultApiVersion(1.2).apiVersionInserter(inserter).build();
9595
restClient.get().uri("/path").retrieve().body(String.class);
9696

9797
expectRequest(request -> assertThat(request.getHeader("X-API-Version")).isEqualTo("1.2"));
9898
}
9999

100-
private void performRequest(DefaultApiVersionInserter.Builder builder) {
100+
private void performRequest(ApiVersionInserter.Builder builder) {
101101
ApiVersionInserter versionInserter = builder.build();
102102
RestClient restClient = restClientBuilder.apiVersionInserter(versionInserter).build();
103103

spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
import org.springframework.web.bind.annotation.RequestHeader;
5050
import org.springframework.web.bind.annotation.RequestParam;
5151
import org.springframework.web.bind.annotation.RequestPart;
52-
import org.springframework.web.client.DefaultApiVersionInserter;
52+
import org.springframework.web.client.ApiVersionInserter;
5353
import org.springframework.web.client.RestClient;
5454
import org.springframework.web.client.RestTemplate;
5555
import org.springframework.web.multipart.MultipartFile;
@@ -274,7 +274,7 @@ void getWithIgnoredUriBuilderFactory(MockWebServer server, Service service) thro
274274
void apiVersion() throws Exception {
275275
RestClient restClient = RestClient.builder()
276276
.baseUrl(anotherServer.url("/").toString())
277-
.apiVersionInserter(DefaultApiVersionInserter.fromHeader("X-API-Version").build())
277+
.apiVersionInserter(ApiVersionInserter.fromHeader("X-API-Version").build())
278278
.build();
279279

280280
RestClientAdapter adapter = RestClientAdapter.create(restClient);

0 commit comments

Comments
 (0)