Browse Source
Support for RequestBody arguments. List supported arguments on HttpExchange. Improve null handling. See gh-28386pull/28411/head
10 changed files with 330 additions and 26 deletions
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.service.invoker; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.ParameterizedTypeReference; |
||||
import org.springframework.core.ReactiveAdapter; |
||||
import org.springframework.core.ReactiveAdapterRegistry; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
||||
|
||||
/** |
||||
* {@link HttpServiceArgumentResolver} for {@link RequestBody @RequestBody} |
||||
* annotated arguments. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 6.0 |
||||
*/ |
||||
public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver { |
||||
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry; |
||||
|
||||
|
||||
public RequestBodyArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) { |
||||
Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required"); |
||||
this.reactiveAdapterRegistry = reactiveAdapterRegistry; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean resolve( |
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) { |
||||
|
||||
RequestBody annot = parameter.getParameterAnnotation(RequestBody.class); |
||||
if (annot == null) { |
||||
return false; |
||||
} |
||||
|
||||
if (argument != null) { |
||||
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType()); |
||||
if (reactiveAdapter != null) { |
||||
setBody(argument, parameter, reactiveAdapter, requestValues); |
||||
} |
||||
else { |
||||
requestValues.setBodyValue(argument); |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private <E> void setBody( |
||||
Object argument, MethodParameter parameter, ReactiveAdapter reactiveAdapter, |
||||
HttpRequestValues.Builder requestValues) { |
||||
|
||||
String message = "Async type for @RequestBody should produce value(s)"; |
||||
Assert.isTrue(!reactiveAdapter.isNoValue(), message); |
||||
|
||||
parameter = parameter.nested(); |
||||
Class<?> elementClass = parameter.getNestedParameterType(); |
||||
Assert.isTrue(elementClass != Void.class, message); |
||||
ParameterizedTypeReference<E> typeRef = ParameterizedTypeReference.forType(parameter.getNestedGenericParameterType()); |
||||
Publisher<E> publisher = reactiveAdapter.toPublisher(argument); |
||||
|
||||
requestValues.setBody(publisher, typeRef); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,141 @@
@@ -0,0 +1,141 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.service.invoker; |
||||
|
||||
import io.reactivex.rxjava3.core.Completable; |
||||
import io.reactivex.rxjava3.core.Single; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.ParameterizedTypeReference; |
||||
import org.springframework.web.bind.annotation.RequestBody; |
||||
import org.springframework.web.service.annotation.GetExchange; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link RequestBodyArgumentResolver}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class RequestBodyArgumentResolverTests { |
||||
|
||||
private final TestHttpClientAdapter client = new TestHttpClientAdapter(); |
||||
|
||||
private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); |
||||
|
||||
|
||||
@Test |
||||
void stringBody() { |
||||
String body = "bodyValue"; |
||||
this.service.execute(body); |
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isEqualTo(body); |
||||
assertThat(getRequestValues().getBody()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void monoBody() { |
||||
Mono<String> bodyMono = Mono.just("bodyValue"); |
||||
this.service.executeMono(bodyMono); |
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull(); |
||||
assertThat(getRequestValues().getBody()).isSameAs(bodyMono); |
||||
assertThat(getRequestValues().getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {}); |
||||
} |
||||
|
||||
@Test |
||||
void singleBody() { |
||||
String bodyValue = "bodyValue"; |
||||
this.service.executeSingle(Single.just(bodyValue)); |
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull(); |
||||
assertThat(getRequestValues().getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {}); |
||||
|
||||
Publisher<?> body = getRequestValues().getBody(); |
||||
assertThat(body).isNotNull(); |
||||
assertThat(((Mono<String>) body).block()).isEqualTo(bodyValue); |
||||
} |
||||
|
||||
@Test |
||||
void monoVoid() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.service.executeMonoVoid(Mono.empty())) |
||||
.withMessage("Async type for @RequestBody should produce value(s)"); |
||||
} |
||||
|
||||
@Test |
||||
void completable() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.service.executeCompletable(Completable.complete())) |
||||
.withMessage("Async type for @RequestBody should produce value(s)"); |
||||
} |
||||
|
||||
@Test |
||||
void notRequestBody() { |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> this.service.executeNotRequestBody("value")) |
||||
.withMessage("Could not resolve parameter [0] in " + |
||||
"public abstract void org.springframework.web.service.invoker." + |
||||
"RequestBodyArgumentResolverTests$Service.executeNotRequestBody(java.lang.String): " + |
||||
"No suitable resolver"); |
||||
} |
||||
|
||||
@Test |
||||
void ignoreNull() { |
||||
this.service.execute(null); |
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull(); |
||||
assertThat(getRequestValues().getBody()).isNull(); |
||||
|
||||
this.service.executeMono(null); |
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull(); |
||||
assertThat(getRequestValues().getBody()).isNull(); |
||||
} |
||||
|
||||
private HttpRequestValues getRequestValues() { |
||||
return this.client.getRequestValues(); |
||||
} |
||||
|
||||
|
||||
private interface Service { |
||||
|
||||
@GetExchange |
||||
void execute(@RequestBody String body); |
||||
|
||||
@GetExchange |
||||
void executeMono(@RequestBody Mono<String> body); |
||||
|
||||
@GetExchange |
||||
void executeSingle(@RequestBody Single<String> body); |
||||
|
||||
@GetExchange |
||||
void executeMonoVoid(@RequestBody Mono<Void> body); |
||||
|
||||
@GetExchange |
||||
void executeCompletable(@RequestBody Completable body); |
||||
|
||||
@GetExchange |
||||
void executeNotRequestBody(String body); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue