Introduce hints in RestClient API

This commit introduces hints in RestClient API for
SmartHttpMessageConverters supporting them.

Closes gh-34924
This commit is contained in:
Sébastien Deleuze
2025-06-06 13:10:12 +02:00
parent 72601b6948
commit 977792009d
5 changed files with 141 additions and 21 deletions
@@ -214,7 +214,8 @@ final class DefaultRestClient implements RestClient {
@SuppressWarnings({"rawtypes", "unchecked"})
private <T> @Nullable T readWithMessageConverters(
ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class<T> bodyClass) {
ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class<T> bodyClass,
@Nullable Map<String, Object> hints) {
MediaType contentType = getContentType(clientResponse);
@@ -241,7 +242,7 @@ final class DefaultRestClient implements RestClient {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) smartMessageConverter.read(resolvableType, responseWrapper, null);
return (T) smartMessageConverter.read(resolvableType, responseWrapper, hints);
}
}
else if (messageConverter.canRead(bodyClass, contentType)) {
@@ -308,6 +309,8 @@ final class DefaultRestClient implements RestClient {
private @Nullable Consumer<ClientHttpRequest> httpRequestConsumer;
private @Nullable Map<String, Object> hints;
public DefaultRequestBodyUriSpec(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
}
@@ -478,6 +481,21 @@ final class DefaultRestClient implements RestClient {
return this;
}
@Override
public DefaultRequestBodyUriSpec hint(String key, Object value) {
getHints().put(key, value);
return this;
}
private Map<String, Object> getHints() {
Map<String, Object> hints = this.hints;
if (hints == null) {
hints = new ConcurrentHashMap<>(1);
this.hints = hints;
}
return hints;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void writeWithMessageConverters(Object body, Type bodyType, ClientHttpRequest clientRequest)
throws IOException {
@@ -497,7 +515,7 @@ final class DefaultRestClient implements RestClient {
ResolvableType resolvableType = ResolvableType.forType(bodyType);
if (smartMessageConverter.canWrite(resolvableType, bodyClass, contentType)) {
logBody(body, contentType, smartMessageConverter);
smartMessageConverter.write(body, resolvableType, contentType, clientRequest, null);
smartMessageConverter.write(body, resolvableType, contentType, clientRequest, this.hints);
return;
}
}
@@ -581,7 +599,7 @@ final class DefaultRestClient implements RestClient {
}
clientResponse = clientRequest.execute();
observationContext.setResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, this.hints);
return exchangeFunction.exchange(clientRequest, convertibleWrapper);
}
catch (IOException ex) {
@@ -745,6 +763,8 @@ final class DefaultRestClient implements RestClient {
private final int defaultStatusHandlerCount;
private @Nullable Map<String, Object> hints;
DefaultResponseSpec(RequestHeadersSpec<?> requestHeadersSpec) {
this.requestHeadersSpec = requestHeadersSpec;
this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers);
@@ -777,14 +797,14 @@ final class DefaultRestClient implements RestClient {
@Override
public <T> @Nullable T body(Class<T> bodyType) {
return executeAndExtract((request, response) -> readBody(request, response, bodyType, bodyType));
return executeAndExtract((request, response) -> readBody(request, response, bodyType, bodyType, this.hints));
}
@Override
public <T> @Nullable T body(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type);
return executeAndExtract((request, response) -> readBody(request, response, type, bodyClass));
return executeAndExtract((request, response) -> readBody(request, response, type, bodyClass, this.hints));
}
@Override
@@ -801,7 +821,7 @@ final class DefaultRestClient implements RestClient {
private <T> ResponseEntity<T> toEntityInternal(Type bodyType, Class<T> bodyClass) {
ResponseEntity<T> entity = executeAndExtract((request, response) -> {
T body = readBody(request, response, bodyType, bodyClass);
T body = readBody(request, response, bodyType, bodyClass, this.hints);
try {
return ResponseEntity.status(response.getStatusCode())
.headers(response.getHeaders())
@@ -838,13 +858,28 @@ final class DefaultRestClient implements RestClient {
return entity;
}
@Override
public ResponseSpec hint(String key, Object value) {
getHints().put(key, value);
return this;
}
private Map<String, Object> getHints() {
Map<String, Object> hints = this.hints;
if (hints == null) {
hints = new ConcurrentHashMap<>(1);
this.hints = hints;
}
return hints;
}
public <T> @Nullable T executeAndExtract(RequestHeadersSpec.ExchangeFunction<T> exchangeFunction) {
return this.requestHeadersSpec.exchange(exchangeFunction);
}
private <T> @Nullable T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass) {
private <T> @Nullable T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass, @Nullable Map<String, Object> hints) {
return DefaultRestClient.this.readWithMessageConverters(
response, () -> applyStatusHandlers(request, response), bodyType, bodyClass);
response, () -> applyStatusHandlers(request, response), bodyType, bodyClass, hints);
}
@@ -871,20 +906,23 @@ final class DefaultRestClient implements RestClient {
private final ClientHttpResponse delegate;
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate) {
private final @Nullable Map<String, Object> hints;
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, @Nullable Map<String, Object> hints) {
this.delegate = delegate;
this.hints = hints;
}
@Override
public <T> @Nullable T bodyTo(Class<T> bodyType) {
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType);
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.hints);
}
@Override
public <T> @Nullable T bodyTo(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.hints);
}
@Override
@@ -927,6 +927,17 @@ public interface RestClient {
* @return this builder
*/
RequestBodySpec body(StreamingHttpOutputMessage.Body body);
/**
* Set the hint with the given name to the given value for
* {@link org.springframework.http.converter.SmartHttpMessageConverter}s
* supporting them.
* @param key the key of the hint to add
* @param value the value of the hint to add
* @return this builder
* @since 7.0
*/
RequestBodySpec hint(String key, Object value);
}
@@ -1026,6 +1037,17 @@ public interface RestClient {
*/
ResponseEntity<Void> toBodilessEntity();
/**
* Set the hint with the given name to the given value for
* {@link org.springframework.http.converter.SmartHttpMessageConverter}s
* supporting them.
* @param key the key of the hint to add
* @param value the value of the hint to add
* @return this builder
* @since 7.0
*/
ResponseSpec hint(String key, Object value);
/**
* Used in {@link #onStatus(Predicate, ErrorHandler)}.