Move toEntity(List) from WebClient.ResponseSpec to ClientResponse
This commit moves `toEntity(Class<T>)` and `toEntityList(Class<T>)`
from WebClient.ResponseSpec to ClientResponse. The main reason for doing
so is that the newly introduced `onStatus` method (see
2f9bd6e075) does not apply to these two
methods, and the result would be confusing. Also, `ClientResponse` and
`ResponseEntity` represent the same data: status code, headers, and a
body.
Issue: SPR-15724
This commit is contained in:
parent
51e02c2911
commit
c9a3b863c4
|
|
@ -27,6 +27,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
|
@ -82,6 +83,22 @@ public interface ClientResponse {
|
|||
*/
|
||||
<T> Flux<T> bodyToFlux(Class<? extends T> elementClass);
|
||||
|
||||
/**
|
||||
* Return this response as a delayed {@code ResponseEntity}.
|
||||
* @param bodyType the expected response body type
|
||||
* @param <T> response body type
|
||||
* @return {@code Mono} with the {@code ResponseEntity}
|
||||
*/
|
||||
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
|
||||
|
||||
/**
|
||||
* Return this response as a delayed list of {@code ResponseEntity}s.
|
||||
* @param elementType the expected response body list element type
|
||||
* @param <T> the type of elements in the list
|
||||
* @return {@code Mono} with the list of {@code ResponseEntity}s
|
||||
*/
|
||||
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
|
||||
|
||||
|
||||
/**
|
||||
* Represents the headers of the HTTP response.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
|
|
@ -103,6 +104,25 @@ class DefaultClientResponse implements ClientResponse {
|
|||
return body(BodyExtractors.toFlux(elementClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
|
||||
HttpHeaders headers = headers().asHttpHeaders();
|
||||
HttpStatus statusCode = statusCode();
|
||||
return bodyToMono(bodyType)
|
||||
.map(body -> new ResponseEntity<>(body, headers, statusCode))
|
||||
.switchIfEmpty(Mono.defer(
|
||||
() -> Mono.just(new ResponseEntity<>(headers, statusCode))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> responseType) {
|
||||
HttpHeaders headers = headers().asHttpHeaders();
|
||||
HttpStatus statusCode = statusCode();
|
||||
return bodyToFlux(responseType)
|
||||
.collectList()
|
||||
.map(body -> new ResponseEntity<>(body, headers, statusCode));
|
||||
}
|
||||
|
||||
|
||||
private class DefaultHeaders implements Headers {
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -443,28 +442,5 @@ class DefaultWebClient implements WebClient {
|
|||
.orElse(response.body(extractor));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
|
||||
return this.responseMono.flatMap(response -> {
|
||||
HttpHeaders headers = response.headers().asHttpHeaders();
|
||||
HttpStatus statusCode = response.statusCode();
|
||||
return response.bodyToMono(bodyType)
|
||||
.map(body -> new ResponseEntity<>(body, headers, statusCode))
|
||||
.switchIfEmpty(Mono.defer(
|
||||
() -> Mono.just(new ResponseEntity<>(headers, statusCode))));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> responseType) {
|
||||
return this.responseMono.flatMap(response ->
|
||||
response.bodyToFlux(responseType).collectList().map(body -> {
|
||||
HttpHeaders headers = response.headers().asHttpHeaders();
|
||||
return new ResponseEntity<>(body, headers, response.statusCode());
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
|
@ -573,26 +572,6 @@ public interface WebClient {
|
|||
*/
|
||||
<T> Flux<T> bodyToFlux(Class<T> elementType);
|
||||
|
||||
/**
|
||||
* Returns the response as a delayed {@code ResponseEntity}. Unlike
|
||||
* {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}, this method does not check
|
||||
* for a 4xx or 5xx status code before extracting the body.
|
||||
* @param bodyType the expected response body type
|
||||
* @param <T> response body type
|
||||
* @return {@code Mono} with the {@code ResponseEntity}
|
||||
*/
|
||||
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
|
||||
|
||||
/**
|
||||
* Returns the response as a delayed list of {@code ResponseEntity}s. Unlike
|
||||
* {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}, this method does not check
|
||||
* for a 4xx or 5xx status code before extracting the body.
|
||||
* @param elementType the expected response body list element type
|
||||
* @param <T> the type of elements in the list
|
||||
* @return {@code Mono} with the list of {@code ResponseEntity}s
|
||||
*/
|
||||
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.function.client
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
|
|
@ -35,3 +36,19 @@ inline fun <reified T : Any> ClientResponse.bodyToMono(): Mono<T> = bodyToMono(T
|
|||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> ClientResponse.bodyToFlux(): Flux<T> = bodyToFlux(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [ClientResponse.toEntity] providing a `toEntity<Foo>()` variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> ClientResponse.toEntity(): Mono<ResponseEntity<T>> = toEntity(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [ClientResponse.toEntityList] providing a `bodyToEntityList<Foo>()` variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> ClientResponse.toEntityList(): Mono<ResponseEntity<List<T>>> = toEntityList(T::class.java)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.web.reactive.function.client
|
||||
|
||||
import org.reactivestreams.Publisher
|
||||
import org.springframework.http.ResponseEntity
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
|
|
@ -49,19 +48,3 @@ inline fun <reified T : Any> WebClient.ResponseSpec.bodyToMono(): Mono<T> = body
|
|||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> WebClient.ResponseSpec.bodyToFlux(): Flux<T> = bodyToFlux(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [WebClient.ResponseSpec.toEntity] providing a `bodyToEntity<Foo>()` variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> WebClient.ResponseSpec.toEntity(): Mono<ResponseEntity<T>> = toEntity(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [WebClient.ResponseSpec.toEntityList] providing a `bodyToEntityList<Foo>()` variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> WebClient.ResponseSpec.toEntityList(): Mono<ResponseEntity<List<T>>> = toEntityList(T::class.java)
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ public class WebClientIntegrationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void jsonStringRetrieveEntity() throws Exception {
|
||||
public void jsonStringExchangeEntity() throws Exception {
|
||||
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody(content));
|
||||
|
|
@ -166,8 +166,8 @@ public class WebClientIntegrationTests {
|
|||
Mono<ResponseEntity<String>> result = this.webClient.get()
|
||||
.uri("/json")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.toEntity(String.class);
|
||||
.exchange()
|
||||
.flatMap(response -> response.toEntity(String.class));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(entity -> {
|
||||
|
|
@ -186,15 +186,15 @@ public class WebClientIntegrationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void jsonStringRetrieveEntityList() throws Exception {
|
||||
public void jsonStringExchangeEntityList() throws Exception {
|
||||
String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]";
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json").setBody(content));
|
||||
|
||||
Mono<ResponseEntity<List<Pojo>>> result = this.webClient.get()
|
||||
.uri("/json")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.toEntityList(Pojo.class);
|
||||
.exchange()
|
||||
.flatMap(response -> response.toEntityList(Pojo.class));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(entity -> {
|
||||
|
|
@ -412,14 +412,14 @@ public class WebClientIntegrationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void retrieveToEntityNotFound() throws Exception {
|
||||
public void exchangeToEntityNotFound() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(404)
|
||||
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
|
||||
|
||||
Mono<ResponseEntity<String>> result = this.webClient.get()
|
||||
.uri("/greeting?name=Spring")
|
||||
.retrieve()
|
||||
.toEntity(String.class);
|
||||
.exchange()
|
||||
.flatMap(response -> response.toEntity(String.class));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()))
|
||||
|
|
@ -521,21 +521,6 @@ public class WebClientIntegrationTests {
|
|||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveNoContent() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Length", "0"));
|
||||
|
||||
Mono<ResponseEntity<Void>> result = this.webClient.get()
|
||||
.uri("/noContent")
|
||||
.retrieve()
|
||||
.toEntity(Void.class);
|
||||
|
||||
StepVerifier.create(result).assertNext(r -> {
|
||||
assertFalse(r.hasBody());
|
||||
assertTrue(r.getStatusCode().is2xxSuccessful());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class MyException extends RuntimeException {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.*
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
|
||||
/**
|
||||
|
|
@ -46,5 +47,17 @@ class ClientResponseExtensionsTests {
|
|||
verify(response, times(1)).bodyToFlux(Foo::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEntity with reified type parameters`() {
|
||||
response.toEntity<Foo>()
|
||||
verify(response, times(1)).toEntity(Foo::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResponseSpec#toEntityList with reified type parameters`() {
|
||||
response.toEntityList<Foo>()
|
||||
verify(response, times(1)).toEntityList(Foo::class.java)
|
||||
}
|
||||
|
||||
class Foo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.*
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import org.reactivestreams.Publisher
|
||||
|
||||
|
|
@ -59,17 +60,5 @@ class WebClientExtensionsTests {
|
|||
verify(responseSpec, times(1)).bodyToFlux(Foo::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResponseSpec#toEntity with reified type parameters`() {
|
||||
responseSpec.toEntity<Foo>()
|
||||
verify(responseSpec, times(1)).toEntity(Foo::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResponseSpec#toEntityList with reified type parameters`() {
|
||||
responseSpec.toEntityList<Foo>()
|
||||
verify(responseSpec, times(1)).toEntityList(Foo::class.java)
|
||||
}
|
||||
|
||||
class Foo
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue