diff --git a/CHANGELOG.md b/CHANGELOG.md index b1109967..5ca02817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - -- +### `jsonschema-module-jakarta-validation` +#### Added +- populate `const`/`enum` based on `@AssertTrue`/`@AssertFalse` ## [4.35.0] - 2024-03-29 ### `jsonschema-generator` diff --git a/jsonschema-examples/pom.xml b/jsonschema-examples/pom.xml index 530a3502..bc6e9f93 100644 --- a/jsonschema-examples/pom.xml +++ b/jsonschema-examples/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-examples diff --git a/jsonschema-generator-bom/pom.xml b/jsonschema-generator-bom/pom.xml index 312137c8..3b616ece 100644 --- a/jsonschema-generator-bom/pom.xml +++ b/jsonschema-generator-bom/pom.xml @@ -8,7 +8,7 @@ com.github.victools jsonschema-generator-bom - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT pom diff --git a/jsonschema-generator-parent/pom.xml b/jsonschema-generator-parent/pom.xml index 8c54ccde..3340bbdf 100644 --- a/jsonschema-generator-parent/pom.xml +++ b/jsonschema-generator-parent/pom.xml @@ -4,14 +4,14 @@ com.github.victools jsonschema-generator-bom - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-bom/pom.xml jsonschema-generator-parent pom Java JSON Schema Generator (Parent) - Java JSON Schema Generator + Modules – creating a JSON Schema from your Java classes + Java JSON Schema Generator + Modules - creating a JSON Schema from your Java classes https://github.com/victools/jsonschema-generator @@ -126,6 +126,7 @@ https://github.com/Nephery Provided PR #435 (fixing Jackson JsonUnwrapped annotation on inherited properties) + Provided PR #456 (introducing support for Jakarta @AssertTrue/@AssertFalse) diff --git a/jsonschema-generator/pom.xml b/jsonschema-generator/pom.xml index a98f98b6..82d33498 100644 --- a/jsonschema-generator/pom.xml +++ b/jsonschema-generator/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-generator diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java index 5f093774..22a8e803 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/AttributeCollector.java @@ -435,6 +435,7 @@ private boolean isSupportedEnumValue(Object target) { } Class targetType = target.getClass(); return targetType.isPrimitive() + || Boolean.class.isAssignableFrom(targetType) || Number.class.isAssignableFrom(targetType) || CharSequence.class.isAssignableFrom(targetType) || Enum.class.isAssignableFrom(targetType); diff --git a/jsonschema-maven-plugin/pom.xml b/jsonschema-maven-plugin/pom.xml index 5e2bb0e9..69f5eef1 100644 --- a/jsonschema-maven-plugin/pom.xml +++ b/jsonschema-maven-plugin/pom.xml @@ -6,7 +6,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-maven-plugin diff --git a/jsonschema-module-jackson/pom.xml b/jsonschema-module-jackson/pom.xml index 73952c73..861836d8 100644 --- a/jsonschema-module-jackson/pom.xml +++ b/jsonschema-module-jackson/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-module-jackson diff --git a/jsonschema-module-jakarta-validation/README.md b/jsonschema-module-jakarta-validation/README.md index f31acc30..cf1a0d0e 100644 --- a/jsonschema-module-jakarta-validation/README.md +++ b/jsonschema-module-jakarta-validation/README.md @@ -13,6 +13,7 @@ Module for the [jsonschema-generator](../jsonschema-generator) – deriving JSON 7. Populate "pattern" for strings. Based on `@Pattern`/`@Email`, when corresponding `JakartaValidationOption.INCLUDE_PATTERN_EXPRESSIONS` is being provided in constructor. 8. Populate "minimum"/"exclusiveMinimum" for numbers. Based on `@Min`/`@DecimalMin`/`@Positive`/`@PositiveOrZero`. 9. Populate "maximum"/"exclusiveMaximum" for numbers. Based on `@Max`/`@DecimalMax`/`@Negative`/`@NegativeOrZero`. +10. Populate "enum"/"const" for booleans. Based on `@AssertTrue`/`@AssertFalse`. Schema attributes derived from validation annotations on fields are also applied to their respective getter methods. Schema attributes derived from validation annotations on getter methods are also applied to their associated fields. diff --git a/jsonschema-module-jakarta-validation/pom.xml b/jsonschema-module-jakarta-validation/pom.xml index dd5e77f4..044b5243 100644 --- a/jsonschema-module-jakarta-validation/pom.xml +++ b/jsonschema-module-jakarta-validation/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-module-jakarta-validation diff --git a/jsonschema-module-jakarta-validation/src/main/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModule.java b/jsonschema-module-jakarta-validation/src/main/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModule.java index 3bfc2574..012ccee0 100644 --- a/jsonschema-module-jakarta-validation/src/main/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModule.java +++ b/jsonschema-module-jakarta-validation/src/main/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModule.java @@ -28,6 +28,8 @@ import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart; import com.github.victools.jsonschema.generator.SchemaKeyword; import jakarta.validation.Constraint; +import jakarta.validation.constraints.AssertFalse; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Email; @@ -45,12 +47,13 @@ import jakarta.validation.constraints.Size; import java.lang.annotation.Annotation; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.ToIntBiFunction; @@ -116,8 +119,9 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) { if (this.options.contains(JakartaValidationOption.NOT_NULLABLE_METHOD_IS_REQUIRED)) { methodConfigPart.withRequiredCheck(this::isRequired); } - Stream.of(DecimalMax.class, DecimalMin.class, Email.class, Max.class, Min.class, Negative.class, NegativeOrZero.class, - NotBlank.class, NotEmpty.class, Null.class, NotNull.class, Pattern.class, Positive.class, PositiveOrZero.class, Size.class) + Stream.of(AssertFalse.class, AssertTrue.class, DecimalMax.class, DecimalMin.class, Email.class, Max.class, Min.class, + Negative.class, NegativeOrZero.class, NotBlank.class, NotEmpty.class, Null.class, NotNull.class, + Pattern.class, Positive.class, PositiveOrZero.class, Size.class) .forEach(annotationType -> builder.withAnnotationInclusionOverride(annotationType, AnnotationInclusion.INCLUDE_AND_INHERIT)); } @@ -137,6 +141,7 @@ private void applyToConfigPart(SchemaGeneratorConfigPart configPart) { configPart.withNumberExclusiveMinimumResolver(this::resolveNumberExclusiveMinimum); configPart.withNumberInclusiveMaximumResolver(this::resolveNumberInclusiveMaximum); configPart.withNumberExclusiveMaximumResolver(this::resolveNumberExclusiveMaximum); + configPart.withEnumResolver(this::resolveEnum); configPart.withInstanceAttributeOverride(this::overrideInstanceAttributes); if (this.options.contains(JakartaValidationOption.INCLUDE_PATTERN_EXPRESSIONS)) { @@ -499,6 +504,28 @@ protected BigDecimal resolveNumberExclusiveMaximum(MemberScope member) { return null; } + /** + * Look-up the finite list of possible values. + * + * @param member field/method to determine allowed values for + * @return applicable "const"/"enum" values or null + * + * @since 4.36.0 + * @see AssertTrue + * @see AssertFalse + */ + protected List resolveEnum(MemberScope member) { + List values; + if (this.getAnnotationFromFieldOrGetter(member, AssertTrue.class, AssertTrue::groups) != null) { + values = Collections.singletonList(true); + } else if (this.getAnnotationFromFieldOrGetter(member, AssertFalse.class, AssertFalse::groups) != null) { + values = Collections.singletonList(false); + } else { + values = null; + } + return values; + } + /** * Implementation of the functional {@code InstanceAttributeOverrideV2} interface. * diff --git a/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/IntegrationTest.java b/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/IntegrationTest.java index 3a8f4385..ae0b4555 100644 --- a/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/IntegrationTest.java +++ b/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/IntegrationTest.java @@ -24,6 +24,8 @@ import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaVersion; import jakarta.validation.Constraint; +import jakarta.validation.constraints.AssertFalse; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Email; @@ -134,6 +136,11 @@ static class TestClass { @DecimalMin(value = "0", inclusive = false) @DecimalMax(value = "1", inclusive = false) public double exclusiveRangeDouble; + + @AssertTrue + public boolean trueBoolean; + @AssertFalse + public boolean falseBoolean; } static class Book implements Publication { diff --git a/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModuleTest.java b/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModuleTest.java index a73997cc..00145c77 100644 --- a/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModuleTest.java +++ b/jsonschema-module-jakarta-validation/src/test/java/com/github/victools/jsonschema/module/jakarta/validation/JakartaValidationModuleTest.java @@ -23,6 +23,8 @@ import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart; +import jakarta.validation.constraints.AssertFalse; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Email; @@ -40,6 +42,8 @@ import jakarta.validation.constraints.Size; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -146,6 +150,7 @@ private void verifyCommonConfigurations() { Mockito.verify(this.fieldConfigPart).withNumberExclusiveMinimumResolver(Mockito.any()); Mockito.verify(this.fieldConfigPart).withNumberInclusiveMaximumResolver(Mockito.any()); Mockito.verify(this.fieldConfigPart).withNumberExclusiveMaximumResolver(Mockito.any()); + Mockito.verify(this.fieldConfigPart).withEnumResolver(Mockito.any()); Mockito.verify(this.fieldConfigPart).withInstanceAttributeOverride(Mockito.any(InstanceAttributeOverrideV2.class)); Mockito.verify(this.methodConfigPart).withNullableCheck(Mockito.any()); @@ -158,9 +163,10 @@ private void verifyCommonConfigurations() { Mockito.verify(this.methodConfigPart).withNumberExclusiveMinimumResolver(Mockito.any()); Mockito.verify(this.methodConfigPart).withNumberInclusiveMaximumResolver(Mockito.any()); Mockito.verify(this.methodConfigPart).withNumberExclusiveMaximumResolver(Mockito.any()); + Mockito.verify(this.methodConfigPart).withEnumResolver(Mockito.any()); Mockito.verify(this.methodConfigPart).withInstanceAttributeOverride(Mockito.any(InstanceAttributeOverrideV2.class)); - Mockito.verify(this.configBuilder, Mockito.times(15)) + Mockito.verify(this.configBuilder, Mockito.times(17)) .withAnnotationInclusionOverride(Mockito.any(), Mockito.eq(AnnotationInclusion.INCLUDE_AND_INHERIT)); } @@ -533,6 +539,58 @@ private void testNumberMinMaxResolvers(String fieldName, BigDecimal expectedMinI Assertions.assertEquals(expectedMaxExclusive, maxExclusive); } + static Stream parametersForTestEnumResolvers() { + return Stream.of( + Arguments.of("unannotatedBoolean", new Object[]{}), + Arguments.of("trueBoolean", new Object[]{true}), + Arguments.of("trueOnGetterBoolean", new Object[]{true}), + Arguments.of("falseBoolean", new Object[]{false}), + Arguments.of("falseOnGetterBoolean", new Object[]{false}), + // it's deemed invalid to have both @AssertTrue and @AssertFalse simultaneously + Arguments.of("trueAndFalseBoolean", new Object[]{true}), + Arguments.of("trueAndFalseOnGetterBoolean", new Object[]{true}) + ); + } + + @ParameterizedTest + @MethodSource("parametersForTestEnumResolvers") + public void testEnumResolversNoValidationGroup(String fieldName, Object[] expectedValues) { + new JakartaValidationModule().applyToConfigBuilder(this.configBuilder); + + this.testEnumResolvers(fieldName, expectedValues); + } + + @ParameterizedTest + @MethodSource("parametersForTestEnumResolvers") + public void testEnumResolversMatchingValidationGroup(String fieldName, Object[] expectedValues) { + new JakartaValidationModule() + .forValidationGroups(Test.class) + .applyToConfigBuilder(this.configBuilder); + + this.testEnumResolvers(fieldName, expectedValues); + } + + @ParameterizedTest + @MethodSource("parametersForTestEnumResolvers") + public void testEnumResolversDifferentValidationGroup(String fieldName, Object[] ignoredExpectedValues) { + new JakartaValidationModule() + .forValidationGroups(Object.class) + .applyToConfigBuilder(this.configBuilder); + + // none of the annotated values are actually expected to be returned + this.testEnumResolvers(fieldName); + } + + private void testEnumResolvers(String fieldName, Object... values) { + TestType testType = new TestType(TestClassForEnums.class); + FieldScope field = testType.getMemberField(fieldName); + + ArgumentCaptor>> enumCaptor = ArgumentCaptor.forClass(ConfigFunction.class); + Mockito.verify(this.fieldConfigPart).withEnumResolver(enumCaptor.capture()); + Collection enumValues = enumCaptor.getValue().apply(field); + Assertions.assertEquals(values.length > 0 ? Arrays.asList(values) : null, enumValues); + } + static Stream parametersForTestValidationGroupSetting() { return Stream.of( Arguments.of("skippedConfiguringGroups", "fieldWithoutValidationGroup", Boolean.TRUE, null), @@ -818,6 +876,36 @@ public Long getNegativeOrZeroOnGetterLong() { } } + private static class TestClassForEnums { + boolean unannotatedBoolean; + @AssertTrue(groups = Test.class) + boolean trueBoolean; + boolean trueOnGetterBoolean; + @AssertFalse(groups = Test.class) + boolean falseBoolean; + boolean falseOnGetterBoolean; + @AssertTrue(groups = Test.class) + @AssertFalse(groups = Test.class) + boolean trueAndFalseBoolean; + boolean trueAndFalseOnGetterBoolean; + + @AssertTrue(groups = Test.class) + public boolean isTrueOnGetterBoolean() { + return trueOnGetterBoolean; + } + + @AssertFalse(groups = Test.class) + public boolean isFalseOnGetterBoolean() { + return falseOnGetterBoolean; + } + + @AssertTrue(groups = Test.class) + @AssertFalse(groups = Test.class) + public boolean isTrueAndFalseOnGetterBoolean() { + return trueAndFalseOnGetterBoolean; + } + } + private static class TestClassForValidationGroups { @Null diff --git a/jsonschema-module-jakarta-validation/src/test/resources/com/github/victools/jsonschema/module/jakarta/validation/integration-test-result.json b/jsonschema-module-jakarta-validation/src/test/resources/com/github/victools/jsonschema/module/jakarta/validation/integration-test-result.json index fe43abf7..7c13df22 100644 --- a/jsonschema-module-jakarta-validation/src/test/resources/com/github/victools/jsonschema/module/jakarta/validation/integration-test-result.json +++ b/jsonschema-module-jakarta-validation/src/test/resources/com/github/victools/jsonschema/module/jakarta/validation/integration-test-result.json @@ -7,6 +7,10 @@ "exclusiveMinimum": 0, "exclusiveMaximum": 1 }, + "falseBoolean": { + "type": "boolean", + "const": false + }, "inclusiveRangeInt": { "type": "integer", "minimum": 7, @@ -113,6 +117,10 @@ "type": "string", "minLength": 5, "maxLength": 12 + }, + "trueBoolean": { + "type": "boolean", + "const": true } }, "required": ["notBlankText", "notEmptyList", "notEmptyMap", "notEmptyPatternText", "notNullEmail", "notNullList"] diff --git a/jsonschema-module-javax-validation/pom.xml b/jsonschema-module-javax-validation/pom.xml index bb89e6ca..fe229141 100644 --- a/jsonschema-module-javax-validation/pom.xml +++ b/jsonschema-module-javax-validation/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-module-javax-validation diff --git a/jsonschema-module-swagger-1.5/pom.xml b/jsonschema-module-swagger-1.5/pom.xml index df053887..ed602d30 100644 --- a/jsonschema-module-swagger-1.5/pom.xml +++ b/jsonschema-module-swagger-1.5/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-module-swagger-1.5 diff --git a/jsonschema-module-swagger-2/pom.xml b/jsonschema-module-swagger-2/pom.xml index 508e69b3..d039680d 100644 --- a/jsonschema-module-swagger-2/pom.xml +++ b/jsonschema-module-swagger-2/pom.xml @@ -5,7 +5,7 @@ com.github.victools jsonschema-generator-parent - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT ../jsonschema-generator-parent/pom.xml jsonschema-module-swagger-2 diff --git a/pom.xml b/pom.xml index 12e2106b..9b052d5e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.github.victools jsonschema-generator-reactor - 4.35.1-SNAPSHOT + 4.36.0-SNAPSHOT pom Java JSON Schema Generator (Reactor) diff --git a/slate-docs/source/includes/_jakarta-validation-module.md b/slate-docs/source/includes/_jakarta-validation-module.md index 620fe804..20538ab8 100644 --- a/slate-docs/source/includes/_jakarta-validation-module.md +++ b/slate-docs/source/includes/_jakarta-validation-module.md @@ -24,6 +24,7 @@ SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(Sc 7. Populate "pattern" for strings. Based on `@Pattern`/`@Email`, if `JakartaValidationOption.INCLUDE_PATTERN_EXPRESSIONS` is being provided (i.e. this is an "opt-in"). 8. Populate "minimum"/"exclusiveMinimum" for numbers. Based on `@Min`/`@DecimalMin`/`@Positive`/`@PositiveOrZero`. 9. Populate "maximum"/"exclusiveMaximum" for numbers. Based on `@Max`/`@DecimalMax`/`@Negative`/`@NegativeOrZero`. +10. Populate "enum"/"const" for booleans. Based on `@AssertTrue`/`@AssertFalse`. Schema attributes derived from validation annotations on fields are also applied to their respective getter methods. Schema attributes derived from validation annotations on getter methods are also applied to their associated fields.