Skip to content

Commit 5f5144a

Browse files
committed
Introduce awaitReceive and awaitReceiveOrNull APIs
This commit introduces awaitReceive/awaitReceiveOrNull as a replacement for awaitBody/awaitBodyOrNull and explains the rationale behind it. Closes gh-30926 Signed-off-by: George Papadopoulos <[email protected]>
1 parent 94a49c4 commit 5f5144a

File tree

1 file changed

+96
-1
lines changed

1 file changed

+96
-1
lines changed

spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ import reactor.core.publisher.Mono
3131
import java.net.InetSocketAddress
3232
import java.security.Principal
3333
import kotlin.reflect.KClass
34+
import kotlin.reflect.KType
35+
import kotlin.reflect.full.starProjectedType
3436

3537
/**
3638
* Extension for [ServerRequest.bodyToMono] providing a `bodyToMono<Foo>()` variant
3739
* leveraging Kotlin reified type parameters. This extension is not subject to type
3840
* erasure and retains actual generic type arguments.
39-
*
41+
*
4042
* @author Sebastien Deleuze
4143
* @since 5.0
4244
*/
@@ -76,28 +78,73 @@ fun <T : Any> ServerRequest.bodyToFlow(clazz: KClass<T>): Flow<T> =
7678
/**
7779
* Non-nullable Coroutines variant of [ServerRequest.bodyToMono].
7880
*
81+
* ### Deprecation
82+
*
83+
* This method is deprecated in favor of [awaitReceive].
84+
*
85+
* * It is difficult to reason about its exception handling,
86+
* since it forces you to handle both types of errors: serialization and coroutines-related.
87+
* * In the case of [IllegalArgumentException] it is indistinguishable if the exception came from coroutines
88+
* or serialization unless checked explicit for `SerializationException` type.
89+
* * It is unclear whether [IllegalArgumentException] can be thrown at all from coroutines-perspective
90+
* as the receiver is a [Mono].
91+
*
7992
* @author Sebastien Deleuze
8093
* @since 5.2
8194
*/
95+
@Deprecated(
96+
message = "Deprecated in favor of awaitReceive.",
97+
level = DeprecationLevel.WARNING,
98+
replaceWith = ReplaceWith("this.awaitReceive")
99+
)
82100
suspend inline fun <reified T : Any> ServerRequest.awaitBody(): T =
83101
bodyToMono<T>().awaitSingle()
84102

85103
/**
86104
* `KClass` non-nullable Coroutines variant of [ServerRequest.bodyToMono].
87105
* Please consider `awaitBody<Foo>` variant if possible.
88106
*
107+
* ### Deprecation
108+
*
109+
* This method is deprecated in favor of [awaitReceive].
110+
*
111+
* * It is difficult to reason about its exception handling,
112+
* since it forces you to handle both types of errors: serialization and coroutines-related.
113+
* * In the case of [IllegalArgumentException] it is indistinguishable if the exception came from coroutines
114+
* or serialization unless checked explicit for `SerializationException` type.
115+
* * It is unclear whether [IllegalArgumentException] can be thrown at all from coroutines-perspective
116+
* as the receiver is a [Mono].
117+
*
89118
* @author Igor Manushin
90119
* @since 5.3
91120
*/
121+
@Deprecated(
122+
message = "Deprecated in favor of awaitReceive.",
123+
level = DeprecationLevel.WARNING,
124+
replaceWith = ReplaceWith("this.awaitReceive")
125+
)
92126
suspend fun <T : Any> ServerRequest.awaitBody(clazz: KClass<T>): T =
93127
bodyToMono(clazz.java).awaitSingle()
94128

95129
/**
96130
* Nullable Coroutines variant of [ServerRequest.bodyToMono].
97131
*
132+
* ### Deprecation
133+
*
134+
* This method is deprecated because the conventions established in Kotlin mandate that an operation
135+
* with the name `awaitBodyOrNull` returns `null` instead of throwing in case there is an error;
136+
* however, this would also mean that this method would return `null` if there is a serialization error.
137+
* This can be confusing to those who expect this function to validate if incoming body can be transformed into expected `T`.
138+
*
98139
* @author Sebastien Deleuze
99140
* @since 5.2
100141
*/
142+
@Deprecated(
143+
message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
144+
"Please consider replacing it with awaitReceiveOrNull.",
145+
level = DeprecationLevel.WARNING,
146+
replaceWith = ReplaceWith("this.awaitReceiveOrNull")
147+
)
101148
@Suppress("DEPRECATION")
102149
suspend inline fun <reified T : Any> ServerRequest.awaitBodyOrNull(): T? =
103150
bodyToMono<T>().awaitSingleOrNull()
@@ -106,13 +153,61 @@ suspend inline fun <reified T : Any> ServerRequest.awaitBodyOrNull(): T? =
106153
* `KClass` nullable Coroutines variant of [ServerRequest.bodyToMono].
107154
* Please consider `awaitBodyOrNull<Foo>` variant if possible.
108155
*
156+
* ### Deprecation
157+
*
158+
* This method is deprecated because the conventions established in Kotlin mandate that an operation
159+
* with the name `awaitBodyOrNull` returns `null` instead of throwing in case there is an error;
160+
* however, this would also mean that this method would return `null` if there is a serialization error.
161+
* This can be confusing to those who expect this function to validate if incoming body can be transformed into expected `T`.
162+
*
109163
* @author Igor Manushin
110164
* @since 5.3
111165
*/
166+
@Deprecated(
167+
message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
168+
"Please consider replacing it with awaitReceiveOrNull.",
169+
level = DeprecationLevel.WARNING,
170+
replaceWith = ReplaceWith("this.awaitReceiveOrNull")
171+
)
112172
@Suppress("DEPRECATION")
113173
suspend fun <T : Any> ServerRequest.awaitBodyOrNull(clazz: KClass<T>): T? =
114174
bodyToMono(clazz.java).awaitSingleOrNull()
115175

176+
/**
177+
* Receives the incoming body for this [request][ServerRequest] and transforms it to the requested `T` type.
178+
*
179+
* @author George Papadopoulos
180+
* @since 6.0.11
181+
* @throws IllegalArgumentException when body cannot be transformed to the requested type.
182+
*/
183+
suspend inline fun <reified T : Any> ServerRequest.awaitReceive(): T = awaitReceiveNullable<T>()
184+
?: throw ContentTransformationException(starProjectedType<T>())
185+
186+
/**
187+
* Receives the incoming body for this [request][ServerRequest] and transforms it to the requested `T` type.
188+
*
189+
* @author George Papadopoulos
190+
* @since 6.0.11
191+
* @throws IllegalArgumentException when body cannot be transformed to the requested type.
192+
*/
193+
suspend inline fun <reified T : Any> ServerRequest.awaitReceiveNullable(): T? {
194+
try {
195+
return bodyToMono<T>().awaitSingleOrNull()
196+
} catch (e: IllegalArgumentException) {
197+
throw ContentTransformationException(starProjectedType<T>())
198+
}
199+
}
200+
201+
@PublishedApi
202+
internal class ContentTransformationException(
203+
type: KType
204+
) : IllegalArgumentException("Cannot transform this request's body to $type")
205+
206+
@PublishedApi
207+
internal inline fun <reified T : Any> starProjectedType(): KType {
208+
return T::class.starProjectedType
209+
}
210+
116211
/**
117212
* Coroutines variant of [ServerRequest.formData].
118213
*

0 commit comments

Comments
 (0)