diff --git a/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java b/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java index 3a197bcd..178b81d6 100644 --- a/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java +++ b/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java @@ -106,7 +106,7 @@ protected String buildMessage(ConstraintViolation violation) { } message.append(']'); } - if (node.getKind() != ElementKind.CONTAINER_ELEMENT) { + if (node.getKind() != ElementKind.CONTAINER_ELEMENT && node.getName() != null) { if (!firstNode) { message.append('.'); } diff --git a/validation/src/test/groovy/io/micronaut/validation/Pojo.java b/validation/src/test/groovy/io/micronaut/validation/Pojo.java index 9d5c874f..50e8f326 100644 --- a/validation/src/test/groovy/io/micronaut/validation/Pojo.java +++ b/validation/src/test/groovy/io/micronaut/validation/Pojo.java @@ -20,6 +20,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +@ValidPojo @Introspected public class Pojo { diff --git a/validation/src/test/groovy/io/micronaut/validation/PojoValidator.java b/validation/src/test/groovy/io/micronaut/validation/PojoValidator.java new file mode 100644 index 00000000..3000e0d4 --- /dev/null +++ b/validation/src/test/groovy/io/micronaut/validation/PojoValidator.java @@ -0,0 +1,25 @@ +package io.micronaut.validation; + +import java.util.Objects; +import jakarta.inject.Singleton; + +import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.validation.validator.constraints.ConstraintValidator; +import io.micronaut.validation.validator.constraints.ConstraintValidatorContext; + +@Singleton +@Introspected +public class PojoValidator implements ConstraintValidator { + + @Override + public boolean isValid(Pojo pojo, AnnotationValue annotationMetadata, + ConstraintValidatorContext context) { + if (Objects.equals(pojo.getEmail(), pojo.getName())) { + context.messageTemplate("Email and Name can not be identical"); + return false; + } + + return true; + } +} diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidPojo.java b/validation/src/test/groovy/io/micronaut/validation/ValidPojo.java new file mode 100644 index 00000000..690013c5 --- /dev/null +++ b/validation/src/test/groovy/io/micronaut/validation/ValidPojo.java @@ -0,0 +1,19 @@ +package io.micronaut.validation; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import jakarta.validation.Constraint; + +/** + * Simple annotation to validate behavior when adding validation annotation at class level + */ +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@Constraint(validatedBy = {PojoValidator.class}) +public @interface ValidPojo { +} diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy index f1efc7b0..cdef4fc5 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy @@ -212,6 +212,36 @@ class ValidatedSpec extends Specification { server.close() } + def "test validated controller validates @Valid classes with validation annotation set on class"() { + given: + ApplicationContext context = ApplicationContext.run([ + 'spec.name': getClass().simpleName + ]) + EmbeddedServer embeddedServer = context.getBean(EmbeddedServer).start() + HttpClient client = context.createBean(HttpClient, embeddedServer.getURL()) + EmbeddedServer server = ApplicationContext.run(EmbeddedServer) + + when: + HttpResponse response = client.toBlocking().exchange( + HttpRequest.POST("/validated/pojo", '{"email":"test@example.com","name":"test@example.com"}') + .contentType(MediaType.APPLICATION_JSON_TYPE), + String + ) + + then: + def e = thrown(HttpClientResponseException) + e.response.code() == HttpStatus.BAD_REQUEST.code + + when: + def result = new JsonSlurper().parseText((String) e.response.getBody().get()) + + then: + result._embedded.errors[0].message == 'pojo: Email and Name can not be identical' + + cleanup: + server.close() + } + def "test validated controller validates @Valid classes with standard embedded errors"() { given: ApplicationContext context = ApplicationContext.run([ diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy index c50f0846..283f3f04 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy @@ -108,7 +108,7 @@ class PojoValidatorSpec extends Specification { then: ex = thrown(ConstraintViolationException) - ex.constraintViolations.size() == 1 + ex.constraintViolations.size() == 2 } void "test don't cascade to iterable without @Valid"() {