Add body methods with Object parameter to WebFlux
The commit deprecates syncBody(Object) in favor of body(Object) which has the same behavior in ServerResponse, WebClient and WebTestClient. It also adds body(Object, Class) and body(Object, ParameterizedTypeReference) methods in order to support any reactive type that can be adapted to a Publisher via ReactiveAdapterRegistry. Related BodyInserters#fromProducer methods are provided as well. Shadowed Kotlin body<T>() extensions are deprecated in favor of bodyWithType<T>() ones, including dedicated Publisher<T> and Flow<T> variants. Coroutines extensions are adapted as well, and body(Object) can now be used with suspending functions. Closes gh-23212
This commit is contained in:
parent
0fbc9bf461
commit
2b4d6ce354
|
@ -53,6 +53,8 @@ dependencies {
|
||||||
optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
|
optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
|
||||||
optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
|
optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
|
||||||
optional("io.projectreactor:reactor-test")
|
optional("io.projectreactor:reactor-test")
|
||||||
|
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
|
||||||
|
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
|
||||||
testCompile(project(":spring-context-support"))
|
testCompile(project(":spring-context-support"))
|
||||||
testCompile(project(":spring-oxm"))
|
testCompile(project(":spring-oxm"))
|
||||||
testCompile("javax.annotation:javax.annotation-api:1.3.2")
|
testCompile("javax.annotation:javax.annotation-api:1.3.2")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -261,8 +261,20 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
|
public RequestHeadersSpec<?> body(Object body) {
|
||||||
this.bodySpec.body(inserter);
|
this.bodySpec.body(body);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestHeadersSpec<?> body(Object producer, Class<?> elementClass) {
|
||||||
|
this.bodySpec.body(producer, elementClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementType) {
|
||||||
|
this.bodySpec.body(producer, elementType);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,11 +285,23 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RequestHeadersSpec<?> syncBody(Object body) {
|
public <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, ParameterizedTypeReference<T> elementType) {
|
||||||
this.bodySpec.syncBody(body);
|
this.bodySpec.body(publisher, elementType);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
|
||||||
|
this.bodySpec.body(inserter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public RequestHeadersSpec<?> syncBody(Object body) {
|
||||||
|
return body(body);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseSpec exchange() {
|
public ResponseSpec exchange() {
|
||||||
ClientResponse clientResponse = this.bodySpec.exchange().block(getTimeout());
|
ClientResponse clientResponse = this.bodySpec.exchange().block(getTimeout());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,6 +30,7 @@ import org.reactivestreams.Publisher;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.format.FormatterRegistry;
|
import org.springframework.format.FormatterRegistry;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
@ -625,26 +626,7 @@ public interface WebTestClient {
|
||||||
RequestBodySpec contentType(MediaType contentType);
|
RequestBodySpec contentType(MediaType contentType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the body of the request to the given {@code BodyInserter}.
|
* Set the body of the request to the given {@code Object} and perform the request.
|
||||||
* @param inserter the inserter
|
|
||||||
* @return spec for decoding the response
|
|
||||||
* @see org.springframework.web.reactive.function.BodyInserters
|
|
||||||
*/
|
|
||||||
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the body of the request to the given asynchronous {@code Publisher}.
|
|
||||||
* @param publisher the request body data
|
|
||||||
* @param elementClass the class of elements contained in the publisher
|
|
||||||
* @param <T> the type of the elements contained in the publisher
|
|
||||||
* @param <S> the type of the {@code Publisher}
|
|
||||||
* @return spec for decoding the response
|
|
||||||
*/
|
|
||||||
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the body of the request to the given synchronous {@code Object} and
|
|
||||||
* perform the request.
|
|
||||||
* <p>This method is a convenient shortcut for:
|
* <p>This method is a convenient shortcut for:
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* .body(BodyInserters.fromObject(object))
|
* .body(BodyInserters.fromObject(object))
|
||||||
|
@ -657,8 +639,83 @@ public interface WebTestClient {
|
||||||
* part with body and headers. The {@code MultiValueMap} can be built
|
* part with body and headers. The {@code MultiValueMap} can be built
|
||||||
* conveniently using
|
* conveniently using
|
||||||
* @param body the {@code Object} to write to the request
|
* @param body the {@code Object} to write to the request
|
||||||
* @return a {@code Mono} with the response
|
* @return spec for decoding the response
|
||||||
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
|
RequestHeadersSpec<?> body(Object body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given producer.
|
||||||
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param elementClass the class of elements contained in the producer
|
||||||
|
* @return spec for decoding the response
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(Object producer, Class<?> elementClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given producer.
|
||||||
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param elementType the type reference of elements contained in the producer
|
||||||
|
* @return spec for decoding the response
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given asynchronous {@code Publisher}.
|
||||||
|
* @param publisher the request body data
|
||||||
|
* @param elementClass the class of elements contained in the publisher
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @param <S> the type of the {@code Publisher}
|
||||||
|
* @return spec for decoding the response
|
||||||
|
*/
|
||||||
|
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given asynchronous {@code Publisher}.
|
||||||
|
* @param publisher the request body data
|
||||||
|
* @param elementType the type reference of elements contained in the publisher
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @param <S> the type of the {@code Publisher}
|
||||||
|
* @return spec for decoding the response
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, ParameterizedTypeReference<T> elementType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given {@code BodyInserter}.
|
||||||
|
* @param inserter the inserter
|
||||||
|
* @return spec for decoding the response
|
||||||
|
* @see org.springframework.web.reactive.function.BodyInserters
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request to the given {@code Object} and perform the request.
|
||||||
|
* <p>This method is a convenient shortcut for:
|
||||||
|
* <pre class="code">
|
||||||
|
* .body(BodyInserters.fromObject(object))
|
||||||
|
* </pre>
|
||||||
|
* <p>The body can be a
|
||||||
|
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create
|
||||||
|
* a multipart request. The values in the {@code MultiValueMap} can be
|
||||||
|
* any Object representing the body of the part, or an
|
||||||
|
* {@link org.springframework.http.HttpEntity HttpEntity} representing a
|
||||||
|
* part with body and headers. The {@code MultiValueMap} can be built
|
||||||
|
* conveniently using
|
||||||
|
* @param body the {@code Object} to write to the request
|
||||||
|
* @return spec for decoding the response
|
||||||
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used.
|
||||||
|
* @deprecated as of Spring Framework 5.2 in favor of {@link #body(Object)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
RequestHeadersSpec<?> syncBody(Object body);
|
RequestHeadersSpec<?> syncBody(Object body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package org.springframework.test.web.reactive.server
|
package org.springframework.test.web.reactive.server
|
||||||
|
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import org.springframework.test.util.AssertionErrors.assertEquals
|
import org.springframework.test.util.AssertionErrors.assertEquals
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient.*
|
import org.springframework.test.web.reactive.server.WebTestClient.*
|
||||||
|
|
||||||
|
@ -27,8 +30,49 @@ import org.springframework.test.web.reactive.server.WebTestClient.*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use 'bodyWithType' instead.", replaceWith = ReplaceWith("bodyWithType(publisher)"))
|
||||||
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||||
inline fun <reified T : Any, S : Publisher<T>> RequestBodySpec.body(publisher: S): RequestHeadersSpec<*>
|
inline fun <reified T : Any, S : Publisher<T>> RequestBodySpec.body(publisher: S): RequestHeadersSpec<*>
|
||||||
= body(publisher, T::class.java)
|
= body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [RequestBodySpec.body] providing a `bodyWithType<T>(Any)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* [Publisher] or another producer adaptable to a
|
||||||
|
* [Publisher] via [org.springframework.core.ReactiveAdapterRegistry]
|
||||||
|
* @param <T> the type of the elements contained in the producer
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(producer: Any): RequestHeadersSpec<*>
|
||||||
|
= body(producer, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [RequestBodySpec.body] providing a `bodyWithType(Publisher<T>)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param publisher the [Publisher] to write to the request
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(publisher: Publisher<T>): RequestHeadersSpec<*> =
|
||||||
|
body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [RequestBodySpec.body] providing a `bodyWithType(Flow<T>)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param flow the [Flow] to write to the request
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@FlowPreview
|
||||||
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(flow: Flow<T>): RequestHeadersSpec<*> =
|
||||||
|
body(flow, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension for [ResponseSpec.expectBody] providing an `expectBody<Foo>()` variant and
|
* Extension for [ResponseSpec.expectBody] providing an `expectBody<Foo>()` variant and
|
||||||
|
@ -44,13 +88,11 @@ inline fun <reified B : Any> ResponseSpec.expectBody(): KotlinBodySpec<B> =
|
||||||
object : KotlinBodySpec<B> {
|
object : KotlinBodySpec<B> {
|
||||||
|
|
||||||
override fun isEqualTo(expected: B): KotlinBodySpec<B> = it
|
override fun isEqualTo(expected: B): KotlinBodySpec<B> = it
|
||||||
.assertWithDiagnostics({ assertEquals("Response body", expected, it.responseBody) })
|
.assertWithDiagnostics { assertEquals("Response body", expected, it.responseBody) }
|
||||||
.let { this }
|
.let { this }
|
||||||
|
|
||||||
override fun consumeWith(consumer: (EntityExchangeResult<B>) -> Unit): KotlinBodySpec<B> =
|
override fun consumeWith(consumer: (EntityExchangeResult<B>) -> Unit): KotlinBodySpec<B> =
|
||||||
it
|
it.assertWithDiagnostics { consumer.invoke(it) }.let { this }
|
||||||
.assertWithDiagnostics({ consumer.invoke(it) })
|
|
||||||
.let { this }
|
|
||||||
|
|
||||||
override fun returnResult(): EntityExchangeResult<B> = it
|
override fun returnResult(): EntityExchangeResult<B> = it
|
||||||
}
|
}
|
||||||
|
@ -88,7 +130,7 @@ interface KotlinBodySpec<B> {
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
inline fun <reified E : Any> ResponseSpec.expectBodyList(): ListBodySpec<E> =
|
inline fun <reified E : Any> ResponseSpec.expectBodyList(): ListBodySpec<E> =
|
||||||
expectBodyList(E::class.java)
|
expectBodyList(object : ParameterizedTypeReference<E>() {})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension for [ResponseSpec.returnResult] providing a `returnResult<Foo>()` variant.
|
* Extension for [ResponseSpec.returnResult] providing a `returnResult<Foo>()` variant.
|
||||||
|
@ -98,4 +140,4 @@ inline fun <reified E : Any> ResponseSpec.expectBodyList(): ListBodySpec<E> =
|
||||||
*/
|
*/
|
||||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||||
inline fun <reified T : Any> ResponseSpec.returnResult(): FluxExchangeResult<T> =
|
inline fun <reified T : Any> ResponseSpec.returnResult(): FluxExchangeResult<T> =
|
||||||
returnResult(T::class.java)
|
returnResult(object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class ApplicationContextSpecTests {
|
||||||
.GET("/sessionClassName", request ->
|
.GET("/sessionClassName", request ->
|
||||||
request.session().flatMap(session -> {
|
request.session().flatMap(session -> {
|
||||||
String className = session.getClass().getSimpleName();
|
String className = session.getClass().getSimpleName();
|
||||||
return ServerResponse.ok().syncBody(className);
|
return ServerResponse.ok().body(className);
|
||||||
}))
|
}))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class ErrorTests {
|
||||||
EntityExchangeResult<Void> result = this.client.post()
|
EntityExchangeResult<Void> result = this.client.post()
|
||||||
.uri("/post")
|
.uri("/post")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.syncBody(new Person("Dan"))
|
.body(new Person("Dan"))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isBadRequest()
|
.expectStatus().isBadRequest()
|
||||||
.expectBody().isEmpty();
|
.expectBody().isEmpty();
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class JsonContentTests {
|
||||||
public void postJsonContent() {
|
public void postJsonContent() {
|
||||||
this.client.post().uri("/persons")
|
this.client.post().uri("/persons")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.syncBody("{\"name\":\"John\"}")
|
.body("{\"name\":\"John\"}")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isCreated()
|
.expectStatus().isCreated()
|
||||||
.expectBody().isEmpty();
|
.expectBody().isEmpty();
|
||||||
|
|
|
@ -145,7 +145,7 @@ public class ResponseEntityTests {
|
||||||
@Test
|
@Test
|
||||||
public void postEntity() {
|
public void postEntity() {
|
||||||
this.client.post()
|
this.client.post()
|
||||||
.syncBody(new Person("John"))
|
.body(new Person("John"))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isCreated()
|
.expectStatus().isCreated()
|
||||||
.expectHeader().valueEquals("location", "/persons/John")
|
.expectHeader().valueEquals("location", "/persons/John")
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class XmlContentTests {
|
||||||
|
|
||||||
this.client.post().uri("/persons")
|
this.client.post().uri("/persons")
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
.contentType(MediaType.APPLICATION_XML)
|
||||||
.syncBody(content)
|
.body(content)
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isCreated()
|
.expectStatus().isCreated()
|
||||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/persons/John")
|
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/persons/John")
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class HttpServerTests {
|
||||||
@Before
|
@Before
|
||||||
public void start() throws Exception {
|
public void start() throws Exception {
|
||||||
HttpHandler httpHandler = RouterFunctions.toHttpHandler(
|
HttpHandler httpHandler = RouterFunctions.toHttpHandler(
|
||||||
route(GET("/test"), request -> ServerResponse.ok().syncBody("It works!")));
|
route(GET("/test"), request -> ServerResponse.ok().body("It works!")));
|
||||||
|
|
||||||
this.server = new ReactorHttpServer();
|
this.server = new ReactorHttpServer();
|
||||||
this.server.setHandler(httpHandler);
|
this.server.setHandler(httpHandler);
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class RouterFunctionTests {
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
RouterFunction<?> route = route(GET("/test"), request ->
|
RouterFunction<?> route = route(GET("/test"), request ->
|
||||||
ServerResponse.ok().syncBody("It works!"));
|
ServerResponse.ok().body("It works!"));
|
||||||
|
|
||||||
this.testClient = WebTestClient.bindToRouterFunction(route).build();
|
this.testClient = WebTestClient.bindToRouterFunction(route).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,14 @@ package org.springframework.test.web.reactive.server
|
||||||
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import org.springframework.web.reactive.function.server.router
|
import org.springframework.web.reactive.function.server.router
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock object based tests for [WebTestClient] Kotlin extensions
|
* Mock object based tests for [WebTestClient] Kotlin extensions
|
||||||
|
@ -30,16 +34,31 @@ import org.springframework.web.reactive.function.server.router
|
||||||
*/
|
*/
|
||||||
class WebTestClientExtensionsTests {
|
class WebTestClientExtensionsTests {
|
||||||
|
|
||||||
val requestBodySpec = mockk<WebTestClient.RequestBodySpec>(relaxed = true)
|
private val requestBodySpec = mockk<WebTestClient.RequestBodySpec>(relaxed = true)
|
||||||
|
|
||||||
val responseSpec = mockk<WebTestClient.ResponseSpec>(relaxed = true)
|
private val responseSpec = mockk<WebTestClient.ResponseSpec>(relaxed = true)
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `RequestBodySpec#body with Publisher and reified type parameters`() {
|
fun `RequestBodySpec#bodyWithType with Publisher and reified type parameters`() {
|
||||||
val body = mockk<Publisher<Foo>>()
|
val body = mockk<Publisher<Foo>>()
|
||||||
requestBodySpec.body(body)
|
requestBodySpec.bodyWithType(body)
|
||||||
verify { requestBodySpec.body(body, Foo::class.java) }
|
verify { requestBodySpec.body(body, object : ParameterizedTypeReference<Foo>() {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@FlowPreview
|
||||||
|
fun `RequestBodySpec#bodyWithType with Flow and reified type parameters`() {
|
||||||
|
val body = mockk<Flow<Foo>>()
|
||||||
|
requestBodySpec.bodyWithType(body)
|
||||||
|
verify { requestBodySpec.body(body, object : ParameterizedTypeReference<Foo>() {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RequestBodySpec#bodyWithType with CompletableFuture and reified type parameters`() {
|
||||||
|
val body = mockk<CompletableFuture<Foo>>()
|
||||||
|
requestBodySpec.bodyWithType<Foo>(body)
|
||||||
|
verify { requestBodySpec.body(body, object : ParameterizedTypeReference<Foo>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -51,7 +70,7 @@ class WebTestClientExtensionsTests {
|
||||||
@Test
|
@Test
|
||||||
fun `KotlinBodySpec#isEqualTo`() {
|
fun `KotlinBodySpec#isEqualTo`() {
|
||||||
WebTestClient
|
WebTestClient
|
||||||
.bindToRouterFunction( router { GET("/") { ok().syncBody("foo") } } )
|
.bindToRouterFunction( router { GET("/") { ok().body("foo") } } )
|
||||||
.build()
|
.build()
|
||||||
.get().uri("/").exchange().expectBody<String>().isEqualTo("foo")
|
.get().uri("/").exchange().expectBody<String>().isEqualTo("foo")
|
||||||
}
|
}
|
||||||
|
@ -59,7 +78,7 @@ class WebTestClientExtensionsTests {
|
||||||
@Test
|
@Test
|
||||||
fun `KotlinBodySpec#consumeWith`() {
|
fun `KotlinBodySpec#consumeWith`() {
|
||||||
WebTestClient
|
WebTestClient
|
||||||
.bindToRouterFunction( router { GET("/") { ok().syncBody("foo") } } )
|
.bindToRouterFunction( router { GET("/") { ok().body("foo") } } )
|
||||||
.build()
|
.build()
|
||||||
.get().uri("/").exchange().expectBody<String>().consumeWith { assertEquals("foo", it.responseBody) }
|
.get().uri("/").exchange().expectBody<String>().consumeWith { assertEquals("foo", it.responseBody) }
|
||||||
}
|
}
|
||||||
|
@ -67,7 +86,7 @@ class WebTestClientExtensionsTests {
|
||||||
@Test
|
@Test
|
||||||
fun `KotlinBodySpec#returnResult`() {
|
fun `KotlinBodySpec#returnResult`() {
|
||||||
WebTestClient
|
WebTestClient
|
||||||
.bindToRouterFunction( router { GET("/") { ok().syncBody("foo") } } )
|
.bindToRouterFunction( router { GET("/") { ok().body("foo") } } )
|
||||||
.build()
|
.build()
|
||||||
.get().uri("/").exchange().expectBody<String>().returnResult().apply { assertEquals("foo", responseBody) }
|
.get().uri("/").exchange().expectBody<String>().returnResult().apply { assertEquals("foo", responseBody) }
|
||||||
}
|
}
|
||||||
|
@ -75,13 +94,13 @@ class WebTestClientExtensionsTests {
|
||||||
@Test
|
@Test
|
||||||
fun `ResponseSpec#expectBodyList with reified type parameters`() {
|
fun `ResponseSpec#expectBodyList with reified type parameters`() {
|
||||||
responseSpec.expectBodyList<Foo>()
|
responseSpec.expectBodyList<Foo>()
|
||||||
verify { responseSpec.expectBodyList(Foo::class.java) }
|
verify { responseSpec.expectBodyList(object : ParameterizedTypeReference<Foo>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ResponseSpec#returnResult with reified type parameters`() {
|
fun `ResponseSpec#returnResult with reified type parameters`() {
|
||||||
responseSpec.returnResult<Foo>()
|
responseSpec.returnResult<Foo>()
|
||||||
verify { responseSpec.returnResult(Foo::class.java) }
|
verify { responseSpec.returnResult(object : ParameterizedTypeReference<Foo>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Foo
|
class Foo
|
||||||
|
|
|
@ -41,7 +41,7 @@ import org.springframework.util.MultiValueMap;
|
||||||
/**
|
/**
|
||||||
* Builder for the body of a multipart request, producing
|
* Builder for the body of a multipart request, producing
|
||||||
* {@code MultiValueMap<String, HttpEntity>}, which can be provided to the
|
* {@code MultiValueMap<String, HttpEntity>}, which can be provided to the
|
||||||
* {@code WebClient} through the {@code syncBody} method.
|
* {@code WebClient} through the {@code body} method.
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
|
@ -67,7 +67,7 @@ import org.springframework.util.MultiValueMap;
|
||||||
*
|
*
|
||||||
* Mono<Void> result = webClient.post()
|
* Mono<Void> result = webClient.post()
|
||||||
* .uri("...")
|
* .uri("...")
|
||||||
* .syncBody(multipartBody)
|
* .body(multipartBody)
|
||||||
* .retrieve()
|
* .retrieve()
|
||||||
* .bodyToMono(Void.class)
|
* .bodyToMono(Void.class)
|
||||||
* </pre>
|
* </pre>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,8 @@ import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ReactiveAdapter;
|
||||||
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
@ -44,6 +46,7 @@ import org.springframework.util.MultiValueMap;
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public abstract class BodyInserters {
|
public abstract class BodyInserters {
|
||||||
|
@ -61,6 +64,8 @@ public abstract class BodyInserters {
|
||||||
private static final BodyInserter<Void, ReactiveHttpOutputMessage> EMPTY_INSERTER =
|
private static final BodyInserter<Void, ReactiveHttpOutputMessage> EMPTY_INSERTER =
|
||||||
(response, context) -> response.setComplete();
|
(response, context) -> response.setComplete();
|
||||||
|
|
||||||
|
private static final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserter that does not write.
|
* Inserter that does not write.
|
||||||
|
@ -73,16 +78,68 @@ public abstract class BodyInserters {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserter to write the given object.
|
* Inserter to write the given object.
|
||||||
* <p>Alternatively, consider using the {@code syncBody(Object)} shortcuts on
|
* <p>Alternatively, consider using the {@code body(Object)} shortcuts on
|
||||||
* {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
|
* {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
|
||||||
* {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
|
* {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
|
||||||
* @param body the body to write to the response
|
* @param body the body to write to the response
|
||||||
* @param <T> the type of the body
|
* @param <T> the type of the body
|
||||||
* @return the inserter to write a single object
|
* @return the inserter to write a single object
|
||||||
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #fromPublisher(Publisher, Class)} or
|
||||||
|
* {@link #fromProducer(Object, Class)} should be used.
|
||||||
|
* @see #fromPublisher(Publisher, Class)
|
||||||
|
* @see #fromProducer(Object, Class)
|
||||||
*/
|
*/
|
||||||
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
|
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
|
||||||
|
Assert.notNull(body, "Body must not be null");
|
||||||
|
Assert.isNull(registry.getAdapter(body.getClass()), "'body' should be an object, for reactive types use a variant specifying a publisher/producer and its related element type");
|
||||||
return (message, context) ->
|
return (message, context) ->
|
||||||
writeWithMessageWriters(message, context, Mono.just(body), ResolvableType.forInstance(body));
|
writeWithMessageWriters(message, context, Mono.just(body), ResolvableType.forInstance(body), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserter to write the given producer of value(s) which must be a {@link Publisher}
|
||||||
|
* or another producer adaptable to a {@code Publisher} via
|
||||||
|
* {@link ReactiveAdapterRegistry}.
|
||||||
|
* <p>Alternatively, consider using the {@code body} shortcuts on
|
||||||
|
* {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
|
||||||
|
* {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
|
||||||
|
* @param <T> the type of the body
|
||||||
|
* @param producer the source of body value(s).
|
||||||
|
* @param elementClass the type of values to be produced
|
||||||
|
* @return the inserter to write a producer
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(T producer, Class<?> elementClass) {
|
||||||
|
Assert.notNull(producer, "'producer' must not be null");
|
||||||
|
Assert.notNull(elementClass, "'elementClass' must not be null");
|
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass());
|
||||||
|
Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry");
|
||||||
|
return (message, context) ->
|
||||||
|
writeWithMessageWriters(message, context, producer, ResolvableType.forClass(elementClass), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserter to write the given producer of value(s) which must be a {@link Publisher}
|
||||||
|
* or another producer adaptable to a {@code Publisher} via
|
||||||
|
* {@link ReactiveAdapterRegistry}.
|
||||||
|
* <p>Alternatively, consider using the {@code body} shortcuts on
|
||||||
|
* {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
|
||||||
|
* {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
|
||||||
|
* @param <T> the type of the body
|
||||||
|
* @param producer the source of body value(s).
|
||||||
|
* @param elementType the type of values to be produced
|
||||||
|
* @return the inserter to write a producer
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(T producer, ParameterizedTypeReference<?> elementType) {
|
||||||
|
Assert.notNull(producer, "'producer' must not be null");
|
||||||
|
Assert.notNull(elementType, "'elementType' must not be null");
|
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass());
|
||||||
|
Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry");
|
||||||
|
return (message, context) ->
|
||||||
|
writeWithMessageWriters(message, context, producer, ResolvableType.forType(elementType), adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +159,7 @@ public abstract class BodyInserters {
|
||||||
Assert.notNull(publisher, "Publisher must not be null");
|
Assert.notNull(publisher, "Publisher must not be null");
|
||||||
Assert.notNull(elementClass, "Element Class must not be null");
|
Assert.notNull(elementClass, "Element Class must not be null");
|
||||||
return (message, context) ->
|
return (message, context) ->
|
||||||
writeWithMessageWriters(message, context, publisher, ResolvableType.forClass(elementClass));
|
writeWithMessageWriters(message, context, publisher, ResolvableType.forClass(elementClass), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,7 +179,7 @@ public abstract class BodyInserters {
|
||||||
Assert.notNull(publisher, "Publisher must not be null");
|
Assert.notNull(publisher, "Publisher must not be null");
|
||||||
Assert.notNull(typeReference, "ParameterizedTypeReference must not be null");
|
Assert.notNull(typeReference, "ParameterizedTypeReference must not be null");
|
||||||
return (message, context) ->
|
return (message, context) ->
|
||||||
writeWithMessageWriters(message, context, publisher, ResolvableType.forType(typeReference.getType()));
|
writeWithMessageWriters(message, context, publisher, ResolvableType.forType(typeReference.getType()), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,8 +202,8 @@ public abstract class BodyInserters {
|
||||||
/**
|
/**
|
||||||
* Inserter to write the given {@code ServerSentEvent} publisher.
|
* Inserter to write the given {@code ServerSentEvent} publisher.
|
||||||
* <p>Alternatively, you can provide event data objects via
|
* <p>Alternatively, you can provide event data objects via
|
||||||
* {@link #fromPublisher(Publisher, Class)}, and set the "Content-Type" to
|
* {@link #fromPublisher(Publisher, Class)} or {@link #fromProducer(Object, Class)},
|
||||||
* {@link MediaType#TEXT_EVENT_STREAM text/event-stream}.
|
* and set the "Content-Type" to {@link MediaType#TEXT_EVENT_STREAM text/event-stream}.
|
||||||
* @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body
|
* @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body
|
||||||
* @param <T> the type of the data elements in the {@link ServerSentEvent}
|
* @param <T> the type of the data elements in the {@link ServerSentEvent}
|
||||||
* @return the inserter to write a {@code ServerSentEvent} publisher
|
* @return the inserter to write a {@code ServerSentEvent} publisher
|
||||||
|
@ -169,7 +226,7 @@ public abstract class BodyInserters {
|
||||||
* Return a {@link FormInserter} to write the given {@code MultiValueMap}
|
* Return a {@link FormInserter} to write the given {@code MultiValueMap}
|
||||||
* as URL-encoded form data. The returned inserter allows for additional
|
* as URL-encoded form data. The returned inserter allows for additional
|
||||||
* entries to be added via {@link FormInserter#with(String, Object)}.
|
* entries to be added via {@link FormInserter#with(String, Object)}.
|
||||||
* <p>Note that you can also use the {@code syncBody(Object)} method in the
|
* <p>Note that you can also use the {@code body(Object)} method in the
|
||||||
* request builders of both the {@code WebClient} and {@code WebTestClient}.
|
* request builders of both the {@code WebClient} and {@code WebTestClient}.
|
||||||
* In that case the setting of the request content type is also not required,
|
* In that case the setting of the request content type is also not required,
|
||||||
* just be sure the map contains String values only or otherwise it would be
|
* just be sure the map contains String values only or otherwise it would be
|
||||||
|
@ -201,7 +258,7 @@ public abstract class BodyInserters {
|
||||||
* Object or an {@link HttpEntity}.
|
* Object or an {@link HttpEntity}.
|
||||||
* <p>Note that you can also build the multipart data externally with
|
* <p>Note that you can also build the multipart data externally with
|
||||||
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
||||||
* {@code syncBody(Object)} shortcut method in {@code WebClient}.
|
* {@code body(Object)} shortcut method in {@code WebClient}.
|
||||||
* @param multipartData the form data to write to the output message
|
* @param multipartData the form data to write to the output message
|
||||||
* @return the inserter that allows adding more parts
|
* @return the inserter that allows adding more parts
|
||||||
* @see MultipartBodyBuilder
|
* @see MultipartBodyBuilder
|
||||||
|
@ -217,7 +274,7 @@ public abstract class BodyInserters {
|
||||||
* {@link HttpEntity}.
|
* {@link HttpEntity}.
|
||||||
* <p>Note that you can also build the multipart data externally with
|
* <p>Note that you can also build the multipart data externally with
|
||||||
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
||||||
* {@code syncBody(Object)} shortcut method in {@code WebClient}.
|
* {@code body(Object)} shortcut method in {@code WebClient}.
|
||||||
* @param name the part name
|
* @param name the part name
|
||||||
* @param value the part value, an Object or {@code HttpEntity}
|
* @param value the part value, an Object or {@code HttpEntity}
|
||||||
* @return the inserter that allows adding more parts
|
* @return the inserter that allows adding more parts
|
||||||
|
@ -233,7 +290,7 @@ public abstract class BodyInserters {
|
||||||
* as multipart data.
|
* as multipart data.
|
||||||
* <p>Note that you can also build the multipart data externally with
|
* <p>Note that you can also build the multipart data externally with
|
||||||
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
||||||
* {@code syncBody(Object)} shortcut method in {@code WebClient}.
|
* {@code body(Object)} shortcut method in {@code WebClient}.
|
||||||
* @param name the part name
|
* @param name the part name
|
||||||
* @param publisher the publisher that forms the part value
|
* @param publisher the publisher that forms the part value
|
||||||
* @param elementClass the class contained in the {@code publisher}
|
* @param elementClass the class contained in the {@code publisher}
|
||||||
|
@ -251,7 +308,7 @@ public abstract class BodyInserters {
|
||||||
* allows specifying generic type information.
|
* allows specifying generic type information.
|
||||||
* <p>Note that you can also build the multipart data externally with
|
* <p>Note that you can also build the multipart data externally with
|
||||||
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
* {@link MultipartBodyBuilder}, and pass the resulting map directly to the
|
||||||
* {@code syncBody(Object)} shortcut method in {@code WebClient}.
|
* {@code body(Object)} shortcut method in {@code WebClient}.
|
||||||
* @param name the part name
|
* @param name the part name
|
||||||
* @param publisher the publisher that forms the part value
|
* @param publisher the publisher that forms the part value
|
||||||
* @param typeReference the type contained in the {@code publisher}
|
* @param typeReference the type contained in the {@code publisher}
|
||||||
|
@ -278,15 +335,25 @@ public abstract class BodyInserters {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static <P extends Publisher<?>, M extends ReactiveHttpOutputMessage> Mono<Void> writeWithMessageWriters(
|
private static <M extends ReactiveHttpOutputMessage> Mono<Void> writeWithMessageWriters(
|
||||||
M outputMessage, BodyInserter.Context context, P body, ResolvableType bodyType) {
|
M outputMessage, BodyInserter.Context context, Object body, ResolvableType bodyType, @Nullable ReactiveAdapter adapter) {
|
||||||
|
|
||||||
|
Publisher<?> publisher;
|
||||||
|
if (body instanceof Publisher) {
|
||||||
|
publisher = (Publisher<?>) body;
|
||||||
|
}
|
||||||
|
else if (adapter != null) {
|
||||||
|
publisher = adapter.toPublisher(body);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
publisher = Mono.just(body);
|
||||||
|
}
|
||||||
MediaType mediaType = outputMessage.getHeaders().getContentType();
|
MediaType mediaType = outputMessage.getHeaders().getContentType();
|
||||||
return context.messageWriters().stream()
|
return context.messageWriters().stream()
|
||||||
.filter(messageWriter -> messageWriter.canWrite(bodyType, mediaType))
|
.filter(messageWriter -> messageWriter.canWrite(bodyType, mediaType))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(BodyInserters::cast)
|
.map(BodyInserters::cast)
|
||||||
.map(writer -> write(body, bodyType, mediaType, outputMessage, context, writer))
|
.map(writer -> write(publisher, bodyType, mediaType, outputMessage, context, writer))
|
||||||
.orElseGet(() -> Mono.error(unsupportedError(bodyType, context, mediaType)));
|
.orElseGet(() -> Mono.error(unsupportedError(bodyType, context, mediaType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.springframework.web.util.UriBuilderFactory;
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
class DefaultWebClient implements WebClient {
|
class DefaultWebClient implements WebClient {
|
||||||
|
@ -290,16 +291,27 @@ class DefaultWebClient implements WebClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
|
public RequestHeadersSpec<?> body(Object body) {
|
||||||
this.inserter = inserter;
|
this.inserter = BodyInserters.fromObject(body);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestHeadersSpec<?> body(Object producer, Class<?> elementClass) {
|
||||||
|
this.inserter = BodyInserters.fromProducer(producer, elementClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementType) {
|
||||||
|
this.inserter = BodyInserters.fromProducer(producer, elementType);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T, P extends Publisher<T>> RequestHeadersSpec<?> body(
|
public <T, P extends Publisher<T>> RequestHeadersSpec<?> body(
|
||||||
P publisher, ParameterizedTypeReference<T> typeReference) {
|
P publisher, ParameterizedTypeReference<T> elementType) {
|
||||||
|
this.inserter = BodyInserters.fromPublisher(publisher, elementType);
|
||||||
this.inserter = BodyInserters.fromPublisher(publisher, typeReference);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,13 +322,17 @@ class DefaultWebClient implements WebClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RequestHeadersSpec<?> syncBody(Object body) {
|
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
|
||||||
Assert.isTrue(!(body instanceof Publisher),
|
this.inserter = inserter;
|
||||||
"Please specify the element class by using body(Publisher, Class)");
|
|
||||||
this.inserter = BodyInserters.fromObject(body);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public RequestHeadersSpec<?> syncBody(Object body) {
|
||||||
|
return body(body);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ClientResponse> exchange() {
|
public Mono<ClientResponse> exchange() {
|
||||||
ClientRequest request = (this.inserter != null ?
|
ClientRequest request = (this.inserter != null ?
|
||||||
|
|
|
@ -30,6 +30,7 @@ import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
@ -56,13 +57,15 @@ import org.springframework.web.util.UriBuilderFactory;
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>For examples with a request body see:
|
* <p>For examples with a request body see:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
* <li>{@link RequestBodySpec#body(Object) body(Object)}
|
||||||
* <li>{@link RequestBodySpec#body(Publisher, Class) body(Publisher,Class)}
|
* <li>{@link RequestBodySpec#body(Publisher, Class) body(Publisher,Class)}
|
||||||
* <li>{@link RequestBodySpec#syncBody(Object) syncBody(Object)}
|
* <li>{@link RequestBodySpec#body(Object, Class) body(Object,Class)}
|
||||||
* <li>{@link RequestBodySpec#body(BodyInserter) body(BodyInserter)}
|
* <li>{@link RequestBodySpec#body(BodyInserter) body(BodyInserter)}
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public interface WebClient {
|
public interface WebClient {
|
||||||
|
@ -517,23 +520,80 @@ public interface WebClient {
|
||||||
RequestBodySpec contentType(MediaType contentType);
|
RequestBodySpec contentType(MediaType contentType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the body of the request using the given body inserter.
|
* A shortcut for {@link #body(BodyInserter)} with an
|
||||||
* {@link BodyInserters} provides access to built-in implementations of
|
* {@linkplain BodyInserters#fromObject Object inserter}.
|
||||||
* {@link BodyInserter}.
|
* For example:
|
||||||
* @param inserter the body inserter to use for the request body
|
* <p><pre class="code">
|
||||||
|
* Person person = ... ;
|
||||||
|
*
|
||||||
|
* Mono<Void> result = client.post()
|
||||||
|
* .uri("/persons/{id}", id)
|
||||||
|
* .contentType(MediaType.APPLICATION_JSON)
|
||||||
|
* .body(person)
|
||||||
|
* .retrieve()
|
||||||
|
* .bodyToMono(Void.class);
|
||||||
|
* </pre>
|
||||||
|
* <p>For multipart requests, provide a
|
||||||
|
* {@link org.springframework.util.MultiValueMap MultiValueMap}. The
|
||||||
|
* values in the {@code MultiValueMap} can be any Object representing
|
||||||
|
* the body of the part, or an
|
||||||
|
* {@link org.springframework.http.HttpEntity HttpEntity} representing
|
||||||
|
* a part with body and headers. The {@code MultiValueMap} can be built
|
||||||
|
* with {@link org.springframework.http.client.MultipartBodyBuilder
|
||||||
|
* MultipartBodyBuilder}.
|
||||||
|
* @param body the {@code Object} to write to the request
|
||||||
* @return this builder
|
* @return this builder
|
||||||
* @see org.springframework.web.reactive.function.BodyInserters
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used.
|
||||||
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
|
RequestHeadersSpec<?> body(Object body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shortcut for {@link #body(BodyInserter)} with a
|
||||||
|
* {@linkplain BodyInserters#fromProducer inserter}.
|
||||||
|
* For example:
|
||||||
|
* <p><pre>
|
||||||
|
* Single<Person> personSingle = ... ;
|
||||||
|
*
|
||||||
|
* Mono<Void> result = client.post()
|
||||||
|
* .uri("/persons/{id}", id)
|
||||||
|
* .contentType(MediaType.APPLICATION_JSON)
|
||||||
|
* .body(personSingle, Person.class)
|
||||||
|
* .retrieve()
|
||||||
|
* .bodyToMono(Void.class);
|
||||||
|
* </pre>
|
||||||
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param elementClass the class of elements contained in the producer
|
||||||
|
* @return this builder
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(Object producer, Class<?> elementClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variant of {@link #body(Object, Class)} that allows providing
|
||||||
|
* element type information that includes generics via a
|
||||||
|
* {@link ParameterizedTypeReference}.
|
||||||
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param elementType the type reference of elements contained in the producer
|
||||||
|
* @return this builder
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shortcut for {@link #body(BodyInserter)} with a
|
* A shortcut for {@link #body(BodyInserter)} with a
|
||||||
* {@linkplain BodyInserters#fromPublisher Publisher inserter}.
|
* {@linkplain BodyInserters#fromPublisher Publisher inserter}.
|
||||||
* For example:
|
* For example:
|
||||||
* <p><pre>
|
* <p><pre>
|
||||||
* Mono<Person> personMono = ... ;
|
* Mono<Person> personMono = ... ;
|
||||||
*
|
*
|
||||||
* Mono<Void> result = client.post()
|
* Mono<Void> result = client.post()
|
||||||
* .uri("/persons/{id}", id)
|
* .uri("/persons/{id}", id)
|
||||||
* .contentType(MediaType.APPLICATION_JSON)
|
* .contentType(MediaType.APPLICATION_JSON)
|
||||||
* .body(personMono, Person.class)
|
* .body(personMono, Person.class)
|
||||||
|
@ -553,13 +613,23 @@ public interface WebClient {
|
||||||
* element type information that includes generics via a
|
* element type information that includes generics via a
|
||||||
* {@link ParameterizedTypeReference}.
|
* {@link ParameterizedTypeReference}.
|
||||||
* @param publisher the {@code Publisher} to write to the request
|
* @param publisher the {@code Publisher} to write to the request
|
||||||
* @param typeReference the type reference of elements contained in the publisher
|
* @param elementType the type reference of elements contained in the publisher
|
||||||
* @param <T> the type of the elements contained in the publisher
|
* @param <T> the type of the elements contained in the publisher
|
||||||
* @param <P> the type of the {@code Publisher}
|
* @param <P> the type of the {@code Publisher}
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
<T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher,
|
<T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher,
|
||||||
ParameterizedTypeReference<T> typeReference);
|
ParameterizedTypeReference<T> elementType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the request using the given body inserter.
|
||||||
|
* {@link BodyInserters} provides access to built-in implementations of
|
||||||
|
* {@link BodyInserter}.
|
||||||
|
* @param inserter the body inserter to use for the request body
|
||||||
|
* @return this builder
|
||||||
|
* @see org.springframework.web.reactive.function.BodyInserters
|
||||||
|
*/
|
||||||
|
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shortcut for {@link #body(BodyInserter)} with an
|
* A shortcut for {@link #body(BodyInserter)} with an
|
||||||
|
@ -585,7 +655,12 @@ public interface WebClient {
|
||||||
* MultipartBodyBuilder}.
|
* MultipartBodyBuilder}.
|
||||||
* @param body the {@code Object} to write to the request
|
* @param body the {@code Object} to write to the request
|
||||||
* @return this builder
|
* @return this builder
|
||||||
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used.
|
||||||
|
* @deprecated as of Spring Framework 5.2 in favor of {@link #body(Object)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
RequestHeadersSpec<?> syncBody(Object body);
|
RequestHeadersSpec<?> syncBody(Object body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
|
@ -222,10 +223,43 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> elementClass) {
|
public Mono<ServerResponse> body(Object body) {
|
||||||
Assert.notNull(publisher, "Publisher must not be null");
|
return new DefaultEntityResponseBuilder<>(body,
|
||||||
Assert.notNull(elementClass, "Element Class must not be null");
|
BodyInserters.fromObject(body))
|
||||||
|
.status(this.statusCode)
|
||||||
|
.headers(this.headers)
|
||||||
|
.cookies(cookies -> cookies.addAll(this.cookies))
|
||||||
|
.hints(hints -> hints.putAll(this.hints))
|
||||||
|
.build()
|
||||||
|
.map(entityResponse -> entityResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ServerResponse> body(Object producer, Class<?> elementClass) {
|
||||||
|
return new DefaultEntityResponseBuilder<>(producer,
|
||||||
|
BodyInserters.fromProducer(producer, elementClass))
|
||||||
|
.status(this.statusCode)
|
||||||
|
.headers(this.headers)
|
||||||
|
.cookies(cookies -> cookies.addAll(this.cookies))
|
||||||
|
.hints(hints -> hints.putAll(this.hints))
|
||||||
|
.build()
|
||||||
|
.map(entityResponse -> entityResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ServerResponse> body(Object producer, ParameterizedTypeReference<?> elementType) {
|
||||||
|
return new DefaultEntityResponseBuilder<>(producer,
|
||||||
|
BodyInserters.fromProducer(producer, elementType))
|
||||||
|
.status(this.statusCode)
|
||||||
|
.headers(this.headers)
|
||||||
|
.cookies(cookies -> cookies.addAll(this.cookies))
|
||||||
|
.hints(hints -> hints.putAll(this.hints))
|
||||||
|
.build()
|
||||||
|
.map(entityResponse -> entityResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> elementClass) {
|
||||||
return new DefaultEntityResponseBuilder<>(publisher,
|
return new DefaultEntityResponseBuilder<>(publisher,
|
||||||
BodyInserters.fromPublisher(publisher, elementClass))
|
BodyInserters.fromPublisher(publisher, elementClass))
|
||||||
.status(this.statusCode)
|
.status(this.statusCode)
|
||||||
|
@ -238,13 +272,9 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
|
public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
|
||||||
ParameterizedTypeReference<T> typeReference) {
|
ParameterizedTypeReference<T> elementType) {
|
||||||
|
|
||||||
Assert.notNull(publisher, "Publisher must not be null");
|
|
||||||
Assert.notNull(typeReference, "ParameterizedTypeReference must not be null");
|
|
||||||
|
|
||||||
return new DefaultEntityResponseBuilder<>(publisher,
|
return new DefaultEntityResponseBuilder<>(publisher,
|
||||||
BodyInserters.fromPublisher(publisher, typeReference))
|
BodyInserters.fromPublisher(publisher, elementType))
|
||||||
.status(this.statusCode)
|
.status(this.statusCode)
|
||||||
.headers(this.headers)
|
.headers(this.headers)
|
||||||
.cookies(cookies -> cookies.addAll(this.cookies))
|
.cookies(cookies -> cookies.addAll(this.cookies))
|
||||||
|
@ -254,19 +284,9 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public Mono<ServerResponse> syncBody(Object body) {
|
public Mono<ServerResponse> syncBody(Object body) {
|
||||||
Assert.notNull(body, "Body must not be null");
|
return body(body);
|
||||||
Assert.isTrue(!(body instanceof Publisher),
|
|
||||||
"Please specify the element class by using body(Publisher, Class)");
|
|
||||||
|
|
||||||
return new DefaultEntityResponseBuilder<>(body,
|
|
||||||
BodyInserters.fromObject(body))
|
|
||||||
.status(this.statusCode)
|
|
||||||
.headers(this.headers)
|
|
||||||
.cookies(cookies -> cookies.addAll(this.cookies))
|
|
||||||
.hints(hints -> hints.putAll(this.hints))
|
|
||||||
.build()
|
|
||||||
.map(entityResponse -> entityResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -64,12 +64,38 @@ public interface EntityResponse<T> extends ServerResponse {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a builder with the given object.
|
* Create a builder with the given object.
|
||||||
* @param t the object that represents the body of the response
|
* @param body the object that represents the body of the response
|
||||||
* @param <T> the type of the elements contained in the publisher
|
* @param <T> the type of the body
|
||||||
* @return the created builder
|
* @return the created builder
|
||||||
*/
|
*/
|
||||||
static <T> Builder<T> fromObject(T t) {
|
static <T> Builder<T> fromObject(T body) {
|
||||||
return new DefaultEntityResponseBuilder<>(t, BodyInserters.fromObject(t));
|
return new DefaultEntityResponseBuilder<>(body, BodyInserters.fromObject(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a builder with the given producer.
|
||||||
|
* @param producer the producer that represents the body of the response
|
||||||
|
* @param elementClass the class of elements contained in the publisher
|
||||||
|
* @return the created builder
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
static <T> Builder<T> fromProducer(T producer, Class<?> elementClass) {
|
||||||
|
return new DefaultEntityResponseBuilder<>(producer,
|
||||||
|
BodyInserters.fromProducer(producer, elementClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a builder with the given producer.
|
||||||
|
* @param producer the producer that represents the body of the response
|
||||||
|
* @param typeReference the type of elements contained in the producer
|
||||||
|
* @return the created builder
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
static <T> Builder<T> fromProducer(T producer,
|
||||||
|
ParameterizedTypeReference<?> typeReference) {
|
||||||
|
|
||||||
|
return new DefaultEntityResponseBuilder<>(producer,
|
||||||
|
BodyInserters.fromProducer(producer, typeReference));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.http.CacheControl;
|
import org.springframework.http.CacheControl;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
@ -384,6 +385,45 @@ public interface ServerResponse {
|
||||||
*/
|
*/
|
||||||
BodyBuilder hints(Consumer<Map<String, Object>> hintsConsumer);
|
BodyBuilder hints(Consumer<Map<String, Object>> hintsConsumer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the response to the given {@code Object} and return it.
|
||||||
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
|
* {@link BodyInserters#fromObject(Object)}.
|
||||||
|
* @param body the body of the response
|
||||||
|
* @return the built response
|
||||||
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used.
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
Mono<ServerResponse> body(Object body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
||||||
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
|
* {@link BodyInserters#fromProducer(Object, Class)}.
|
||||||
|
* @param producer the producer to write to the response. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param elementClass the class of elements contained in the producer
|
||||||
|
* @return the built response
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
Mono<ServerResponse> body(Object producer, Class<?> elementClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
||||||
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
|
* {@link BodyInserters#fromProducer(Object, ParameterizedTypeReference)}.
|
||||||
|
* @param producer the producer to write to the response. This must be a
|
||||||
|
* {@link Publisher} or another producer adaptable to a
|
||||||
|
* {@code Publisher} via {@link ReactiveAdapterRegistry}
|
||||||
|
* @param typeReference a type reference describing the elements contained in the producer
|
||||||
|
* @return the built response
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
Mono<ServerResponse> body(Object producer, ParameterizedTypeReference<?> typeReference);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
||||||
* This convenience method combines {@link #body(BodyInserter)} and
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
|
@ -399,7 +439,7 @@ public interface ServerResponse {
|
||||||
/**
|
/**
|
||||||
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
* Set the body of the response to the given asynchronous {@code Publisher} and return it.
|
||||||
* This convenience method combines {@link #body(BodyInserter)} and
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
* {@link BodyInserters#fromPublisher(Publisher, Class)}.
|
* {@link BodyInserters#fromPublisher(Publisher, ParameterizedTypeReference)}.
|
||||||
* @param publisher the {@code Publisher} to write to the response
|
* @param publisher the {@code Publisher} to write to the response
|
||||||
* @param typeReference a type reference describing the elements contained in the publisher
|
* @param typeReference a type reference describing the elements contained in the publisher
|
||||||
* @param <T> the type of the elements contained in the publisher
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
@ -410,23 +450,28 @@ public interface ServerResponse {
|
||||||
ParameterizedTypeReference<T> typeReference);
|
ParameterizedTypeReference<T> typeReference);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the body of the response to the given synchronous {@code Object} and return it.
|
* Set the body of the response to the given {@code BodyInserter} and return it.
|
||||||
|
* @param inserter the {@code BodyInserter} that writes to the response
|
||||||
|
* @return the built response
|
||||||
|
*/
|
||||||
|
Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body of the response to the given {@code Object} and return it.
|
||||||
* This convenience method combines {@link #body(BodyInserter)} and
|
* This convenience method combines {@link #body(BodyInserter)} and
|
||||||
* {@link BodyInserters#fromObject(Object)}.
|
* {@link BodyInserters#fromObject(Object)}.
|
||||||
* @param body the body of the response
|
* @param body the body of the response
|
||||||
* @return the built response
|
* @return the built response
|
||||||
* @throws IllegalArgumentException if {@code body} is a {@link Publisher}, for which
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher}, for which
|
||||||
* {@link #body(Publisher, Class)} should be used.
|
* {@link #body(Publisher, Class)} should be used.
|
||||||
|
* @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
|
||||||
|
* instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
|
||||||
|
* for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used.
|
||||||
|
* @deprecated as of Spring Framework 5.2 in favor of {@link #body(Object)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
Mono<ServerResponse> syncBody(Object body);
|
Mono<ServerResponse> syncBody(Object body);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the body of the response to the given {@code BodyInserter} and return it.
|
|
||||||
* @param inserter the {@code BodyInserter} that writes to the response
|
|
||||||
* @return the built response
|
|
||||||
*/
|
|
||||||
Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the template with the given {@code name} using the given {@code modelAttributes}.
|
* Render the template with the given {@code name} using the given {@code modelAttributes}.
|
||||||
* The model attributes are mapped under a
|
* The model attributes are mapped under a
|
||||||
|
|
|
@ -16,14 +16,10 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.function.client
|
package org.springframework.web.reactive.function.client
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
import kotlinx.coroutines.reactive.awaitSingle
|
||||||
import kotlinx.coroutines.reactive.flow.asFlow
|
import kotlinx.coroutines.reactive.flow.asFlow
|
||||||
import kotlinx.coroutines.reactive.flow.asPublisher
|
|
||||||
import kotlinx.coroutines.reactor.mono
|
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import org.springframework.core.ParameterizedTypeReference
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec
|
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec
|
||||||
|
@ -39,20 +35,59 @@ import reactor.core.publisher.Mono
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use 'bodyWithType' instead.", replaceWith = ReplaceWith("bodyWithType(publisher)"))
|
||||||
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||||
inline fun <reified T : Any, S : Publisher<T>> RequestBodySpec.body(publisher: S): RequestHeadersSpec<*> =
|
inline fun <reified T : Any, S : Publisher<T>> RequestBodySpec.body(publisher: S): RequestHeadersSpec<*> =
|
||||||
body(publisher, object : ParameterizedTypeReference<T>() {})
|
body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coroutines [Flow] based extension for [WebClient.RequestBodySpec.body] providing a
|
* Extension for [WebClient.RequestBodySpec.body] providing a `bodyWithType<T>(Any)` variant
|
||||||
* body(Flow<T>)` variant leveraging Kotlin reified type parameters. This extension is
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
* not subject to type erasure and retains actual generic type arguments.
|
* erasure and retains actual generic type arguments.
|
||||||
*
|
* @param producer the producer to write to the request. This must be a
|
||||||
|
* [Publisher] or another producer adaptable to a
|
||||||
|
* [Publisher] via [org.springframework.core.ReactiveAdapterRegistry]
|
||||||
|
* @param <T> the type of the elements contained in the producer
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(producer: Any): RequestHeadersSpec<*> =
|
||||||
|
body(producer, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [WebClient.RequestBodySpec.body] providing a `bodyWithType(Publisher<T>)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param publisher the [Publisher] to write to the request
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(publisher: Publisher<T>): RequestHeadersSpec<*> =
|
||||||
|
body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [WebClient.RequestBodySpec.body] providing a `bodyWithType(Flow<T>)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param flow the [Flow] to write to the request
|
||||||
|
* @param <T> the type of the elements contained in the flow
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
inline fun <reified T : Any, S : Flow<T>> RequestBodySpec.body(flow: S): RequestHeadersSpec<*> =
|
inline fun <reified T : Any> RequestBodySpec.bodyWithType(flow: Flow<T>): RequestHeadersSpec<*> =
|
||||||
body(flow.asPublisher(), object : ParameterizedTypeReference<T>() {})
|
body(flow, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutines variant of [WebClient.RequestHeadersSpec.exchange].
|
||||||
|
*
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
suspend fun RequestHeadersSpec<out RequestHeadersSpec<*>>.awaitExchange(): ClientResponse =
|
||||||
|
exchange().awaitSingle()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension for [WebClient.ResponseSpec.bodyToMono] providing a `bodyToMono<Foo>()` variant
|
* Extension for [WebClient.ResponseSpec.bodyToMono] providing a `bodyToMono<Foo>()` variant
|
||||||
|
@ -90,25 +125,6 @@ inline fun <reified T : Any> WebClient.ResponseSpec.bodyToFlux(): Flux<T> =
|
||||||
inline fun <reified T : Any> WebClient.ResponseSpec.bodyToFlow(batchSize: Int = 1): Flow<T> =
|
inline fun <reified T : Any> WebClient.ResponseSpec.bodyToFlow(batchSize: Int = 1): Flow<T> =
|
||||||
bodyToFlux<T>().asFlow(batchSize)
|
bodyToFlux<T>().asFlow(batchSize)
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coroutines variant of [WebClient.RequestHeadersSpec.exchange].
|
|
||||||
*
|
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
suspend fun RequestHeadersSpec<out RequestHeadersSpec<*>>.awaitExchange(): ClientResponse =
|
|
||||||
exchange().awaitSingle()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coroutines variant of [WebClient.RequestBodySpec.body].
|
|
||||||
*
|
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
inline fun <reified T: Any> RequestBodySpec.body(crossinline supplier: suspend () -> T)
|
|
||||||
= body(GlobalScope.mono(Dispatchers.Unconfined) { supplier.invoke() })
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coroutines variant of [WebClient.ResponseSpec.bodyToMono].
|
* Coroutines variant of [WebClient.ResponseSpec.bodyToMono].
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.springframework.web.reactive.function.server
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
import kotlinx.coroutines.reactive.awaitSingle
|
||||||
import kotlinx.coroutines.reactive.flow.asPublisher
|
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import org.springframework.core.ParameterizedTypeReference
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
|
@ -33,9 +32,63 @@ import reactor.core.publisher.Mono
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use 'bodyWithType' instead.", replaceWith = ReplaceWith("bodyWithType(publisher)"))
|
||||||
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||||
inline fun <reified T : Any> ServerResponse.BodyBuilder.body(publisher: Publisher<T>): Mono<ServerResponse> =
|
inline fun <reified T : Any> ServerResponse.BodyBuilder.body(publisher: Publisher<T>): Mono<ServerResponse> =
|
||||||
body(publisher, object : ParameterizedTypeReference<T>() {})
|
body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [ServerResponse.BodyBuilder.body] providing a `bodyWithType<T>(Any)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param producer the producer to write to the response. This must be a
|
||||||
|
* [Publisher] or another producer adaptable to a
|
||||||
|
* [Publisher] via [org.springframework.core.ReactiveAdapterRegistry]
|
||||||
|
* @param <T> the type of the elements contained in the producer
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyWithType(producer: Any): Mono<ServerResponse> =
|
||||||
|
body(producer, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for [ServerResponse.BodyBuilder.body] providing a `bodyWithType(Publisher<T>)` variant
|
||||||
|
* leveraging Kotlin reified type parameters. This extension is not subject to type
|
||||||
|
* erasure and retains actual generic type arguments.
|
||||||
|
* @param publisher the [Publisher] to write to the response
|
||||||
|
* @param <T> the type of the elements contained in the publisher
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyWithType(publisher: Publisher<T>): Mono<ServerResponse> =
|
||||||
|
body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutines variant of [ServerResponse.BodyBuilder.body] with an [Any] parameter.
|
||||||
|
*
|
||||||
|
* Set the body of the response to the given {@code Object} and return it.
|
||||||
|
* This convenience method combines [body] and
|
||||||
|
* [org.springframework.web.reactive.function.BodyInserters.fromObject].
|
||||||
|
* @param body the body of the response
|
||||||
|
* @return the built response
|
||||||
|
* @throws IllegalArgumentException if `body` is a [Publisher] or an
|
||||||
|
* instance of a type supported by [org.springframework.core.ReactiveAdapterRegistry.getSharedInstance],
|
||||||
|
*/
|
||||||
|
suspend fun ServerResponse.BodyBuilder.bodyAndAwait(body: Any): ServerResponse =
|
||||||
|
body(body).awaitSingle()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutines variant of [ServerResponse.BodyBuilder.body] with [Any] and
|
||||||
|
* [ParameterizedTypeReference] parameters providing a `bodyAndAwait(Flow<T>)` variant.
|
||||||
|
* This extension is not subject to type erasure and retains actual generic type arguments.
|
||||||
|
*
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@FlowPreview
|
||||||
|
suspend inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyAndAwait(flow: Flow<T>): ServerResponse =
|
||||||
|
body(flow, object : ParameterizedTypeReference<T>() {}).awaitSingle()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension for [ServerResponse.BodyBuilder.body] providing a
|
* Extension for [ServerResponse.BodyBuilder.body] providing a
|
||||||
* `bodyToServerSentEvents(Publisher<T>)` variant. This extension is not subject to type
|
* `bodyToServerSentEvents(Publisher<T>)` variant. This extension is not subject to type
|
||||||
|
@ -44,7 +97,7 @@ inline fun <reified T : Any> ServerResponse.BodyBuilder.body(publisher: Publishe
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
@Deprecated("Use 'sse().body()' instead.")
|
@Deprecated("Use 'sse().bodyWithType(publisher)' instead.", replaceWith = ReplaceWith("sse().bodyWithType(publisher)"))
|
||||||
inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyToServerSentEvents(publisher: Publisher<T>): Mono<ServerResponse> =
|
inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyToServerSentEvents(publisher: Publisher<T>): Mono<ServerResponse> =
|
||||||
contentType(MediaType.TEXT_EVENT_STREAM).body(publisher, object : ParameterizedTypeReference<T>() {})
|
contentType(MediaType.TEXT_EVENT_STREAM).body(publisher, object : ParameterizedTypeReference<T>() {})
|
||||||
|
|
||||||
|
@ -77,38 +130,7 @@ fun ServerResponse.BodyBuilder.html() = contentType(MediaType.TEXT_HTML)
|
||||||
fun ServerResponse.BodyBuilder.sse() = contentType(MediaType.TEXT_EVENT_STREAM)
|
fun ServerResponse.BodyBuilder.sse() = contentType(MediaType.TEXT_EVENT_STREAM)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coroutines variant of [ServerResponse.HeadersBuilder.build].
|
* Coroutines variant of [ServerResponse.BodyBuilder.render].
|
||||||
*
|
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
suspend fun ServerResponse.HeadersBuilder<out ServerResponse.HeadersBuilder<*>>.buildAndAwait(): ServerResponse =
|
|
||||||
build().awaitSingle()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coroutines [Flow] based extension for [ServerResponse.BodyBuilder.body] providing a
|
|
||||||
* `bodyFlowAndAwait(Flow<T>)` variant. This extension is not subject to type erasure and retains
|
|
||||||
* actual generic type arguments.
|
|
||||||
*
|
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
@FlowPreview
|
|
||||||
suspend inline fun <reified T : Any> ServerResponse.BodyBuilder.bodyFlowAndAwait(flow: Flow<T>): ServerResponse =
|
|
||||||
body(flow.asPublisher(), object : ParameterizedTypeReference<T>() {}).awaitSingle()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coroutines variant of [ServerResponse.BodyBuilder.syncBody].
|
|
||||||
*
|
|
||||||
* @author Sebastien Deleuze
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
suspend fun ServerResponse.BodyBuilder.bodyAndAwait(body: Any): ServerResponse =
|
|
||||||
syncBody(body).awaitSingle()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coroutines variant of [ServerResponse.BodyBuilder.syncBody] without the sync prefix since it is ok to use it within
|
|
||||||
* another suspendable function.
|
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
|
@ -117,11 +139,20 @@ suspend fun ServerResponse.BodyBuilder.renderAndAwait(name: String, vararg model
|
||||||
render(name, *modelAttributes).awaitSingle()
|
render(name, *modelAttributes).awaitSingle()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coroutines variant of [ServerResponse.BodyBuilder.syncBody] without the sync prefix since it is ok to use it within
|
* Coroutines variant of [ServerResponse.BodyBuilder.render].
|
||||||
* another suspendable function.
|
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
suspend fun ServerResponse.BodyBuilder.renderAndAwait(name: String, model: Map<String, *>): ServerResponse =
|
suspend fun ServerResponse.BodyBuilder.renderAndAwait(name: String, model: Map<String, *>): ServerResponse =
|
||||||
render(name, model).awaitSingle()
|
render(name, model).awaitSingle()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutines variant of [ServerResponse.HeadersBuilder.build].
|
||||||
|
*
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
suspend fun ServerResponse.HeadersBuilder<out ServerResponse.HeadersBuilder<*>>.buildAndAwait(): ServerResponse =
|
||||||
|
build().awaitSingle()
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonView;
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import io.reactivex.Single;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -159,6 +160,51 @@ public class BodyInsertersTests {
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ofProducerWithMono() {
|
||||||
|
Mono<User> body = Mono.just(new User("foo", "bar"));
|
||||||
|
BodyInserter<?, ReactiveHttpOutputMessage> inserter = BodyInserters.fromProducer(body, User.class);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||||
|
Mono<Void> result = inserter.insert(response, this.context);
|
||||||
|
StepVerifier.create(result).expectComplete().verify();
|
||||||
|
StepVerifier.create(response.getBodyAsString())
|
||||||
|
.expectNext("{\"username\":\"foo\",\"password\":\"bar\"}")
|
||||||
|
.expectComplete()
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ofProducerWithFlux() {
|
||||||
|
Flux<String> body = Flux.just("foo");
|
||||||
|
BodyInserter<?, ReactiveHttpOutputMessage> inserter = BodyInserters.fromProducer(body, String.class);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||||
|
Mono<Void> result = inserter.insert(response, this.context);
|
||||||
|
StepVerifier.create(result).expectComplete().verify();
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.consumeNextWith(buf -> {
|
||||||
|
String actual = DataBufferTestUtils.dumpString(buf, UTF_8);
|
||||||
|
assertThat(actual).isEqualTo("foo");
|
||||||
|
})
|
||||||
|
.expectComplete()
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ofProducerWithSingle() {
|
||||||
|
Single<User> body = Single.just(new User("foo", "bar"));
|
||||||
|
BodyInserter<?, ReactiveHttpOutputMessage> inserter = BodyInserters.fromProducer(body, User.class);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||||
|
Mono<Void> result = inserter.insert(response, this.context);
|
||||||
|
StepVerifier.create(result).expectComplete().verify();
|
||||||
|
StepVerifier.create(response.getBodyAsString())
|
||||||
|
.expectNext("{\"username\":\"foo\",\"password\":\"bar\"}")
|
||||||
|
.expectComplete()
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ofPublisher() {
|
public void ofPublisher() {
|
||||||
Flux<String> body = Flux.just("foo");
|
Flux<String> body = Flux.just("foo");
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class MultipartIntegrationTests extends AbstractRouterFunctionIntegration
|
||||||
Mono<ClientResponse> result = webClient
|
Mono<ClientResponse> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("http://localhost:" + this.port + "/multipartData")
|
.uri("http://localhost:" + this.port + "/multipartData")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.exchange();
|
.exchange();
|
||||||
|
|
||||||
StepVerifier
|
StepVerifier
|
||||||
|
@ -75,7 +75,7 @@ public class MultipartIntegrationTests extends AbstractRouterFunctionIntegration
|
||||||
Mono<ClientResponse> result = webClient
|
Mono<ClientResponse> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("http://localhost:" + this.port + "/parts")
|
.uri("http://localhost:" + this.port + "/parts")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.exchange();
|
.exchange();
|
||||||
|
|
||||||
StepVerifier
|
StepVerifier
|
||||||
|
@ -89,7 +89,7 @@ public class MultipartIntegrationTests extends AbstractRouterFunctionIntegration
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("http://localhost:" + this.port + "/transferTo")
|
.uri("http://localhost:" + this.port + "/transferTo")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ public class MultipartIntegrationTests extends AbstractRouterFunctionIntegration
|
||||||
Path tempFile = Files.createTempFile("MultipartIntegrationTests", null);
|
Path tempFile = Files.createTempFile("MultipartIntegrationTests", null);
|
||||||
return part.transferTo(tempFile)
|
return part.transferTo(tempFile)
|
||||||
.then(ServerResponse.ok()
|
.then(ServerResponse.ok()
|
||||||
.syncBody(tempFile.toString()));
|
.body(tempFile.toString()));
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
return Mono.error(e);
|
return Mono.error(e);
|
||||||
|
|
|
@ -186,7 +186,7 @@ public class DefaultWebClientTests {
|
||||||
WebClient client = this.builder.build();
|
WebClient client = this.builder.build();
|
||||||
|
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||||
client.post().uri("https://example.com").syncBody(mono));
|
client.post().uri("https://example.com").body(mono));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -354,7 +354,7 @@ public class WebClientIntegrationTests {
|
||||||
.uri("/pojo/capitalize")
|
.uri("/pojo/capitalize")
|
||||||
.accept(MediaType.APPLICATION_JSON)
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.syncBody(new Pojo("foofoo", "barbar"))
|
.body(new Pojo("foofoo", "barbar"))
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Pojo.class);
|
.bodyToMono(Pojo.class);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import io.reactivex.Single;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -76,6 +77,14 @@ public class DefaultEntityResponseBuilderTests {
|
||||||
assertThat(response.entity()).isSameAs(body);
|
assertThat(response.entity()).isSameAs(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromProducer() {
|
||||||
|
Single<String> body = Single.just("foo");
|
||||||
|
ParameterizedTypeReference<String> typeReference = new ParameterizedTypeReference<String>() {};
|
||||||
|
EntityResponse<Single<String>> response = EntityResponse.fromProducer(body, typeReference).build().block();
|
||||||
|
assertThat(response.entity()).isSameAs(body);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void status() {
|
public void status() {
|
||||||
String body = "foo";
|
String body = "foo";
|
||||||
|
|
|
@ -308,7 +308,7 @@ public class DefaultServerResponseBuilderTests {
|
||||||
public void copyCookies() {
|
public void copyCookies() {
|
||||||
Mono<ServerResponse> serverResponse = ServerResponse.ok()
|
Mono<ServerResponse> serverResponse = ServerResponse.ok()
|
||||||
.cookie(ResponseCookie.from("foo", "bar").build())
|
.cookie(ResponseCookie.from("foo", "bar").build())
|
||||||
.syncBody("body");
|
.body("body");
|
||||||
|
|
||||||
assertThat(serverResponse.block().cookies().isEmpty()).isFalse();
|
assertThat(serverResponse.block().cookies().isEmpty()).isFalse();
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ public class DefaultServerResponseBuilderTests {
|
||||||
Mono<Void> mono = Mono.empty();
|
Mono<Void> mono = Mono.empty();
|
||||||
|
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||||
ServerResponse.ok().syncBody(mono));
|
ServerResponse.ok().body(mono));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -368,7 +368,7 @@ public class DefaultServerResponseBuilderTests {
|
||||||
String etag = "\"foo\"";
|
String etag = "\"foo\"";
|
||||||
ServerResponse responseMono = ServerResponse.ok()
|
ServerResponse responseMono = ServerResponse.ok()
|
||||||
.eTag(etag)
|
.eTag(etag)
|
||||||
.syncBody("bar")
|
.body("bar")
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com")
|
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com")
|
||||||
|
@ -392,7 +392,7 @@ public class DefaultServerResponseBuilderTests {
|
||||||
|
|
||||||
ServerResponse responseMono = ServerResponse.ok()
|
ServerResponse responseMono = ServerResponse.ok()
|
||||||
.lastModified(oneMinuteBeforeNow)
|
.lastModified(oneMinuteBeforeNow)
|
||||||
.syncBody("bar")
|
.body("bar")
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com")
|
MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com")
|
||||||
|
|
|
@ -33,8 +33,8 @@ public class InvalidHttpMethodIntegrationTests extends AbstractRouterFunctionInt
|
||||||
@Override
|
@Override
|
||||||
protected RouterFunction<?> routerFunction() {
|
protected RouterFunction<?> routerFunction() {
|
||||||
return RouterFunctions.route(RequestPredicates.GET("/"),
|
return RouterFunctions.route(RequestPredicates.GET("/"),
|
||||||
request -> ServerResponse.ok().syncBody("FOO"))
|
request -> ServerResponse.ok().body("FOO"))
|
||||||
.andRoute(RequestPredicates.all(), request -> ServerResponse.ok().syncBody("BAR"));
|
.andRoute(RequestPredicates.all(), request -> ServerResponse.ok().body("BAR"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -125,7 +125,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati
|
||||||
|
|
||||||
public Mono<ServerResponse> pattern(ServerRequest request) {
|
public Mono<ServerResponse> pattern(ServerRequest request) {
|
||||||
String pattern = matchingPattern(request).getPatternString();
|
String pattern = matchingPattern(request).getPatternString();
|
||||||
return ServerResponse.ok().syncBody(pattern);
|
return ServerResponse.ok().body(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<ClientResponse> result = webClient
|
Mono<ClientResponse> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/requestPart")
|
.uri("/requestPart")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.exchange();
|
.exchange();
|
||||||
|
|
||||||
StepVerifier
|
StepVerifier
|
||||||
|
@ -99,7 +99,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/requestBodyMap")
|
.uri("/requestBodyMap")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/requestBodyFlux")
|
.uri("/requestBodyFlux")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/filePartFlux")
|
.uri("/filePartFlux")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/filePartMono")
|
.uri("/filePartMono")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Flux<String> result = webClient
|
Flux<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/transferTo")
|
.uri("/transferTo")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToFlux(String.class);
|
.bodyToFlux(String.class);
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
||||||
Mono<String> result = webClient
|
Mono<String> result = webClient
|
||||||
.post()
|
.post()
|
||||||
.uri("/modelAttribute")
|
.uri("/modelAttribute")
|
||||||
.syncBody(generateBody())
|
.body(generateBody())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(String.class);
|
.bodyToMono(String.class);
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Test
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import org.springframework.core.ParameterizedTypeReference
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock object based tests for [WebClient] Kotlin extensions
|
* Mock object based tests for [WebClient] Kotlin extensions
|
||||||
|
@ -41,9 +42,9 @@ class WebClientExtensionsTests {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `RequestBodySpec#body with Publisher and reified type parameters`() {
|
fun `RequestBodySpec#bodyWithType with Publisher and reified type parameters`() {
|
||||||
val body = mockk<Publisher<List<Foo>>>()
|
val body = mockk<Publisher<List<Foo>>>()
|
||||||
requestBodySpec.body(body)
|
requestBodySpec.bodyWithType(body)
|
||||||
verify { requestBodySpec.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
verify { requestBodySpec.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +52,16 @@ class WebClientExtensionsTests {
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
fun `RequestBodySpec#body with Flow and reified type parameters`() {
|
fun `RequestBodySpec#body with Flow and reified type parameters`() {
|
||||||
val body = mockk<Flow<List<Foo>>>()
|
val body = mockk<Flow<List<Foo>>>()
|
||||||
requestBodySpec.body(body)
|
requestBodySpec.bodyWithType(body)
|
||||||
verify { requestBodySpec.body(ofType<Publisher<List<Foo>>>(), object : ParameterizedTypeReference<List<Foo>>() {}) }
|
verify { requestBodySpec.body(ofType<Any>(), object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@FlowPreview
|
||||||
|
fun `RequestBodySpec#body with CompletableFuture and reified type parameters`() {
|
||||||
|
val body = mockk<CompletableFuture<List<Foo>>>()
|
||||||
|
requestBodySpec.bodyWithType<List<Foo>>(body)
|
||||||
|
verify { requestBodySpec.body(ofType<Any>(), object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,19 +92,6 @@ class WebClientExtensionsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun body() {
|
|
||||||
val headerSpec = mockk<WebClient.RequestHeadersSpec<*>>()
|
|
||||||
val supplier: suspend () -> String = mockk()
|
|
||||||
every { requestBodySpec.body(ofType<Mono<String>>()) } returns headerSpec
|
|
||||||
runBlocking {
|
|
||||||
requestBodySpec.body(supplier)
|
|
||||||
}
|
|
||||||
verify {
|
|
||||||
requestBodySpec.body(ofType<Mono<String>>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun awaitBody() {
|
fun awaitBody() {
|
||||||
val spec = mockk<WebClient.ResponseSpec>()
|
val spec = mockk<WebClient.ResponseSpec>()
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.server
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import io.reactivex.Flowable
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -28,12 +29,14 @@ import org.reactivestreams.Publisher
|
||||||
import org.springframework.core.ParameterizedTypeReference
|
import org.springframework.core.ParameterizedTypeReference
|
||||||
import org.springframework.http.MediaType.*
|
import org.springframework.http.MediaType.*
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock object based tests for [ServerResponse] Kotlin extensions
|
* Mock object based tests for [ServerResponse] Kotlin extensions
|
||||||
*
|
*
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UnassignedFluxMonoInstance")
|
||||||
class ServerResponseExtensionsTests {
|
class ServerResponseExtensionsTests {
|
||||||
|
|
||||||
private val bodyBuilder = mockk<ServerResponse.BodyBuilder>(relaxed = true)
|
private val bodyBuilder = mockk<ServerResponse.BodyBuilder>(relaxed = true)
|
||||||
|
@ -42,10 +45,51 @@ class ServerResponseExtensionsTests {
|
||||||
@Test
|
@Test
|
||||||
fun `BodyBuilder#body with Publisher and reified type parameters`() {
|
fun `BodyBuilder#body with Publisher and reified type parameters`() {
|
||||||
val body = mockk<Publisher<List<Foo>>>()
|
val body = mockk<Publisher<List<Foo>>>()
|
||||||
bodyBuilder.body(body)
|
bodyBuilder.bodyWithType(body)
|
||||||
verify { bodyBuilder.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
verify { bodyBuilder.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BodyBuilder#body with CompletableFuture and reified type parameters`() {
|
||||||
|
val body = mockk<CompletableFuture<List<Foo>>>()
|
||||||
|
bodyBuilder.bodyWithType<List<Foo>>(body)
|
||||||
|
verify { bodyBuilder.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BodyBuilder#body with Flowable and reified type parameters`() {
|
||||||
|
val body = mockk<Flowable<List<Foo>>>()
|
||||||
|
bodyBuilder.bodyWithType(body)
|
||||||
|
verify { bodyBuilder.body(body, object : ParameterizedTypeReference<List<Foo>>() {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BodyBuilder#bodyAndAwait with object parameter`() {
|
||||||
|
val response = mockk<ServerResponse>()
|
||||||
|
val body = "foo"
|
||||||
|
every { bodyBuilder.body(ofType<String>()) } returns Mono.just(response)
|
||||||
|
runBlocking {
|
||||||
|
bodyBuilder.bodyAndAwait(body)
|
||||||
|
}
|
||||||
|
verify {
|
||||||
|
bodyBuilder.body(ofType<String>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@FlowPreview
|
||||||
|
fun `BodyBuilder#bodyAndAwait with flow parameter`() {
|
||||||
|
val response = mockk<ServerResponse>()
|
||||||
|
val body = mockk<Flow<List<Foo>>>()
|
||||||
|
every { bodyBuilder.body(ofType<Flow<List<Foo>>>(), object : ParameterizedTypeReference<List<Foo>>() {}) } returns Mono.just(response)
|
||||||
|
runBlocking {
|
||||||
|
bodyBuilder.bodyAndAwait(body)
|
||||||
|
}
|
||||||
|
verify {
|
||||||
|
bodyBuilder.body(ofType<Flow<List<Foo>>>(), object : ParameterizedTypeReference<List<Foo>>() {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `BodyBuilder#json`() {
|
fun `BodyBuilder#json`() {
|
||||||
bodyBuilder.json()
|
bodyBuilder.json()
|
||||||
|
@ -71,42 +115,7 @@ class ServerResponseExtensionsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun await() {
|
fun `BodyBuilder#renderAndAwait with a vararg parameter`() {
|
||||||
val response = mockk<ServerResponse>()
|
|
||||||
val builder = mockk<ServerResponse.HeadersBuilder<*>>()
|
|
||||||
every { builder.build() } returns Mono.just(response)
|
|
||||||
runBlocking {
|
|
||||||
assertEquals(response, builder.buildAndAwait())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `bodyAndAwait with object parameter`() {
|
|
||||||
val response = mockk<ServerResponse>()
|
|
||||||
val body = "foo"
|
|
||||||
every { bodyBuilder.syncBody(ofType<String>()) } returns Mono.just(response)
|
|
||||||
runBlocking {
|
|
||||||
bodyBuilder.bodyAndAwait(body)
|
|
||||||
}
|
|
||||||
verify {
|
|
||||||
bodyBuilder.syncBody(ofType<String>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@FlowPreview
|
|
||||||
fun bodyFlowAndAwait() {
|
|
||||||
val response = mockk<ServerResponse>()
|
|
||||||
val body = mockk<Flow<List<Foo>>>()
|
|
||||||
every { bodyBuilder.body(ofType<Publisher<List<Foo>>>()) } returns Mono.just(response)
|
|
||||||
runBlocking {
|
|
||||||
bodyBuilder.bodyFlowAndAwait(body)
|
|
||||||
}
|
|
||||||
verify { bodyBuilder.body(ofType<Publisher<List<Foo>>>(), object : ParameterizedTypeReference<List<Foo>>() {}) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `renderAndAwait with a vararg parameter`() {
|
|
||||||
val response = mockk<ServerResponse>()
|
val response = mockk<ServerResponse>()
|
||||||
every { bodyBuilder.render("foo", any(), any()) } returns Mono.just(response)
|
every { bodyBuilder.render("foo", any(), any()) } returns Mono.just(response)
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
@ -118,7 +127,7 @@ class ServerResponseExtensionsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `renderAndAwait with a Map parameter`() {
|
fun `BodyBuilder#renderAndAwait with a Map parameter`() {
|
||||||
val response = mockk<ServerResponse>()
|
val response = mockk<ServerResponse>()
|
||||||
val map = mockk<Map<String, *>>()
|
val map = mockk<Map<String, *>>()
|
||||||
every { bodyBuilder.render("foo", map) } returns Mono.just(response)
|
every { bodyBuilder.render("foo", map) } returns Mono.just(response)
|
||||||
|
@ -130,5 +139,15 @@ class ServerResponseExtensionsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `HeadersBuilder#buildAndAwait`() {
|
||||||
|
val response = mockk<ServerResponse>()
|
||||||
|
val builder = mockk<ServerResponse.HeadersBuilder<*>>()
|
||||||
|
every { builder.build() } returns Mono.just(response)
|
||||||
|
runBlocking {
|
||||||
|
assertEquals(response, builder.buildAndAwait())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Foo
|
class Foo
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,7 +318,8 @@ is closed and is not placed back in the pool.
|
||||||
[[webflux-client-body]]
|
[[webflux-client-body]]
|
||||||
== Request Body
|
== Request Body
|
||||||
|
|
||||||
The request body can be encoded from an `Object`, as the following example shows:
|
The request body can be encoded from any asynchronous type handled by `ReactiveAdapterRegistry`,
|
||||||
|
like `Mono` as the following example shows:
|
||||||
|
|
||||||
[source,java,intent=0]
|
[source,java,intent=0]
|
||||||
[subs="verbatim,quotes"]
|
[subs="verbatim,quotes"]
|
||||||
|
@ -348,7 +349,7 @@ You can also have a stream of objects be encoded, as the following example shows
|
||||||
.bodyToMono(Void.class);
|
.bodyToMono(Void.class);
|
||||||
----
|
----
|
||||||
|
|
||||||
Alternatively, if you have the actual value, you can use the `syncBody` shortcut method,
|
Alternatively, if you have the actual value, you can use the `body` shortcut method,
|
||||||
as the following example shows:
|
as the following example shows:
|
||||||
|
|
||||||
[source,java,intent=0]
|
[source,java,intent=0]
|
||||||
|
@ -359,7 +360,7 @@ as the following example shows:
|
||||||
Mono<Void> result = client.post()
|
Mono<Void> result = client.post()
|
||||||
.uri("/persons/{id}", id)
|
.uri("/persons/{id}", id)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.syncBody(person)
|
.body(person)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Void.class);
|
.bodyToMono(Void.class);
|
||||||
----
|
----
|
||||||
|
@ -380,7 +381,7 @@ content is automatically set to `application/x-www-form-urlencoded` by the
|
||||||
|
|
||||||
Mono<Void> result = client.post()
|
Mono<Void> result = client.post()
|
||||||
.uri("/path", id)
|
.uri("/path", id)
|
||||||
.syncBody(formData)
|
.body(formData)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Void.class);
|
.bodyToMono(Void.class);
|
||||||
----
|
----
|
||||||
|
@ -428,7 +429,7 @@ explicitly provide the `MediaType` to use for each part through one of the overl
|
||||||
builder `part` methods.
|
builder `part` methods.
|
||||||
|
|
||||||
Once a `MultiValueMap` is prepared, the easiest way to pass it to the the `WebClient` is
|
Once a `MultiValueMap` is prepared, the easiest way to pass it to the the `WebClient` is
|
||||||
through the `syncBody` method, as the following example shows:
|
through the `body` method, as the following example shows:
|
||||||
|
|
||||||
[source,java,intent=0]
|
[source,java,intent=0]
|
||||||
[subs="verbatim,quotes"]
|
[subs="verbatim,quotes"]
|
||||||
|
@ -437,7 +438,7 @@ through the `syncBody` method, as the following example shows:
|
||||||
|
|
||||||
Mono<Void> result = client.post()
|
Mono<Void> result = client.post()
|
||||||
.uri("/path", id)
|
.uri("/path", id)
|
||||||
.syncBody(builder.build())
|
.body(builder.build())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Void.class);
|
.bodyToMono(Void.class);
|
||||||
----
|
----
|
||||||
|
|
Loading…
Reference in New Issue