diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java index e074f241..1f19bf21 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java @@ -1025,7 +1025,6 @@ private boolean doesHaveValidatedTypeParameters( ) { if (!context.elementRequireCascadeValidation.containsKey(annotatedElement)) { context.elementRequireCascadeValidation.put(annotatedElement, false); - AnnotationMetadata annotationMetadata = annotatedElement.getAnnotationMetadata(); Argument annotatedElementAsArgument = null; if (annotatedElement instanceof Argument) { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy index e49a05af..aafc3873 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy @@ -246,7 +246,7 @@ class ValidatorSpec extends Specification { void "validate property argument - with iterable constraints"() { given: var e = new ValidatorSpecClasses.Email(["me@oracle.com", "", "me2@oracle.com"]) - var violations = validator.validate(e); + var violations = validator.validate(e) violations = violations.sort{it->it.getPropertyPath().toString()} expect: @@ -777,6 +777,33 @@ class ValidatorSpec extends Specification { constraintViolations[0].toString() == 'DefaultConstraintViolation{rootBean=class io.micronaut.validation.validator.$BookService$Definition$Intercepted, invalidValue=50, path=saveBook.pages}' constraintViolations[1].toString() == 'DefaultConstraintViolation{rootBean=class io.micronaut.validation.validator.$BookService$Definition$Intercepted, invalidValue=, path=saveBook.title}' } + + void "test cascade to container"() { + given: + def salad = new ValidatorSpecClasses.Salad([ + new ValidatorSpecClasses.Ingredient("carrot"), + new ValidatorSpecClasses.Ingredient("") + ]) + def violations = validator.validate(salad) + + expect: + violations.size() == 1 + violations[0].invalidValue == "" + } + + void "test cascade to container with setter"() { + given: + def salad = new ValidatorSpecClasses.SaladWithSetter() + salad.ingredients = [ + new ValidatorSpecClasses.Ingredient("carrot"), + new ValidatorSpecClasses.Ingredient("") + ] + def violations = validator.validate(salad) + + expect: + violations.size() == 1 + violations[0].invalidValue == "" + } } @Introspected diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java index dc0b706b..74de268d 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java @@ -114,9 +114,9 @@ public Set getBooks() { // test validate property argument cascade @Introspected public static class Email { - final private @Size(max=2) List<@NotBlank String> recoveryEmails; + private @Size(max=2) List<@NotBlank String> recoveryEmails; - public Email(@Size(max=2) List recoveryEmails) { + public Email(List recoveryEmails) { this.recoveryEmails = recoveryEmails; } @@ -290,4 +290,36 @@ enum AuthorState { PUBLISHED, DRAFT } + + // test cascade to container + @Introspected + public static class Salad { + List<@Valid Ingredient> ingredients; + + public Salad(List ingredients) { + this.ingredients = ingredients; + } + + public List getIngredients() { + return ingredients; + } + } + + @Introspected + public static class SaladWithSetter { + List<@Valid Ingredient> ingredients; + + public List getIngredients() { + return ingredients; + } + + public void setIngredients(List ingredients) { + this.ingredients = ingredients; + } + } + + @Introspected + public record Ingredient( + @NotBlank String name + ) {} } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy index 8a548c0c..7d698c3c 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy @@ -1,10 +1,14 @@ package io.micronaut.validation.validator.pojo import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Executable import io.micronaut.context.exceptions.BeanInstantiationException +import io.micronaut.validation.Pojo +import jakarta.inject.Singleton import spock.lang.Specification import javax.validation.ConstraintViolationException +import javax.validation.Valid class PojoConfigurationPropertiesSpec extends Specification { @@ -15,6 +19,20 @@ class PojoConfigurationPropertiesSpec extends Specification { ] ]) + void "test @Valid on config props property manual"() { + when: + Pojo pojo = new Pojo() + pojo.name = "" + PojoConfigProps configProps = new PojoConfigProps([pojo]) +// configProps.pojos = [pojo] + + context.getBean(PojoConfigPropsValidator).validateConfigProps(configProps) + + then: + def ex = thrown(ConstraintViolationException) + ex.message.contains("must not be blank") + } + void "test @Valid on config props property"() { when: context.getBean(PojoConfigProps) @@ -24,3 +42,9 @@ class PojoConfigurationPropertiesSpec extends Specification { ex.message.contains("List of constraint violations:[\n\tpojos[0].name - must not be blank\n]") } } + +@Singleton +class PojoConfigPropsValidator { + @Executable + void validateConfigProps(@Valid PojoConfigProps configProps) {} +}