Skip to content

Commit 58006f5

Browse files
committed
HV-1946 Add an empty path node for iterable/multivalued container elements
1 parent 46fe5e3 commit 58006f5

File tree

5 files changed

+71
-10
lines changed

5 files changed

+71
-10
lines changed

engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,7 @@ private void doValidate(Object value, String nodeName) {
749749
cascadedValueContext.setTypeParameter( cascadingMetaData.getDeclaredContainerClass(), cascadingMetaData.getDeclaredTypeParameterIndex() );
750750
}
751751

752-
if ( nodeName != null ) {
753-
cascadedTypeArgumentValueContext.appendTypeParameterNode( nodeName );
754-
}
752+
cascadedTypeArgumentValueContext.appendTypeParameterNode( nodeName );
755753

756754
validateCascadedContainerElementsInContext( value, validationContext, cascadedTypeArgumentValueContext, cascadingMetaData, validationOrder );
757755
}

engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ public NodeImpl addContainerElementNode(String nodeName) {
134134
return currentLeafNode;
135135
}
136136

137+
public boolean needToAddContainerElementNode(String nodeName) {
138+
// If the node name is not null that would mean that we need to add it,
139+
// but otherwise -- we may want to have an empty node
140+
// if a current node is some iterable/multivalued element (E.g. array/list/map etc.).
141+
// If we don't add it -- the path would be broken and would lead to a situation
142+
// where container elements would be pointing to a container element node itself
143+
// resulting in various node methods like `Node#getIndex()` producing incorrect results.
144+
// As an additional side effect of not adding a node it might lead to the path not being correctly copied.
145+
return nodeName != null || currentLeafNode.isIterable();
146+
}
147+
137148
public NodeImpl addParameterNode(String nodeName, int index) {
138149
requiresWriteableNodeList();
139150

engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContext.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,11 @@ public final void appendNode(ConstraintLocation location) {
102102
}
103103

104104
public final void appendTypeParameterNode(String nodeName) {
105-
PathImpl newPath = PathImpl.createCopy( propertyPath );
106-
newPath.addContainerElementNode( nodeName );
107-
propertyPath = newPath;
105+
if ( propertyPath.needToAddContainerElementNode( nodeName ) ) {
106+
PathImpl newPath = PathImpl.createCopy( propertyPath );
107+
newPath.addContainerElementNode( nodeName );
108+
propertyPath = newPath;
109+
}
108110
}
109111

110112
public final void markCurrentPropertyAsIterable() {

engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,7 @@ private void doValidate(Object value, String nodeName) {
226226
valueContext.setTypeParameter( containerClass, currentValueExtractionPathNode.getTypeParameterIndex() );
227227
}
228228

229-
if ( nodeName != null ) {
230-
valueContext.appendTypeParameterNode( nodeName );
231-
}
232-
229+
valueContext.appendTypeParameterNode( nodeName );
233230
valueContext.setCurrentValidatedValue( value );
234231

235232
if ( currentValueExtractionPathNode.hasNext() ) {

engine/src/test/java/org/hibernate/validator/test/internal/engine/path/NodeImplTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
import jakarta.validation.Validator;
4040
import jakarta.validation.constraints.Max;
4141
import jakarta.validation.constraints.NotNull;
42+
import jakarta.validation.constraints.Pattern;
4243
import jakarta.validation.constraints.Size;
4344
import jakarta.validation.valueextraction.ExtractedValue;
45+
import jakarta.validation.valueextraction.UnwrapByDefault;
4446
import jakarta.validation.valueextraction.ValueExtractor;
4547

4648
import org.hibernate.validator.path.ContainerElementNode;
@@ -505,6 +507,46 @@ public void testContainerElementNodeGetValueForNestedContainer() {
505507
assertFalse( nodeIterator.hasNext() );
506508
}
507509

510+
@Test
511+
@TestForIssue(jiraKey = "HV-1946")
512+
public void testIndexedContainerElementMultipleFailuresCorrectPath() {
513+
class People {
514+
@Pattern( regexp = "[a-z]+")
515+
Person[] people;
516+
517+
public People(Person... people) {
518+
this.people = people;
519+
}
520+
}
521+
522+
Validator validator = ValidatorUtil.getConfiguration()
523+
.addValueExtractor( new PersonArrayValueExtractor() )
524+
.buildValidatorFactory()
525+
.getValidator();
526+
527+
Set<ConstraintViolation<People>> constraintViolations = validator.validate(
528+
new People( new Person( "name1" ), new Person( "name2" ), new Person( "name3" ) )
529+
);
530+
531+
assertThat( constraintViolations ).containsOnlyViolations(
532+
violationOf( Pattern.class )
533+
.withPropertyPath( pathWith()
534+
.property( "people" )
535+
.containerElement( null, true, null, 0, Person[].class, null )
536+
),
537+
violationOf( Pattern.class )
538+
.withPropertyPath( pathWith()
539+
.property( "people" )
540+
.containerElement( null, true, null, 1, Person[].class, null )
541+
),
542+
violationOf( Pattern.class )
543+
.withPropertyPath( pathWith()
544+
.property( "people" )
545+
.containerElement( null, true, null, 2, Person[].class, null )
546+
)
547+
);
548+
}
549+
508550
private void assertConstraintViolationToOneValidation(Set<ConstraintViolation<AWithB>> constraintViolations) {
509551
assertThat( constraintViolations ).containsOnlyViolations(
510552
violationOf( NotNull.class )
@@ -752,4 +794,15 @@ public void extractValues(CustomContainer<?> originalValue, ValueExtractor.Value
752794
}
753795
}
754796
}
797+
798+
@UnwrapByDefault
799+
private static final class PersonArrayValueExtractor implements ValueExtractor<Person @ExtractedValue(type = String.class) []> {
800+
801+
@Override
802+
public void extractValues(Person[] originalValue, ValueExtractor.ValueReceiver receiver) {
803+
for ( int i = 0; i < originalValue.length; i++ ) {
804+
receiver.indexedValue( null, i, originalValue[i].name );
805+
}
806+
}
807+
}
755808
}

0 commit comments

Comments
 (0)