mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-09 00:29:04 +00:00
Use PropertyPath instead of utility methods
Prior to this commit, `DataBinder` and the property accessor hierarchy relied on `PropertyAccessorUtils` and several independent scanners for property paths. This commit migrates all of them to `PropertyPath`, so there is exactly one parser deciding what a well-formed property path is, used identically for policy checks and for actual navigation. This removes long standing protected methods like A`getPropertyAccessorForPropertyPathi` and `getFinalPath` from `bstractNestablePropertyAccessor`. The path is now parsed exactly once per public entry point, via the new `resolvePropertyPath`, which returns a `ResolvedProperty`. Then, property navigation walks the parsed segment list rather than re-scanning partial strings. Any subclass overriding the removed method will need to adapt. This removal initially conflicted with gh-37252 (maxNestedPathDepth support). The public configuration remains but the actual behavior changed; it is replaced with `PropertyPath.Options` which enforces limit right after parsing, before property navigation begins. The exception thrown changes from `InvalidPropertyException` to `InvalidPropertyPathException`. This commmit also reverts the `map[']` / `map["]` quoting behavior from gh-36765 as it is incompatible with the new grammar. See gh-37275
This commit is contained in:
+7
-4
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.InvalidPropertyException;
|
||||
import org.springframework.beans.InvalidPropertyPathException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.NotWritablePropertyException;
|
||||
import org.springframework.beans.NullValueInNestedPathException;
|
||||
@@ -162,7 +163,7 @@ class DataBinderFieldAccessTests {
|
||||
rod.setSpouse(kerry);
|
||||
kerry.setSpouse(rod);
|
||||
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
DataBinder binder = new DataBinder(rod, "rod");
|
||||
binder.setMaxNestedPathDepth(2);
|
||||
binder.initDirectFieldAccess();
|
||||
|
||||
@@ -173,9 +174,11 @@ class DataBinderFieldAccessTests {
|
||||
|
||||
MutablePropertyValues tooDeep = new MutablePropertyValues();
|
||||
tooDeep.add("spouse.spouse.spouse.name", "Joe");
|
||||
assertThatExceptionOfType(InvalidPropertyException.class)
|
||||
.isThrownBy(() -> binder.bind(tooDeep))
|
||||
.withMessageEndingWith("Nesting depth of property path exceeds the maximum of 2");
|
||||
binder.bind(tooDeep);
|
||||
assertThat(binder.getBindingResult().getFieldErrors("spouse.spouse.spouse.name")).singleElement().satisfies(error -> {
|
||||
assertThat(error.getCode()).isEqualTo(InvalidPropertyPathException.ERROR_CODE);
|
||||
assertThat(error.getRejectedValue()).isEqualTo("Joe");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.ConfigurablePropertyAccessor;
|
||||
import org.springframework.beans.InvalidPropertyException;
|
||||
import org.springframework.beans.InvalidPropertyPathException;
|
||||
import org.springframework.beans.MethodInvocationException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.NotWritablePropertyException;
|
||||
@@ -735,6 +736,56 @@ class DataBinderTests {
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favoriteColor");
|
||||
}
|
||||
|
||||
@Test // gh-37275
|
||||
void bindingWithMalformedFieldNameSurfacesAsFieldErrorRatherThanBeingSilentlyDropped() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "rod");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("map[key1]other", "value"); // trailing garbage after a key: not a well-formed property path
|
||||
|
||||
binder.bind(pvs);
|
||||
|
||||
assertThat(rod.getName()).as("well-formed fields still bind").isEqualTo("Rod");
|
||||
assertThat(binder.getBindingResult().getFieldErrors("map[key1]other")).singleElement().satisfies(error -> {
|
||||
assertThat(error.getCode()).isEqualTo(InvalidPropertyPathException.ERROR_CODE);
|
||||
assertThat(error.getRejectedValue()).isEqualTo("value");
|
||||
});
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(binder::close);
|
||||
}
|
||||
|
||||
@Test // gh-37275
|
||||
void bindingWithMalformedFieldNameIsNotSuppressedEvenWhenAllowedFieldsAreConfigured() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "rod");
|
||||
binder.setAllowedFields("name");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("map[key1]other", "value");
|
||||
|
||||
binder.bind(pvs);
|
||||
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).isEmpty();
|
||||
assertThat(binder.getBindingResult().getFieldErrors("map[key1]other")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test // gh-37275
|
||||
void isAllowedNeverThrowsForMalformedField() {
|
||||
class ExposingBinder extends DataBinder {
|
||||
ExposingBinder(Object target) {
|
||||
super(target, "target");
|
||||
}
|
||||
boolean callIsAllowed(String field) {
|
||||
return isAllowed(field);
|
||||
}
|
||||
}
|
||||
|
||||
ExposingBinder binder = new ExposingBinder(new TestBean());
|
||||
binder.setAllowedFields("name");
|
||||
|
||||
assertThat(binder.callIsAllowed("map[key1]other")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void bindingWithAllowedAndDisallowedFields() throws BindException {
|
||||
@@ -2080,7 +2131,7 @@ class DataBinderTests {
|
||||
rod.setSpouse(kerry);
|
||||
kerry.setSpouse(rod);
|
||||
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
DataBinder binder = new DataBinder(rod, "rod");
|
||||
binder.setMaxNestedPathDepth(2);
|
||||
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -2090,9 +2141,11 @@ class DataBinderTests {
|
||||
|
||||
MutablePropertyValues tooDeep = new MutablePropertyValues();
|
||||
tooDeep.add("spouse.spouse.spouse.name", "Joe");
|
||||
assertThatExceptionOfType(InvalidPropertyException.class)
|
||||
.isThrownBy(() -> binder.bind(tooDeep))
|
||||
.withMessageEndingWith("Nesting depth of property path exceeds the maximum of 2");
|
||||
binder.bind(tooDeep);
|
||||
assertThat(binder.getBindingResult().getFieldErrors("spouse.spouse.spouse.name")).singleElement().satisfies(error -> {
|
||||
assertThat(error.getCode()).isEqualTo(InvalidPropertyPathException.ERROR_CODE);
|
||||
assertThat(error.getRejectedValue()).isEqualTo("Joe");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // gh-37252
|
||||
|
||||
Reference in New Issue
Block a user