jamesfredley commented on code in PR #16624:
URL: https://github.com/apache/grails-core/pull/16624#discussion_r4417434024


##########
grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/validation/jakarta/GormValidatorAdapter.groovy:
##########
@@ -60,6 +67,58 @@ class GormValidatorAdapter extends SpringValidatorAdapter 
implements CascadingVa
         }
     }
 
+    /**
+     * Validates the object in the given groups, or in the default group when 
there are none
+     *
+     * @param obj The object to validate
+     * @param errors The errors to report the violations to
+     * @param cascade Whether validation cascades to the persistent 
associations of the entity through the
+     * properties marked {@code @Valid}; it cascades through the other 
properties marked {@code @Valid} either way
+     * @param groups The groups to validate
+     * @since 8.0.1
+     */
+    void validate(Object obj, Errors errors, boolean cascade, Class<?>[] 
groups) {
+        CASCADE_VALIDATION.set(cascade)
+        try {
+            validate(obj, errors, (Object[]) groups)
+        } finally {
+            CASCADE_VALIDATION.remove()
+        }
+    }
+
+    /**
+     * Validates the object in the given groups as {@link #validate(Object, 
Errors, boolean, Class[])} does,
+     * reporting only the violations of the constraints declared in one of the 
reported groups. The provider
+     * validates a group together with the groups it extends; when validation 
cascades from an object validated in
+     * a group whose extended groups a {@code @ConvertGroup} converts, the 
associated object is validated in the
+     * group alone, so the violations of the converted groups are left out.
+     *
+     * @param obj The object to validate
+     * @param errors The errors to report the violations to
+     * @param cascade Whether validation cascades to the persistent 
associations of the entity
+     * @param groups The groups to validate
+     * @param reportedGroups The groups whose violations to report, null to 
report every violation
+     * @since 8.0.1
+     */
+    void validate(Object obj, Errors errors, boolean cascade, Class<?>[] 
groups, Set<Class<?>> reportedGroups) {
+        if (reportedGroups == null) {
+            validate(obj, errors, cascade, groups)
+            return
+        }
+        CASCADE_VALIDATION.set(cascade)
+        try {
+            Set<ConstraintViolation<Object>> reported = new LinkedHashSet<>()
+            for (ConstraintViolation<Object> violation : 
thisValidator.validate(obj, groups)) {
+                if 
(!Collections.disjoint(violation.constraintDescriptor.groups, reportedGroups)) {
+                    reported.add(violation)

Review Comment:
   This keeps a violation only when its groups intersect the persistent 
entity's reported groups. A transient `@Valid` child can convert `Extended` to 
`DetailsChecks`. Those violations are real and then discarded, so the parent is 
accepted with invalid details. Filter each cascaded bean with the groups in 
effect for that bean.



##########
grails-datamapping-validation/src/main/groovy/grails/gorm/validation/PersistentEntityValidator.groovy:
##########
@@ -232,63 +427,37 @@ class PersistentEntityValidator implements 
CascadingValidator, ConstrainedEntity
         MappingContext mappingContext = associatedEntity.getMappingContext()
         EntityReflector associatedReflector = 
mappingContext.getEntityReflector(associatedEntity)
 
-        Association otherSide = null
-        if (association.isBidirectional()) {
-            otherSide = association.getInverseSide()
-        }
-
-        Map associatedConstrainedProperties
+        PersistentEntityValidator associatedValidator = null
+        Map<String, ConstrainedProperty> associatedConstrainedProperties = 
Collections.<String, ConstrainedProperty>emptyMap()
 
         def validator = mappingContext.getEntityValidator(associatedEntity)
         if (validator instanceof PersistentEntityValidator) {
-            associatedConstrainedProperties = ((PersistentEntityValidator) 
validator).getConstrainedProperties()
-        }
-        else {
-            associatedConstrainedProperties = Collections.<String, 
ConstrainedProperty>emptyMap()
+            associatedValidator = (PersistentEntityValidator) validator
+            associatedConstrainedProperties = 
associatedValidator.getValidatedConstrainedProperties(associatedAllHints)
         }
 
-        // Invoke any beforeValidate callbacks on the associated object before 
validating
-        validateHelper.invokeBeforeValidate(associatedObject, 
associatedConstrainedProperties.keySet() as List<String>)
-
-        List<PersistentProperty> associatedPersistentProperties = 
associatedEntity.getPersistentProperties()
         String nestedPath = errors.getNestedPath()
         try {
             errors.setNestedPath(buildNestedPath(nestedPath, propertyName, 
indexOrKey))
+            CASCADE.set(new Cascade(associatedHints, associatedAllHints, 
association))
 
-            for (PersistentProperty associatedPersistentProperty : 
associatedPersistentProperties) {
-                if (association.isEmbedded() && 
EMBEDDED_EXCLUDES.contains(associatedPersistentProperty.getName())) {
-                    continue
-                }
-
-                String associatedPropertyName = 
associatedPersistentProperty.getName()
-                if 
(associatedConstrainedProperties.containsKey(associatedPropertyName)) {
-                    ConstrainedProperty associatedConstrainedProperty = 
associatedConstrainedProperties.get(associatedPropertyName)
-                    validatePropertyWithConstraint(associatedObject, 
errors.getNestedPath() + associatedPropertyName, associatedReflector, errors, 
associatedConstrainedProperty, associatedPersistentProperty)
-                }
-
-                // Don't continue cascade if the the other side is equal to 
avoid stack overflow
-                if (associatedPersistentProperty == otherSide) {
-                    continue
-                }
-
-                if (associatedPersistentProperty instanceof Association) {
-                    if (association.isBidirectional() && 
associatedPersistentProperty == association.inverseSide) {
-                        // If this property is the inverse side of the 
currently processed association then
-                        // we don't want to process it
-                        continue
-                    }
+            // the constraints do not depend on the hints of a step, so they 
are validated once for all of them
+            if (validatedObjects.add(new 
ValidatedConstraints(associatedObject, associatedAllHints))) {
+                // Invoke any beforeValidate callbacks on the associated 
object before validating
+                validateHelper.invokeBeforeValidate(associatedObject, 
associatedConstrainedProperties.keySet() as List<String>)
+                validateConstraints(associatedObject, associatedEntity, 
errors, associatedReflector, associatedConstrainedProperties, association, 
false)
+            }
 
-                    cascadeToAssociativeProperty(
-                        associatedObject,
-                        errors,
-                        associatedReflector,
-                        (Association) associatedPersistentProperty,
-                        validatedObjects)
-                }
+            if (associatedValidator != null) {
+                
associatedValidator.validateAssociationsAndBeyond(associatedObject, errors, 
true, associatedHints, validatedObjects)

Review Comment:
   This passes the associated object through without the unwrap the root path 
does. An initialized proxy whose target has a valid field-annotated `label` is 
then read by Bean Validation as the proxy, so `NotBlank` fails with a null 
rejected value. Unwrap an initialized association before cascading, the same 
way the root object is unwrapped.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to