Conditional use of URI vars for data binding in WebFlux

This commit aligns Spring WebFlux with WebMvc in adding URI variables
conditionally if not already in the map. The idea is that data
binding is primarily through form data and query params, with URI
variables, and request headers (to be implemented next) also made
available but without shadowing existing values.

See gh-32676
This commit is contained in:
rstoyanchev 2024-06-04 15:40:38 +01:00
parent 398aae2b9a
commit 23160a43dd
2 changed files with 49 additions and 5 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -18,7 +18,6 @@ package org.springframework.web.reactive;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import reactor.core.publisher.Mono;
@ -29,6 +28,7 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.SmartValidator;
@ -209,10 +209,29 @@ public class BindingContext {
@Override
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
return super.getValuesToBind(exchange).doOnNext(map ->
map.putAll(exchange.<Map<String, String>>getAttributeOrDefault(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap())));
return super.getValuesToBind(exchange).doOnNext(map -> {
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(vars)) {
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
}
});
}
private static void addValueIfNotPresent(
Map<String, Object> map, String label, String name, @Nullable Object value) {
if (value != null) {
if (map.containsKey(name)) {
if (logger.isDebugEnabled()) {
logger.debug(label + " '" + name + "' overridden by request bind value.");
}
}
else {
map.put(name, value);
}
}
}
}

View File

@ -17,16 +17,20 @@
package org.springframework.web.reactive;
import java.lang.reflect.Method;
import java.util.Map;
import jakarta.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.server.MockServerWebExchange;
@ -64,6 +68,27 @@ class BindingContextTests {
assertThat(binder.getValidatorsToApply()).containsExactly(springValidator);
}
@Test
void uriVariablesAddedConditionally() {
MockServerHttpRequest request = MockServerHttpRequest.post("/path")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body("name=John&age=25");
MockServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("age", "26"));
TestBean testBean = new TestBean();
BindingContext bindingContext = new BindingContext(null);
WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, testBean, "testBean", null);
binder.bind(exchange).block();
assertThat(testBean.getName()).isEqualTo("John");
assertThat(testBean.getAge()).isEqualTo(25);
}
@SuppressWarnings("unused")
private void handleValidObject(@Valid Foo foo) {