From 5f5144ab17b2253c47b2ccd728339e45f07b76d0 Mon Sep 17 00:00:00 2001 From: George Papadopoulos Date: Thu, 7 Sep 2023 19:18:14 +0300 Subject: [PATCH] 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 --- .../server/ServerRequestExtensions.kt | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt index 5063b56c9db0..ee3078f77a98 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt @@ -31,12 +31,14 @@ import reactor.core.publisher.Mono import java.net.InetSocketAddress import java.security.Principal import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.full.starProjectedType /** * Extension for [ServerRequest.bodyToMono] providing a `bodyToMono()` variant * leveraging Kotlin reified type parameters. This extension is not subject to type * erasure and retains actual generic type arguments. - * + * * @author Sebastien Deleuze * @since 5.0 */ @@ -76,9 +78,25 @@ fun ServerRequest.bodyToFlow(clazz: KClass): Flow = /** * Non-nullable Coroutines variant of [ServerRequest.bodyToMono]. * + * ### Deprecation + * + * This method is deprecated in favor of [awaitReceive]. + * + * * It is difficult to reason about its exception handling, + * since it forces you to handle both types of errors: serialization and coroutines-related. + * * In the case of [IllegalArgumentException] it is indistinguishable if the exception came from coroutines + * or serialization unless checked explicit for `SerializationException` type. + * * It is unclear whether [IllegalArgumentException] can be thrown at all from coroutines-perspective + * as the receiver is a [Mono]. + * * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Deprecated in favor of awaitReceive.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.awaitReceive") +) suspend inline fun ServerRequest.awaitBody(): T = bodyToMono().awaitSingle() @@ -86,18 +104,47 @@ suspend inline fun ServerRequest.awaitBody(): T = * `KClass` non-nullable Coroutines variant of [ServerRequest.bodyToMono]. * Please consider `awaitBody` variant if possible. * + * ### Deprecation + * + * This method is deprecated in favor of [awaitReceive]. + * + * * It is difficult to reason about its exception handling, + * since it forces you to handle both types of errors: serialization and coroutines-related. + * * In the case of [IllegalArgumentException] it is indistinguishable if the exception came from coroutines + * or serialization unless checked explicit for `SerializationException` type. + * * It is unclear whether [IllegalArgumentException] can be thrown at all from coroutines-perspective + * as the receiver is a [Mono]. + * * @author Igor Manushin * @since 5.3 */ +@Deprecated( + message = "Deprecated in favor of awaitReceive.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.awaitReceive") +) suspend fun ServerRequest.awaitBody(clazz: KClass): T = bodyToMono(clazz.java).awaitSingle() /** * Nullable Coroutines variant of [ServerRequest.bodyToMono]. * + * ### Deprecation + * + * This method is deprecated because the conventions established in Kotlin mandate that an operation + * with the name `awaitBodyOrNull` returns `null` instead of throwing in case there is an error; + * however, this would also mean that this method would return `null` if there is a serialization error. + * This can be confusing to those who expect this function to validate if incoming body can be transformed into expected `T`. + * * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " + + "Please consider replacing it with awaitReceiveOrNull.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.awaitReceiveOrNull") +) @Suppress("DEPRECATION") suspend inline fun ServerRequest.awaitBodyOrNull(): T? = bodyToMono().awaitSingleOrNull() @@ -106,13 +153,61 @@ suspend inline fun ServerRequest.awaitBodyOrNull(): T? = * `KClass` nullable Coroutines variant of [ServerRequest.bodyToMono]. * Please consider `awaitBodyOrNull` variant if possible. * + * ### Deprecation + * + * This method is deprecated because the conventions established in Kotlin mandate that an operation + * with the name `awaitBodyOrNull` returns `null` instead of throwing in case there is an error; + * however, this would also mean that this method would return `null` if there is a serialization error. + * This can be confusing to those who expect this function to validate if incoming body can be transformed into expected `T`. + * * @author Igor Manushin * @since 5.3 */ +@Deprecated( + message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " + + "Please consider replacing it with awaitReceiveOrNull.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.awaitReceiveOrNull") +) @Suppress("DEPRECATION") suspend fun ServerRequest.awaitBodyOrNull(clazz: KClass): T? = bodyToMono(clazz.java).awaitSingleOrNull() +/** + * Receives the incoming body for this [request][ServerRequest] and transforms it to the requested `T` type. + * + * @author George Papadopoulos + * @since 6.0.11 + * @throws IllegalArgumentException when body cannot be transformed to the requested type. + */ +suspend inline fun ServerRequest.awaitReceive(): T = awaitReceiveNullable() + ?: throw ContentTransformationException(starProjectedType()) + +/** + * Receives the incoming body for this [request][ServerRequest] and transforms it to the requested `T` type. + * + * @author George Papadopoulos + * @since 6.0.11 + * @throws IllegalArgumentException when body cannot be transformed to the requested type. + */ +suspend inline fun ServerRequest.awaitReceiveNullable(): T? { + try { + return bodyToMono().awaitSingleOrNull() + } catch (e: IllegalArgumentException) { + throw ContentTransformationException(starProjectedType()) + } +} + +@PublishedApi +internal class ContentTransformationException( + type: KType +) : IllegalArgumentException("Cannot transform this request's body to $type") + +@PublishedApi +internal inline fun starProjectedType(): KType { + return T::class.starProjectedType +} + /** * Coroutines variant of [ServerRequest.formData]. *