spring-projects / spring-restdocs

Test-driven documentation for RESTful services
https://spring.io/projects/spring-restdocs
Apache License 2.0
1.16k stars 736 forks source link

Add support for Bean Validation's constraint groups #887

Open AugustoRavazoli opened 1 year ago

AugustoRavazoli commented 1 year ago

The docs gives an example of documenting constraints:

public void example() {
    ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class);
    List<String> descriptions = userConstraints.descriptionsForProperty("name");
}

static class UserInput {

    @NotNull
    @Size(min = 1)
    String name;

    @NotNull
    @Size(min = 8)
    String password;

}

This way, you get all constraints descriptions for one property, but when using validation groups, how to get only the constraints descriptions for the given group? For example:

public void example() {
    ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class);
        List<String> descriptions = userConstraints.descriptionsForProperty("email").groups(OnCreate.class);
}

static class UserInput {

    @NotNull
    String name;

        @NotNull(groups = OnCreate.class)
        @Null(groups = OnEdit.class)
        String email;
}
wilkinsona commented 1 year ago

There's no support for constraint groups at the moment.

AugustoRavazoli commented 1 year ago

After reading the source code of the library and some tests, I found a possible solution using a custom ResourceBundleConstraintDescriptionResolver:

public class ConstrainedFields {

    private final ConstraintDescriptions constraints;
    private final List<Class<?>> groups;  // Constraints groups to exclude

    public ConstrainedFields(Class<?> clazz) {
      groups = new ArrayList<>();
      constraints = new ConstraintDescriptions(clazz, resolver(groups));
    }

    public FieldDescriptor pathExcludingGroups(String path, Class<?>... excludedGroups) {
      groups.addAll(List.of(excludedGroups));
      return path(path);
    }

    public FieldDescriptor path(String path) {
      var description = constraints.descriptionsForProperty(path).stream()
        .filter(s -> !s.isEmpty())
        .collect(joining(". "));
      return fieldWithPath(path).attributes(key("constraints").value(description));
    }

    private static ResourceBundleConstraintDescriptionResolver resolver(List<Class<?>> groups) {
      return new ResourceBundleConstraintDescriptionResolver() {

        @Override
        public String resolveDescription(Constraint constraint) {
          var description = super.resolveDescription(constraint);
          if (!groups.isEmpty()) {
            var constraintGroups = (Class<?>[]) constraint.getConfiguration().get("groups");
            if (List.of(constraintGroups).containsAll(groups)) {
              description = "";
              groups.clear();
            }
          }
          return description;
        }

      };
    }

  }