From 02d003127ffd39969e6de5b36bb091f3060904f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 15 Aug 2023 18:01:32 +0200 Subject: [PATCH] Move ServerWebExchange Kotlin extensions This commit moves ServerWebExchange Kotlin extensions where they belong: in the spring-web module with the org.springframework.web.server package, like ServerWebExchange itself. The extensions in the wrong location are deprecated and semi-automated migration to the new variants is made possible via @Deprecated + ReplaceWith(...). Some tests have been added as well. Closes gh-31046 --- .../web/server/ServerWebExchangeExtensions.kt | 69 ++++++++++++++ .../server/ServerWebExchangeExtensionsTest.kt | 89 +++++++++++++++++++ .../server/ServerWebExchangeExtensions.kt | 22 ++++- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 spring-web/src/main/kotlin/org/springframework/web/server/ServerWebExchangeExtensions.kt create mode 100644 spring-web/src/test/kotlin/org/springframework/web/server/ServerWebExchangeExtensionsTest.kt diff --git a/spring-web/src/main/kotlin/org/springframework/web/server/ServerWebExchangeExtensions.kt b/spring-web/src/main/kotlin/org/springframework/web/server/ServerWebExchangeExtensions.kt new file mode 100644 index 0000000000..06081a7c62 --- /dev/null +++ b/spring-web/src/main/kotlin/org/springframework/web/server/ServerWebExchangeExtensions.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.mono +import org.springframework.http.codec.multipart.Part +import org.springframework.util.MultiValueMap +import java.security.Principal + +/** + * Coroutines variant of [ServerWebExchange.getFormData]. + * + * @author Sebastien Deleuze + * @since 6.1 + */ +suspend fun ServerWebExchange.awaitFormData(): MultiValueMap = + this.formData.awaitSingle() + +/** + * Coroutines variant of [ServerWebExchange.getMultipartData]. + * + * @author Sebastien Deleuze + * @since 6.1 + */ +suspend fun ServerWebExchange.awaitMultipartData(): MultiValueMap = + this.multipartData.awaitSingle() + +/** + * Coroutines variant of [ServerWebExchange.getPrincipal]. + * + * @author Sebastien Deleuze + * @since 6.1 + */ +suspend fun ServerWebExchange.awaitPrincipal(): T = + this.getPrincipal().awaitSingle() + +/** + * Coroutines variant of [ServerWebExchange.getSession]. + * + * @author Sebastien Deleuze + * @since 6.1 + */ +suspend fun ServerWebExchange.awaitSession(): WebSession = + this.session.awaitSingle() + +/** + * Coroutines variant of [ServerWebExchange.Builder.principal]. + * + * @author Sebastien Deleuze + * @since 6.1 + */ +fun ServerWebExchange.Builder.principal(supplier: suspend () -> Principal): ServerWebExchange.Builder + = principal(mono(Dispatchers.Unconfined) { supplier.invoke() }) diff --git a/spring-web/src/test/kotlin/org/springframework/web/server/ServerWebExchangeExtensionsTest.kt b/spring-web/src/test/kotlin/org/springframework/web/server/ServerWebExchangeExtensionsTest.kt new file mode 100644 index 0000000000..ed7ef4b17c --- /dev/null +++ b/spring-web/src/test/kotlin/org/springframework/web/server/ServerWebExchangeExtensionsTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.http.codec.multipart.Part +import org.springframework.util.MultiValueMap +import reactor.core.publisher.Mono +import java.security.Principal + +/** + * Mock object based tests for [ServerWebExchange] Kotlin extensions + * + * @author Sebastien Deleuze + */ +class ServerWebExchangeExtensionsTest { + + @Test + fun `awaitFormData extension`() { + val exchange = mockk() + val multiMap = mockk>() + every { exchange.formData } returns Mono.just(multiMap) + runBlocking { + assertThat(exchange.awaitFormData()).isEqualTo(multiMap) + } + } + + @Test + fun `awaitMultipartData extension`() { + val exchange = mockk() + val multiMap = mockk>() + every { exchange.multipartData } returns Mono.just(multiMap) + runBlocking { + assertThat(exchange.awaitMultipartData()).isEqualTo(multiMap) + } + } + + @Test + fun `awaitPrincipal extension`() { + val exchange = mockk() + val principal = mockk() + every { exchange.getPrincipal() } returns Mono.just(principal) + runBlocking { + assertThat(exchange.awaitPrincipal()).isEqualTo(principal) + } + verify { exchange.getPrincipal() } + } + + @Test + fun `awaitSession extension`() { + val exchange = mockk() + val session = mockk() + every { exchange.session } returns Mono.just(session) + runBlocking { + assertThat(exchange.awaitSession()).isEqualTo(session) + } + } + + @Test + fun `principal builder extension`() { + val builder = mockk() + val principal = mockk() + every { builder.principal(any>()) } returns builder + runBlocking { + assertThat(builder.principal { principal }).isEqualTo(builder) + } + verify { builder.principal(any>()) } + } + +} diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/server/ServerWebExchangeExtensions.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/server/ServerWebExchangeExtensions.kt index 0c0dc2859c..adb93ac018 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/server/ServerWebExchangeExtensions.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/server/ServerWebExchangeExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,10 @@ import java.security.Principal * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Use the variant with the org.springframework.web.server package instead.", + ReplaceWith("awaitFormData()", "org.springframework.web.server.awaitFormData"), +) suspend fun ServerWebExchange.awaitFormData(): MultiValueMap = this.formData.awaitSingle() @@ -40,6 +44,10 @@ suspend fun ServerWebExchange.awaitFormData(): MultiValueMap = * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Use the variant with the org.springframework.web.server package instead.", + ReplaceWith("awaitMultipartData()", "org.springframework.web.server.awaitMultipartData"), +) suspend fun ServerWebExchange.awaitMultipartData(): MultiValueMap = this.multipartData.awaitSingle() @@ -49,6 +57,10 @@ suspend fun ServerWebExchange.awaitMultipartData(): MultiValueMap * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Use the variant with the org.springframework.web.server package instead.", + ReplaceWith("awaitPrincipal()", "org.springframework.web.server.awaitPrincipal"), +) suspend fun ServerWebExchange.awaitPrincipal(): T = this.getPrincipal().awaitSingle() @@ -58,6 +70,10 @@ suspend fun ServerWebExchange.awaitPrincipal(): T = * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Use the variant with the org.springframework.web.server package instead.", + ReplaceWith("awaitSession()", "org.springframework.web.server.awaitSession"), +) suspend fun ServerWebExchange.awaitSession(): WebSession = this.session.awaitSingle() @@ -67,5 +83,9 @@ suspend fun ServerWebExchange.awaitSession(): WebSession = * @author Sebastien Deleuze * @since 5.2 */ +@Deprecated( + message = "Use the variant with the org.springframework.web.server package instead.", + ReplaceWith("principal(supplier)", "org.springframework.web.server.principal"), +) fun ServerWebExchange.Builder.principal(supplier: suspend () -> Principal): ServerWebExchange.Builder = principal(mono(Dispatchers.Unconfined) { supplier.invoke() })