swagger-core icon indicating copy to clipboard operation
swagger-core copied to clipboard

Incorrect inclusion of javax/jakarta validation annotations with `groups` property set

Open mc1arke opened this issue 10 months ago • 1 comments

The javax/jakarta annotations contain a groups property that can be used to enable different combinations of validation on a model depending on which group is currently active - https://jakarta.ee/learn/docs/jakartaee-tutorial/current/beanvalidation/bean-validation-advanced/bean-validation-advanced.html#_grouping_constraints.

When executed against a model, swagger-core currently treats any of the supported javax/jakarta annotations as applying a constraint to the schema, even where the annotated field/attribute specifies groups and is therefore is not mandatory on all requests. By default, swagger-core should only set fields as mandatory/constrained based on annotations where no groups are specified, but provide a way of the end-user adding other validation groups into the constraints where they want to treat certain groups as always being enabled.

Replication Steps

        @Test
        public void shouldIncludeOnlyNonGroupedJakartaValidatedFieldsAsMandatoryByDefault() {
            ResolvedSchema schema = ModelConverters.getInstance(false).resolveAsResolvedSchema(new AnnotatedType().type(CustomClass.class));
            String expectedJson = "{\"schema\":{\"required\":[\"nonGroupValidatedField\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}},\"referencedSchemas\":{\"CustomClass\":{\"required\":[\"nonGroupValidatedField\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}}}}";
            SerializationMatchers.assertEqualsToJson(schema, expectedJson);
        }


        private interface ValidationGroup {

        }

        private interface OtherValidationGroup {

        }


        private static class CustomClass {

            @NotNull
            public String nonGroupValidatedField;

            @Min(value = 1, groups = ValidationGroup.class)
            public Integer singleGroupValidatedField;

            @DecimalMin(value = "1.0", groups = {ValidationGroup.class, OtherValidationGroup.class})
            public BigDecimal multipleGroupValidatedField;

            @Pattern(regexp = ".*", groups = OtherValidationGroup.class)
            public String otherGroupValidatedField;

            @NotEmpty(groups = ValidationGroup.class)
            public String singleGroupValidatedField2;
        }

The above replicate a validator being run in default mode, e.g. Validation.buildDefaultValidatorFactory().getValidator().validate(new CustomClass());, which is the default way that frameworks such as Spring invoke validators. Users using something similar to Validation.buildDefaultValidatorFactory().getValidator().validate(new CustomClass(), ValidationGroup.class); would add the annotations with ValidationGroup in the groups property into the enforced validation.

mc1arke avatar Dec 07 '24 21:12 mc1arke