Add support for Bean Validation's constraint groups#940
Add support for Bean Validation's constraint groups#940nosan wants to merge 1 commit intospring-projects:mainfrom
Conversation
a232a20 to
f6319bf
Compare
|
@wilkinsona |
|
I have very little time for REST Docs at the moment but I shouldn't have let this sit for so long without at least acknowledging it. Sorry. I'll try to make some time to look at it properly in the next few days. |
|
No pressure - take your time. I just wanted to check if it makes sense to proceed, since there's a merge conflict, and understand what to do with the pull request. |
|
I've finally found some time to review this. Thanks for the PR, @nosan, and for your patience. I consider groups to be a concept that's specific to bean validation. As such, I'd prefer to minimise how many classes know about that concept. If I were to do that building on what REST Docs offers today, I'd use a custom sub-class of public class ValidatorGroupConstraintResolver extends ValidatorConstraintResolver {
private final Class<?> group;
public ValidatorGroupConstraintResolver(Class<?> group) {
this.group = group;
}
@Override
public List<Constraint> resolveForProperty(String property, Class<?> clazz) {
return super.resolveForProperty(property, clazz).stream()
.filter((constraint) -> isMember(constraint, this.group))
.toList();
}
private boolean isMember(Constraint constraint, Class<?> group) {
for (Class<?> candidate : (Class<?>[]) constraint.getConfiguration().get("groups")) {
if (group.isAssignableFrom(candidate)) {
return true;
}
}
return false;
}
}I think it might be worth REST Docs making this a bit easier as, for example, dealing with the inheritance of the groups is somewhat subtle (I think I've got that right by using |
cf5064e to
31c887f
Compare
Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
|
Thanks, @wilkinsona I think the group logic is not as simple as we thought. Fortunately for us, Bean Validation provides an API to retrieve constraints for the given groups. I'm not sure that passing groups via the constructor is a good option here. While it works, it means that I need to create a new As an alternative to using the constructor with less pain, perhaps the following method could be introduced: public ValidatorConstraintResolver withGroups(Class<?>... groups) {
return new ValidatorConstraintResolver(this.validator, groups);
}The new instance would then take the groups into account. This would allow people to reuse the same The second observation is an inconsistency between how Bean Validation performs validation and the constraints that REST Docs exposes. When REST Docs, however, currently returns all constraints. It's unclear whether this behavior is intentional. Here is a small test case to reproduce the issue: @Test
void constraintsWithDefaultGroup() {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
assertThat(validator.validate(new ConstrainedFields())).isEmpty();
List<Constraint> constraints = new ValidatorConstraintResolver(validator).resolveForProperty("doorCode", ConstrainedFields.class);
// Expecting empty but was: ...
assertThat(constraints).isEmpty();
}
private static final class ConstrainedFields {
@NotNull(groups = Serializable.class)
String doorCode;
}As I understand it, the main entry point for the end user is It would probably end up looking like this: new ConstraintDescriptions(UserInput.class, new ValidatorConstraintResolver(MyGroup.class)) .descriptionsForProperty("foo");If ValidatorConstraintResolver validatorConstraintResolver = new ValidatorConstraintResolver();
new ConstraintDescriptions( UserInput.class, validatorConstraintResolver.withGroups(MyGroup.class))
.descriptionsForProperty("foo");This also doesn't look very clean, as I would need to create a new I think introducing a
What do you think? I've updated the PR with the requested changes. |
|
Thanks, @nosan. Plenty of food for thought there.
It's too long ago need for me to remember the intent with any degree of certainty. Looking at the code and tests, I suspect that I just didn't consider groups at all and the current behaviour wasn't intended.
I wonder if we should be making some more assumptions about how validation is triggered, certainly in the golden-path case. If we assume the use of Framework's |
I think the @PatchMapping
Person update(@Validated(UpdateGroup.class) Person p) {}
@PostMapping
Person create(@Validated(CreateGroup.class) Person p) {}In this case, the Person class itself does not have a However, for #1026, it could be possible. It is interesting to understand how this works when I have |
Wishful thinking on my part. Your point is particularly true with groups where different usages will require different validation and
The method-level annotation wins. From the
|
gh-887
ConstraintDescriptionscan be extended with additional methods withgroupsas an argument.P.S. docs also need to be updated.