diff --git a/validation/src/main/java/io/micronaut/validation/validator/BeanValidationContext.java b/validation/src/main/java/io/micronaut/validation/validator/BeanValidationContext.java new file mode 100644 index 00000000..cd9c54a8 --- /dev/null +++ b/validation/src/main/java/io/micronaut/validation/validator/BeanValidationContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.validation.validator; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.beans.BeanProperty; +import java.util.Arrays; +import java.util.List; + +/** + * Context object to allow configuring validation behaviour. + */ +public interface BeanValidationContext { + /** + * The default validation context. + */ + BeanValidationContext DEFAULT = new DefaultBeanValidationContext(List.of()); + + /** + * The validation groups. + * @return The groups + */ + default List> groups() { + return List.of(); + } + + /** + * Create a validation context from the given groups. + * @param groups The groups + * @return The context + */ + static @NonNull BeanValidationContext fromGroups(Class... groups) { + return new DefaultBeanValidationContext( + groups != null ? Arrays.asList(groups) : List.of() + ); + } + + /** + * Hook to allow exclusion of properties during validation. + * @param object The object being validated + * @param property The property being validated. + * @return True if it should be validated. + * @param The object type + */ + default boolean isPropertyValidated( + @NonNull T object, @NonNull BeanProperty property) { + return true; + } +} diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultBeanValidationContext.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultBeanValidationContext.java new file mode 100644 index 00000000..17b8f896 --- /dev/null +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultBeanValidationContext.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.validation.validator; + +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.util.CollectionUtils; +import java.util.List; + +/** + * A default implementation of {@link BeanValidationContext}. + * @param groups The groups + */ +@Internal +record DefaultBeanValidationContext( + @NonNull + List> groups) + implements BeanValidationContext { + public DefaultBeanValidationContext { + if (CollectionUtils.isEmpty(groups)) { + groups = List.of(); + } + } +} diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintValidatorContext.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintValidatorContext.java index 41f6a22f..ab2f69cf 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintValidatorContext.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintValidatorContext.java @@ -22,7 +22,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.beans.BeanIntrospection; import io.micronaut.core.util.ArgumentUtils; -import io.micronaut.core.util.ArrayUtils; +import io.micronaut.core.util.CollectionUtils; import io.micronaut.validation.validator.constraints.ConstraintValidatorContext; import jakarta.validation.ClockProvider; import jakarta.validation.ConstraintViolation; @@ -64,6 +64,7 @@ final class DefaultConstraintValidatorContext implements ConstraintValidatorC boolean disableDefaultConstraintViolation; ConstraintDescriptor constraint; + private final BeanValidationContext validationContext; private final DefaultValidator defaultValidator; private final BeanIntrospection beanIntrospection; private final R rootBean; @@ -84,33 +85,41 @@ final class DefaultConstraintValidatorContext implements ConstraintValidatorC private Map, Class> convertedGroups = Collections.emptyMap(); private Set> currentViolations = new LinkedHashSet<>(); - DefaultConstraintValidatorContext(DefaultValidator defaultValidator, BeanIntrospection beanIntrospection, R rootBean, Class... groups) { - this(defaultValidator, beanIntrospection, rootBean, null, null, new ValidationPath(), new LinkedHashSet<>(), processGroups(groups), Collections.emptyList()); + DefaultConstraintValidatorContext(DefaultValidator defaultValidator, BeanIntrospection beanIntrospection, R rootBean, BeanValidationContext validationContext) { + this(defaultValidator, beanIntrospection, validationContext, rootBean, null, new ValidationPath(), new LinkedHashSet<>(), null, Collections.emptyList()); } private DefaultConstraintValidatorContext(DefaultValidator defaultValidator, BeanIntrospection beanIntrospection, - R rootBean, - Object[] executableParameterValues, + BeanValidationContext validationContext, R rootBean, Object executableReturnValue, ValidationPath path, Set> overallViolations, - List> definedGroups, + Object[] executableParameterValues, List> currentGroups) { + this.validationContext = validationContext; this.defaultValidator = defaultValidator; this.beanIntrospection = beanIntrospection; this.rootBean = rootBean; this.rootClass = beanIntrospection == null ? (rootBean == null ? null : (Class) rootBean.getClass()) : beanIntrospection.getBeanType(); this.executableParameterValues = executableParameterValues; this.executableReturnValue = executableReturnValue; - this.definedGroups = definedGroups; + this.definedGroups = processGroups(validationContext.groups()); this.currentGroups = currentGroups; this.currentPath = path != null ? path : new ValidationPath(); this.overallViolations = overallViolations; } - private static List> processGroups(Class[] definedGroups) { - if (ArrayUtils.isEmpty(definedGroups)) { + /** + * The validation context. + * @return The context + */ + public @NonNull BeanValidationContext getValidationContext() { + return validationContext; + } + + private static List> processGroups(List> definedGroups) { + if (CollectionUtils.isEmpty(definedGroups)) { return DEFAULT_GROUPS; } sanityCheckGroups(definedGroups); @@ -121,7 +130,7 @@ private static List> processGroups(Class[] definedGroups) { return Collections.unmodifiableList(groupList); } - private static void sanityCheckGroups(Class[] groups) { + private static void sanityCheckGroups(List> groups) { ArgumentUtils.requireNonNull("groups", groups); for (Class clazz : groups) { @@ -365,7 +374,7 @@ Optional getMessageTemplate() { } DefaultConstraintValidatorContext copy() { - return new DefaultConstraintValidatorContext<>(defaultValidator, beanIntrospection, rootBean, executableParameterValues, executableReturnValue, new ValidationPath(currentPath), new LinkedHashSet<>(overallViolations), definedGroups, currentGroups); + return new DefaultConstraintValidatorContext<>(defaultValidator, beanIntrospection, validationContext, rootBean, executableReturnValue, new ValidationPath(currentPath), new LinkedHashSet<>(overallViolations), executableParameterValues, currentGroups); } @Internal 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 b8594a6f..c208e08c 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java @@ -163,7 +163,17 @@ public Set> validate(@NonNull T object, @Nullable Cla if (introspection == null) { throw new ValidationException("Bean introspection not found for the class: " + object.getClass()); } - return validate(introspection, object, groups); + return validate(introspection, object, BeanValidationContext.fromGroups(groups)); + } + + @Override + public Set> validate(T object, BeanValidationContext validationContext) { + requireNonNull("object", object); + final BeanIntrospection introspection = getBeanIntrospection(object); + if (introspection == null) { + throw new ValidationException("Bean introspection not found for the class: " + object.getClass()); + } + return validate(introspection, object, validationContext); } /** @@ -180,12 +190,26 @@ public Set> validate(@NonNull T object, @Nullable Cla public Set> validate(@NonNull BeanIntrospection introspection, @NonNull T object, @NonNull Class... groups) { + return validate( + introspection, + object, + BeanValidationContext.fromGroups(groups) + ); + } + + @Override + public Set> validate(BeanIntrospection introspection, T object, BeanValidationContext context) { if (introspection == null) { throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected"); } - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, object, groups); - doValidate(context, introspection, object); - return context.getOverallViolations(); + DefaultConstraintValidatorContext constraintValidatorContext = new DefaultConstraintValidatorContext<>( + this, + introspection, + object, + context + ); + doValidate(constraintValidatorContext, introspection, object); + return constraintValidatorContext.getOverallViolations(); } @NonNull @@ -193,9 +217,18 @@ public Set> validate(@NonNull BeanIntrospection in public Set> validateProperty(@NonNull T object, @NonNull String propertyName, @NonNull Class... groups) { + return validateProperty( + object, + propertyName, + BeanValidationContext.fromGroups(groups) + ); + } + + @Override + public Set> validateProperty(T object, String propertyName, BeanValidationContext context) { requireNonNull("object", object); requireNonEmpty("propertyName", propertyName); - requireNonNull("groups", groups); + context = context != null ? context : BeanValidationContext.DEFAULT; final BeanIntrospection introspection = getBeanIntrospection(object); if (introspection == null) { throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected"); @@ -206,18 +239,18 @@ public Set> validateProperty(@NonNull T object, throw new IllegalArgumentException("Cannot find property with name: " + property); } - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, object, groups); + DefaultConstraintValidatorContext constraintValidationContext = new DefaultConstraintValidatorContext<>(this, introspection, object, context); - for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences(introspection)) { - try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) { - visitProperty(context, object, property.get(), false); + for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : constraintValidationContext.findGroupSequences(introspection)) { + try (DefaultConstraintValidatorContext.GroupsValidation validation = constraintValidationContext.withGroupSequence(groupSequence)) { + visitProperty(constraintValidationContext, object, property.get(), false); if (validation.isFailed()) { - return Collections.unmodifiableSet(context.getOverallViolations()); + return Collections.unmodifiableSet(constraintValidationContext.getOverallViolations()); } } } - return Collections.unmodifiableSet(context.getOverallViolations()); + return Collections.unmodifiableSet(constraintValidationContext.getOverallViolations()); } @NonNull @@ -226,9 +259,14 @@ public Set> validateValue(@NonNull Class beanType, @NonNull String propertyName, @Nullable Object value, @NonNull Class... groups) { + requireNonNull("groups", groups); + return validateValue(beanType, propertyName, value, BeanValidationContext.fromGroups(groups)); + } + + @Override + public Set> validateValue(Class beanType, String propertyName, Object value, BeanValidationContext context) { requireNonNull("beanType", beanType); requireNonEmpty("propertyName", propertyName); - requireNonNull("groups", groups); final BeanIntrospection introspection = getBeanIntrospection(beanType); if (introspection == null) { @@ -238,25 +276,25 @@ public Set> validateValue(@NonNull Class beanType, final BeanProperty beanProperty = introspection.getProperty(propertyName) .orElseThrow(() -> new IllegalArgumentException("No property [" + propertyName + "] found on type: " + beanType)); - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, null, groups); + DefaultConstraintValidatorContext constraintContext = new DefaultConstraintValidatorContext<>(this, introspection, null, context); - try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addPropertyNode(beanProperty.getName())) { - if (isNotReachable(context, null)) { - return Collections.unmodifiableSet(context.getOverallViolations()); + try (ValidationPath.ContextualPath ignored = constraintContext.getCurrentPath().addPropertyNode(beanProperty.getName())) { + if (isNotReachable(constraintContext, null)) { + return Collections.unmodifiableSet(constraintContext.getOverallViolations()); } - for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences(introspection)) { - try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) { + for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : constraintContext.findGroupSequences(introspection)) { + try (DefaultConstraintValidatorContext.GroupsValidation validation = constraintContext.withGroupSequence(groupSequence)) { - visitElement(context, null, beanProperty.asArgument(), beanProperty.asArgument().getAnnotationMetadata(), value, false); + visitElement(constraintContext, null, beanProperty.asArgument(), beanProperty.asArgument().getAnnotationMetadata(), value, false); if (validation.isFailed()) { - return Collections.unmodifiableSet(context.getOverallViolations()); + return Collections.unmodifiableSet(constraintContext.getOverallViolations()); } } } } - return Collections.unmodifiableSet(context.getOverallViolations()); + return Collections.unmodifiableSet(constraintContext.getOverallViolations()); } @NonNull @@ -267,7 +305,7 @@ public Set validatedAnnotatedElement(@NonNull AnnotatedElement element, return Collections.emptySet(); } - final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value); + final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value, BeanValidationContext.DEFAULT); Argument type = value != null ? Argument.of((Class) value.getClass(), element.getAnnotationMetadata()) : Argument.OBJECT_ARGUMENT; @@ -349,7 +387,7 @@ public Set> validateParameters(@NonNull T object, throw new IllegalArgumentException("The method parameter array must have exactly " + argLen + " elements."); } - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, groups); + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, BeanValidationContext.fromGroups(groups)); try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) { try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addMethodNode(method)) { AnnotationMetadata methodAnnotationMetadata = method.getAnnotationMetadata().getDeclaredMetadata(); @@ -378,7 +416,7 @@ public Set> validateParameters(@NonNull T object, Object[] parameters = argumentValues.stream().map(ArgumentValue::getValue).toArray(); - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, groups); + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, BeanValidationContext.fromGroups(groups)); try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameters)) { try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addMethodNode(method)) { AnnotationMetadata methodAnnotationMetadata = method.getAnnotationMetadata().getDeclaredMetadata(); @@ -431,7 +469,7 @@ public Set> validateReturnValue(@NonNull T object, requireNonNull("groups", groups); final ReturnType returnType = executableMethod.getReturnType(); - final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean, groups); + final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean, BeanValidationContext.fromGroups(groups)); try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableReturnValue(returnValue)) { try (ValidationPath.ContextualPath ignored2 = context.getCurrentPath().addMethodNode(executableMethod)) { @@ -516,7 +554,7 @@ public Set> validateConstructorParameters(Class context = (DefaultConstraintValidatorContext) new DefaultConstraintValidatorContext<>(this, null, beanType, groups); + DefaultConstraintValidatorContext context = (DefaultConstraintValidatorContext) new DefaultConstraintValidatorContext<>(this, null, beanType, BeanValidationContext.fromGroups(groups)); try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) { try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addConstructorNode(beanType.getSimpleName(), constructorArguments)) { validateParametersInternal(context, null, AnnotationMetadata.EMPTY_METADATA, parameterValues, constructorArguments, argLength); @@ -581,7 +619,7 @@ private Set> validatePublisherValue(Argu E value, boolean canCascade ) { - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, publisher, groups); + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, publisher, BeanValidationContext.fromGroups(groups)); try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addReturnValueNode()) { try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath().addContainerElementNode("", ValidationPath.DefaultContainerContext.ofIterableContainer(publisherArgument.getType()))) { @@ -603,7 +641,7 @@ public CompletionStage validateCompletionStage(@NonNull CompletionStage context = new DefaultConstraintValidatorContext<>(this, null, groups); + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, null, BeanValidationContext.fromGroups(groups)); for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) { try (DefaultConstraintValidatorContext.GroupsValidation ignore = context.withGroupSequence(groupSequence)) { return instrumentCompletionStage(context, completionStage, argument, true); @@ -626,7 +664,7 @@ public void validateBeanArgument(@NonNull BeanResolutionContext resolutionCo return; } - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value); + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value, BeanValidationContext.DEFAULT); final Class rootClass = injectionPoint.getDeclaringBean().getBeanType(); @@ -672,7 +710,7 @@ public void validateBean(@NonNull BeanResolutionContext resolutionContext, if (CollectionUtils.isEmpty(executableMethods)) { return; } - final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean); + final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean, BeanValidationContext.DEFAULT); final Class[] interfaces = beanType.getInterfaces(); String constructorName; if (ArrayUtils.isNotEmpty(interfaces)) { @@ -971,7 +1009,8 @@ private void visitProperty(DefaultConstraintValidatorContext context, } try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addPropertyNode(property.getName())) { - if (isNotReachable(context, object)) { + if (isNotReachable(context, object) || + !context.getValidationContext().isPropertyValidated(object, property)) { return; } try (DefaultConstraintValidatorContext.ValidationCloseable ignore = context.convertGroups(property.getAnnotationMetadata())) { diff --git a/validation/src/main/java/io/micronaut/validation/validator/Validator.java b/validation/src/main/java/io/micronaut/validation/validator/Validator.java index 8291e604..1ee03a5d 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/Validator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/Validator.java @@ -22,6 +22,7 @@ import jakarta.validation.Constraint; import jakarta.validation.ConstraintViolation; import jakarta.validation.Valid; +import jakarta.validation.ValidationException; import java.util.Set; /** @@ -45,6 +46,7 @@ public interface Validator extends jakarta.validation.Validator { /** * Overridden variation that returns a {@link ExecutableMethodValidator}. + * * @return The validator */ @Override @@ -52,36 +54,115 @@ public interface Validator extends jakarta.validation.Validator { @Override @NonNull Set> validate( - @NonNull T object, - Class... groups + @NonNull T object, + Class... groups + ); + + /** + * Validates all constraints on {@code object}. + * + * @param object object to validate + * @param validationContext The context + * @param the type of the object to validate + * @return constraint violations or an empty set if none + * @throws IllegalArgumentException if object is {@code null} + * or if {@code null} is passed to the varargs groups + * @throws ValidationException if a non recoverable error happens + * during the validation process + */ + @NonNull Set> validate( + @NonNull T object, + @NonNull BeanValidationContext validationContext ); /** * Validate the given introspection and object. + * * @param introspection The introspection - * @param object The object - * @param groups The groups - * @param The object type + * @param object The object + * @param groups The groups + * @param The object type * @return The constraint violations */ - @NonNull - Set> validate( - @NonNull BeanIntrospection introspection, - @NonNull T object, @Nullable Class... groups); + @NonNull Set> validate( + @NonNull BeanIntrospection introspection, + @NonNull T object, @Nullable Class... groups); + + /** + * Validate the given introspection and object. + * + * @param introspection The introspection + * @param object The object + * @param context The context + * @param The object type + * @return The constraint violations + */ + @NonNull Set> validate( + @NonNull BeanIntrospection introspection, + @NonNull T object, + @NonNull BeanValidationContext context); @Override @NonNull Set> validateProperty( - @NonNull T object, - @NonNull String propertyName, - Class... groups + @NonNull T object, + @NonNull String propertyName, + Class... groups + ); + + /** + * Validates all constraints placed on the property of {@code object} + * named {@code propertyName}. + * + * @param object object to validate + * @param propertyName property to validate (i.e. field and getter constraints) + * @param context The context + * @param the type of the object to validate + * @return constraint violations or an empty set if none + * @throws IllegalArgumentException if {@code object} is {@code null}, + * if {@code propertyName} is {@code null}, empty or not a valid object property + * or if {@code null} is passed to the varargs groups + * @throws ValidationException if a non recoverable error happens + * during the validation process + */ + @NonNull Set> validateProperty( + @NonNull T object, + @NonNull String propertyName, + BeanValidationContext context ); @Override @NonNull Set> validateValue( - @NonNull Class beanType, - @NonNull String propertyName, - @Nullable Object value, - Class... groups + @NonNull Class beanType, + @NonNull String propertyName, + @Nullable Object value, + Class... groups + ); + + /** + * Validates all constraints placed on the property named {@code propertyName} + * of the class {@code beanType} would the property value be {@code value}. + *

+ * {@link ConstraintViolation} objects return {@code null} for + * {@link ConstraintViolation#getRootBean()} and + * {@link ConstraintViolation#getLeafBean()}. + * + * @param beanType the bean type + * @param propertyName property to validate + * @param value property value to validate + * @param context The context + * @param the type of the object to validate + * @return constraint violations or an empty set if none + * @throws IllegalArgumentException if {@code beanType} is {@code null}, + * if {@code propertyName} is {@code null}, empty or not a valid object property + * or if {@code null} is passed to the varargs groups + * @throws ValidationException if a non recoverable error happens + * during the validation process + */ + @NonNull Set> validateValue( + @NonNull Class beanType, + @NonNull String propertyName, + @Nullable Object value, + BeanValidationContext context ); /** @@ -93,7 +174,7 @@ Set> validate( */ static @NonNull Validator getInstance() { return new DefaultValidator( - new DefaultValidatorConfiguration() + new DefaultValidatorConfiguration() ); } } diff --git a/validation/src/test/groovy/io/micronaut/validation/BeanValidationContextSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/BeanValidationContextSpec.groovy new file mode 100644 index 00000000..7087f06e --- /dev/null +++ b/validation/src/test/groovy/io/micronaut/validation/BeanValidationContextSpec.groovy @@ -0,0 +1,47 @@ +package io.micronaut.validation + +import io.micronaut.core.beans.BeanProperty +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.micronaut.validation.validator.BeanValidationContext +import io.micronaut.validation.validator.Validator +import jakarta.inject.Inject +import spock.lang.Specification + +@MicronautTest +class BeanValidationContextSpec + extends Specification { + + @Inject Validator validator + + void "test skip validation of properties with custom context"() { + given: + Pojo pojo = new Pojo(email: "938r79l", name:"") + + when: + def violations = validator.validate( + pojo, + new BeanValidationContext() { + def boolean isPropertyValidated(Object object, BeanProperty property) { + return property.name == 'email' + } + } + ) + + then: + violations.size() == 1 + } + + void "test don't skip validation of properties with default context"() { + given: + Pojo pojo = new Pojo(email: "938r79l", name:"") + + when: + def violations = validator.validate( + pojo, + BeanValidationContext.DEFAULT + ) + + then: + violations.size() == 2 + } +}