diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ValidatorConstraintResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ValidatorConstraintResolver.java index 5c8efc6b..dc3b8af7 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ValidatorConstraintResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/constraints/ValidatorConstraintResolver.java @@ -17,7 +17,9 @@ package org.springframework.restdocs.constraints; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; import jakarta.validation.Validation; import jakarta.validation.Validator; @@ -38,40 +40,51 @@ */ public class ValidatorConstraintResolver implements ConstraintResolver { + private final Class[] groups; + private final Validator validator; /** * Creates a new {@code ValidatorConstraintResolver} that will use a {@link Validator} * in its default configuration to resolve constraints. - * + * @param groups the validation groups to consider when resolving constraints * @see Validation#buildDefaultValidatorFactory() * @see ValidatorFactory#getValidator() */ - public ValidatorConstraintResolver() { - this(Validation.buildDefaultValidatorFactory().getValidator()); + public ValidatorConstraintResolver(Class... groups) { + this(Validation.buildDefaultValidatorFactory().getValidator(), groups); } /** * Creates a new {@code ValidatorConstraintResolver} that will use the given * {@code Validator} to resolve constraints. * @param validator the validator + * @param groups the validation groups to consider when resolving constraints. */ - public ValidatorConstraintResolver(Validator validator) { + public ValidatorConstraintResolver(Validator validator, Class... groups) { this.validator = validator; + this.groups = groups; } @Override public List resolveForProperty(String property, Class clazz) { List constraints = new ArrayList<>(); + for (ConstraintDescriptor constraintDescriptor : getConstraintDescriptors(property, clazz)) { + constraints.add(new Constraint(constraintDescriptor.getAnnotation().annotationType().getName(), + constraintDescriptor.getAttributes())); + } + return constraints; + } + + private Set> getConstraintDescriptors(String property, Class clazz) { BeanDescriptor beanDescriptor = this.validator.getConstraintsForClass(clazz); PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty(property); if (propertyDescriptor != null) { - for (ConstraintDescriptor constraintDescriptor : propertyDescriptor.getConstraintDescriptors()) { - constraints.add(new Constraint(constraintDescriptor.getAnnotation().annotationType().getName(), - constraintDescriptor.getAttributes())); - } + return propertyDescriptor.findConstraints() + .unorderedAndMatchingGroups(this.groups) + .getConstraintDescriptors(); } - return constraints; + return Collections.emptySet(); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java index e5ad3f53..2754841b 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/constraints/ValidatorConstraintResolverTests.java @@ -31,6 +31,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; import jakarta.validation.constraints.Size; +import jakarta.validation.groups.Default; import org.assertj.core.api.Condition; import org.assertj.core.description.TextDescription; import org.hibernate.validator.constraints.CompositionType; @@ -46,18 +47,16 @@ */ class ValidatorConstraintResolverTests { - private final ValidatorConstraintResolver resolver = new ValidatorConstraintResolver(); - @Test void singleFieldConstraint() { - List constraints = this.resolver.resolveForProperty("single", ConstrainedFields.class); + List constraints = resolveForProperty("single", ConstrainedFields.class); assertThat(constraints).hasSize(1); assertThat(constraints.get(0).getName()).isEqualTo(NotNull.class.getName()); } @Test void multipleFieldConstraints() { - List constraints = this.resolver.resolveForProperty("multiple", ConstrainedFields.class); + List constraints = resolveForProperty("multiple", ConstrainedFields.class); assertThat(constraints).hasSize(2); assertThat(constraints.get(0)).is(constraint(NotNull.class)); assertThat(constraints.get(1)).is(constraint(Size.class).config("min", 8).config("max", 16)); @@ -65,14 +64,67 @@ void multipleFieldConstraints() { @Test void noFieldConstraints() { - List constraints = this.resolver.resolveForProperty("none", ConstrainedFields.class); + List constraints = resolveForProperty("none", ConstrainedFields.class); assertThat(constraints).hasSize(0); } @Test void compositeConstraint() { - List constraints = this.resolver.resolveForProperty("composite", ConstrainedFields.class); + List constraints = resolveForProperty("composite", ConstrainedFields.class); + assertThat(constraints).hasSize(1); + } + + @Test + void constraintsWithSpecificGroup() { + List constraints = resolveForProperty("street1", ConstrainedFields.class, BasicPostal.class); + assertThat(constraints).hasSize(1); + assertThat(constraints.get(0)).is(constraint(NotNull.class)); + } + + @Test + void constraintsWithSpecificGroupInheritance() { + List constraints = resolveForProperty("street1", ConstrainedFields.class, FullPostal.class); assertThat(constraints).hasSize(1); + assertThat(constraints.get(0)).is(constraint(NotNull.class)); + } + + @Test + void constraintsWithSpecificGroupInheritanceIncludingDefault() { + List constraints = resolveForProperty("single", ConstrainedFields.class, FullPostal.class); + assertThat(constraints).hasSize(1); + assertThat(constraints.get(0)).is(constraint(NotNull.class)); + } + + @Test + void constraintsWithNoMatchingGroup() { + List constraints = resolveForProperty("doorCode", ConstrainedFields.class, BasicPostal.class); + assertThat(constraints).hasSize(0); + } + + @Test + void constraintsWithMatchingGroup() { + List constraints = resolveForProperty("doorCode", ConstrainedFields.class, FullPostal.class); + assertThat(constraints).hasSize(1); + assertThat(constraints.get(0)).is(constraint(NotNull.class)); + } + + @Test + void constraintsWithMultipleGroups() { + List constraints = resolveForProperty("street1", ConstrainedFields.class, BasicPostal.class, + FullPostal.class); + assertThat(constraints).hasSize(1); + assertThat(constraints.get(0)).is(constraint(NotNull.class)); + } + + @Test + void constraintsWithDefaultGroup() { + List constraints = resolveForProperty("single", ConstrainedFields.class, Default.class); + assertThat(constraints).hasSize(1); + } + + private List resolveForProperty(String property, Class clazz, Class... groups) { + ValidatorConstraintResolver resolver = new ValidatorConstraintResolver(groups); + return resolver.resolveForProperty(property, clazz); } private ConstraintCondition constraint(final Class annotation) { @@ -94,6 +146,15 @@ private static final class ConstrainedFields { @CompositeConstraint private String composite; + @NotNull(groups = BasicPostal.class) + String street1; + + @NotNull(groups = BasicPostal.class) + String zipCode; + + @NotNull(groups = FullPostal.class) + String doorCode; + } @ConstraintComposition(CompositionType.OR) @@ -143,4 +204,12 @@ public boolean matches(Constraint constraint) { } + interface BasicPostal extends Default { + + } + + interface FullPostal extends BasicPostal { + + } + }