From 224d67cde1a662a3447f58288ef2a38b6b6c5433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20M=C3=BChlemann?= Date: Wed, 9 Oct 2024 07:20:31 +0200 Subject: [PATCH] Implement non strict validation Issue: #285 --- .../composition/FoldValidationGenerator.java | 7 +-- .../InvalidCompositionDtoGetterGenerator.java | 8 +-- .../ValidCountValidationMethod.java | 3 +- .../validator/ValidatorClassGenerator.java | 13 ++++- .../DiscriminatableJavaComposition.java | 8 +++ .../FoldValidationGeneratorTest.java | 14 ++++++ ...alidCompositionDtoGetterGeneratorTest.java | 15 ++++++ .../ValidCountValidationMethodTest.java | 14 ++++++ .../FoldValidationGeneratorTest.snap | 15 ++++++ ...alidCompositionDtoGetterGeneratorTest.snap | 29 +++++++++++ .../ValidCountValidationMethodTest.snap | 12 +++++ .../ValidatorClassGeneratorTest.java | 19 +++++++ .../ValidatorClassGeneratorTest.snap | 23 +++++++++ .../DiscriminatableJavaCompositionTest.java | 49 +++++++++++++++++++ 14 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaCompositionTest.java diff --git a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGenerator.java b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGenerator.java index 2e43d2e4d..b58b4bbd6 100644 --- a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGenerator.java +++ b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGenerator.java @@ -54,7 +54,7 @@ private static Generator methodCon (comp, s, w) -> w.println( "if (%s() %s) {", - getValidCountMethodName(comp.getType()), getCountCondition(comp.getType()))) + getValidCountMethodName(comp.getType()), getCountCondition(comp, s))) .append(constant("return null;"), 1) .append(constant("}")) .append( @@ -66,8 +66,9 @@ private static Generator methodCon additionalFoldArguments(composition.getType()))); } - private static String getCountCondition(DiscriminatableJavaComposition.Type type) { - return type.equals(ONE_OF) ? "!= 1" : "== 0"; + private static String getCountCondition( + DiscriminatableJavaComposition composition, PojoSettings settings) { + return composition.validateExactlyOneMatch(settings) ? "!= 1" : "== 0"; } private static String additionalFoldArguments(DiscriminatableJavaComposition.Type type) { diff --git a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGenerator.java b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGenerator.java index 547c97111..d3d3205fa 100644 --- a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGenerator.java +++ b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGenerator.java @@ -73,7 +73,7 @@ private static Generator addInvalidSingleRes return Generator.emptyGen() .append( (container, s, w) -> - w.println("if(%s) {", invalidCondition(container.getComposition()))) + w.println("if(%s) {", invalidCondition(container.getComposition(), s))) .appendOptional( singleResultDiscriminatorHandling().indent(1), DiscriminatorAndMemberPojo::fromCompositionContainer) @@ -81,12 +81,14 @@ private static Generator addInvalidSingleRes .append(constant("}")); } - private static String invalidCondition(DiscriminatableJavaComposition composition) { + private static String invalidCondition( + DiscriminatableJavaComposition composition, PojoSettings settings) { final DiscriminatableJavaComposition.Type type = composition.getType(); final String validCountCondition = String.format( "%s() %s", - getCompositionValidCountMethodName(type), type.equals(ONE_OF) ? "!= 1" : "== 0"); + getCompositionValidCountMethodName(type), + composition.validateExactlyOneMatch(settings) ? "!= 1" : "== 0"); final String discriminatorCondition = String.format("!%s()", isValidAgainstTheCorrectSchemaMethodName(type)); return PList.of( diff --git a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethod.java b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethod.java index 8ba264223..f00dd2f2a 100644 --- a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethod.java +++ b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethod.java @@ -87,6 +87,7 @@ Generator isValidAgainstMoreThanOneSchema() { .append(annotation) .append(JacksonAnnotationGenerator.jsonIgnore()) .append(method) - .prependNewLine(); + .prependNewLine() + .filter(DiscriminatableJavaComposition::validateExactlyOneMatch); } } diff --git a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGenerator.java b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGenerator.java index f1db82940..ad5196e7b 100644 --- a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGenerator.java +++ b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGenerator.java @@ -31,6 +31,7 @@ import io.github.muehmar.codegenerator.writer.Writer; import java.util.Optional; import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; @@ -131,7 +132,11 @@ private static PList createAllOfDtoValidationConditions(JavaObjectPoj private static Condition methodContentOneOfCondition() { return Condition.constant("getOneOfValidCount() == 1") - .filter(JavaObjectPojo::hasOneOfComposition); + .filter( + (pojo, settings) -> + pojo.getOneOfComposition() + .map(comp -> comp.validateExactlyOneMatch(settings)) + .orElse(false)); } private static Condition methodContentOneOfDiscriminatorCondition() { @@ -206,5 +211,11 @@ default Condition filter(Predicate predicate) { final Generator self = this; return (p, s, w) -> predicate.test(p) ? self.generate(p, s, w) : w; } + + @Override + default Condition filter(BiPredicate predicate) { + final Generator self = this; + return (p, s, w) -> predicate.test(p, s) ? self.generate(p, s, w) : w; + } } } diff --git a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaComposition.java b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaComposition.java index 4029b1861..15a9452fc 100644 --- a/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaComposition.java +++ b/plugin/src/main/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaComposition.java @@ -1,10 +1,13 @@ package com.github.muehmar.gradle.openapi.generator.java.model.composition; +import static com.github.muehmar.gradle.openapi.util.Booleans.not; + import ch.bluecare.commons.data.NonEmptyList; import ch.bluecare.commons.data.PList; import com.github.muehmar.gradle.openapi.generator.java.model.member.TechnicalPojoMember; import com.github.muehmar.gradle.openapi.generator.java.model.name.JavaName; import com.github.muehmar.gradle.openapi.generator.java.model.pojo.JavaObjectPojo; +import com.github.muehmar.gradle.openapi.generator.settings.PojoSettings; import java.util.Optional; public interface DiscriminatableJavaComposition { @@ -17,6 +20,11 @@ public interface DiscriminatableJavaComposition { Type getType(); + default boolean validateExactlyOneMatch(PojoSettings settings) { + return getType() == Type.ONE_OF + && not(settings.isNonStrictOneOfValidation() && hasDiscriminator()); + } + default boolean hasDiscriminator() { return getDiscriminator().isPresent(); } diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGeneratorTest.java b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGeneratorTest.java index f97a5059c..be861bbdb 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGeneratorTest.java +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/FoldValidationGeneratorTest.java @@ -37,6 +37,20 @@ void generate_when_oneOfPojo_then_correctOutput() { expect.toMatchSnapshot(writerSnapshot(writer)); } + @Test + @SnapshotName("oneOfWithDiscriminatorAndNonStrictValidation") + void generate_when_oneOfWithDiscriminatorAndNonStrictValidation_then_correctOutput() { + final Generator generator = foldValidationGenerator(); + + final Writer writer = + generator.generate( + JavaPojos.oneOfPojoWithEnumDiscriminator(), + defaultTestSettings().withNonStrictOneOfValidation(true), + javaWriter()); + + expect.toMatchSnapshot(writerSnapshot(writer)); + } + @Test @SnapshotName("oneOfProtectedAndDeprecatedSettings") void generate_when_protectedAndDeprecatedSettings_then_correctOutput() { diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGeneratorTest.java b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGeneratorTest.java index faed4d3c6..03a31a8e1 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGeneratorTest.java +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/InvalidCompositionDtoGetterGeneratorTest.java @@ -47,6 +47,21 @@ void generate_when_oneOfPojo_then_correctOutput() { expect.toMatchSnapshot(writerSnapshot(writer)); } + @Test + @SnapshotName("oneOfPojoWithDiscriminatorAndNonStrictValidation") + void generate_when_oneOfPojoWithDiscriminatorAndNonStrictValidation_then_correctOutput() { + final Generator generator = + invalidCompositionDtoGetterGenerator(); + + final Writer writer = + generator.generate( + JavaPojos.oneOfPojoWithEnumDiscriminator(), + defaultTestSettings().withNonStrictOneOfValidation(true), + javaWriter()); + + expect.toMatchSnapshot(writerSnapshot(writer)); + } + @Test @SnapshotName("oneOfPojoWithPublicDeprecatedValidationMethods") void generate_when_oneOfPojoWithPublicDeprecatedValidationMethods_then_correctOutput() { diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethodTest.java b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethodTest.java index 5a8f24b78..f053fd1fd 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethodTest.java +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/ValidCountValidationMethodTest.java @@ -33,6 +33,20 @@ void generate_when_oneOfPojo_then_correctOutput() { expect.toMatchSnapshot(writerSnapshot(writer)); } + @Test + @SnapshotName("oneOfPojoWithDiscriminatorAndNonStrictValidation") + void generate_when_oneOfPojoWithDiscriminatorAndNonStrictValidation_then_correctOutput() { + final Generator generator = + ValidCountValidationMethod.validCountValidationMethodGenerator(); + + final JavaObjectPojo javaPojo = JavaPojos.oneOfPojoWithEnumDiscriminator(); + final Writer writer = + generator.generate( + javaPojo, defaultTestSettings().withNonStrictOneOfValidation(true), javaWriter()); + + expect.toMatchSnapshot(writerSnapshot(writer)); + } + @Test @SnapshotName("anyOf") void generate_when_anyOfPojo_then_correctOutput() { diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/FoldValidationGeneratorTest.snap b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/FoldValidationGeneratorTest.snap index 168cee6fd..f90bc3e0f 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/FoldValidationGeneratorTest.snap +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/FoldValidationGeneratorTest.snap @@ -65,4 +65,19 @@ protected Object getOneOf() { } return foldOneOf(dto -> dto, dto -> dto, () -> null); } +] + + +oneOfWithDiscriminatorAndNonStrictValidation=[ +com.fasterxml.jackson.annotation.JsonIgnore +javax.validation.Valid + +@Valid +@JsonIgnore +private Object getOneOf() { + if (getOneOfValidCount() == 0) { + return null; + } + return foldOneOf(dto -> dto, dto -> dto, () -> null); +} ] \ No newline at end of file diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/InvalidCompositionDtoGetterGeneratorTest.snap b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/InvalidCompositionDtoGetterGeneratorTest.snap index 38096a600..c2de88049 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/InvalidCompositionDtoGetterGeneratorTest.snap +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/InvalidCompositionDtoGetterGeneratorTest.snap @@ -124,6 +124,35 @@ private Map getInvalidOneOf() { ] +oneOfPojoWithDiscriminatorAndNonStrictValidation=[ +com.fasterxml.jackson.annotation.JsonIgnore +java.util.HashMap +java.util.Map +javax.validation.Valid + +@Valid +@JsonIgnore +private Map getInvalidOneOf() { + final Map dtos = new HashMap<>(); + if(getOneOfValidCount() == 0 || !isValidAgainstTheCorrectOneOfSchema()) { + if(color != null) { + switch(color.getValue()) { + case "yellow": + dtos.put("Yellow", asYellowDto()); + return dtos; + case "orange": + dtos.put("Orange", asOrangeDto()); + return dtos; + } + } + dtos.put("Yellow", asYellowDto()); + dtos.put("Orange", asOrangeDto()); + } + return dtos; +} +] + + oneOfPojoWithEnumDiscriminator=[ com.fasterxml.jackson.annotation.JsonIgnore java.util.HashMap diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/ValidCountValidationMethodTest.snap b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/ValidCountValidationMethodTest.snap index e7ac67ed8..c778f582d 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/ValidCountValidationMethodTest.snap +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/composition/__snapshots__/ValidCountValidationMethodTest.snap @@ -25,4 +25,16 @@ private boolean isValidAgainstNoOneOfSchema() { private boolean isValidAgainstMoreThanOneSchema() { return getOneOfValidCount() > 1; } +] + + +oneOfPojoWithDiscriminatorAndNonStrictValidation=[ +com.fasterxml.jackson.annotation.JsonIgnore +javax.validation.constraints.AssertFalse + +@AssertFalse(message = "Is not valid against one of the schemas [Yellow, Orange]") +@JsonIgnore +private boolean isValidAgainstNoOneOfSchema() { + return getOneOfValidCount() == 0; +} ] \ No newline at end of file diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGeneratorTest.java b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGeneratorTest.java index a68d9a392..7f195633a 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGeneratorTest.java +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/ValidatorClassGeneratorTest.java @@ -94,6 +94,25 @@ void generate_when_oneOfPojoWithDiscriminator_then_correctOutput() { expect.toMatchSnapshot(writer.asString()); } + @Test + @SnapshotName("oneOfPojoWithDiscriminatorAndNonStrictOneOfValidation") + void generate_when_oneOfPojoWithDiscriminatorAndNonStrictOneOfValidation_then_correctOutput() { + final Generator generator = validationClassGenerator(); + + final JavaOneOfComposition javaOneOfComposition = + JavaOneOfCompositions.fromPojosAndDiscriminator( + NonEmptyList.of(sampleObjectPojo1(), sampleObjectPojo2()), + UntypedDiscriminator.fromPropertyName(Name.ofString("type"))); + + final JavaObjectPojo pojo = JavaPojos.oneOfPojo(javaOneOfComposition); + + final Writer writer = + generator.generate( + pojo, defaultTestSettings().withNonStrictOneOfValidation(true), javaWriter()); + + expect.toMatchSnapshot(writer.asString()); + } + @Test @SnapshotName("anyOfPojoWithMembers") void generate_when_anyOfPojoWithMembers_then_correctOutput() { diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/__snapshots__/ValidatorClassGeneratorTest.snap b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/__snapshots__/ValidatorClassGeneratorTest.snap index 7ba06c7cb..579981f6c 100644 --- a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/__snapshots__/ValidatorClassGeneratorTest.snap +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/generator/pojo/validation/validator/__snapshots__/ValidatorClassGeneratorTest.snap @@ -647,6 +647,29 @@ private class Validator { ] +oneOfPojoWithDiscriminatorAndNonStrictOneOfValidation=[ +private class Validator { + + private boolean isAdditionalPropertiesValid() { + if(getAdditionalProperties_() != null) { + return getAdditionalProperties_().values().stream().allMatch(this::isAdditionalPropertiesValueValid); + } + + return false; + } + + private boolean isAdditionalPropertiesValueValid(Object additionalPropertiesValue) { + return true; + } + + private boolean isValid() { + return isValidAgainstTheCorrectOneOfSchema() + && isAdditionalPropertiesValid(); + } +} +] + + oneOfPojoWithMembers=[ private class Validator { private boolean isColorValid() { diff --git a/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaCompositionTest.java b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaCompositionTest.java new file mode 100644 index 000000000..e66720bea --- /dev/null +++ b/plugin/src/test/java/com/github/muehmar/gradle/openapi/generator/java/model/composition/DiscriminatableJavaCompositionTest.java @@ -0,0 +1,49 @@ +package com.github.muehmar.gradle.openapi.generator.java.model.composition; + +import static com.github.muehmar.gradle.openapi.generator.java.model.pojo.JavaPojos.sampleObjectPojo1; +import static com.github.muehmar.gradle.openapi.generator.java.model.pojo.JavaPojos.sampleObjectPojo2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.github.muehmar.gradle.openapi.generator.java.model.pojo.JavaPojos; +import com.github.muehmar.gradle.openapi.generator.settings.PojoSettings; +import com.github.muehmar.gradle.openapi.generator.settings.TestPojoSettings; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class DiscriminatableJavaCompositionTest { + + @ParameterizedTest + @MethodSource("compositionAndSettingsCombinations") + void validateExactlyOneMatch_when_compositionAndSettingsCombinations_then_matchExpected( + DiscriminatableJavaComposition composition, PojoSettings settings, boolean expected) { + + assertEquals(expected, composition.validateExactlyOneMatch(settings)); + } + + public static Stream compositionAndSettingsCombinations() { + final DiscriminatableJavaComposition oneOfCompositionWithDiscriminator = + JavaPojos.oneOfPojoWithEnumDiscriminator().getOneOfComposition().get(); + final DiscriminatableJavaComposition oneOfComposition = + JavaPojos.oneOfPojo(sampleObjectPojo1(), sampleObjectPojo2()).getOneOfComposition().get(); + final DiscriminatableJavaComposition anyOfPojoWithDiscriminator = + JavaPojos.anyOfPojoWithDiscriminator().getAnyOfComposition().get(); + final DiscriminatableJavaComposition anyOfComposition = + JavaPojos.anyOfPojo(sampleObjectPojo1(), sampleObjectPojo2()).getAnyOfComposition().get(); + + final PojoSettings defaultSettings = TestPojoSettings.defaultTestSettings(); + final PojoSettings nonStrictSettings = defaultSettings.withNonStrictOneOfValidation(true); + + return Stream.of( + arguments(oneOfComposition, defaultSettings, true), + arguments(oneOfComposition, nonStrictSettings, true), + arguments(oneOfCompositionWithDiscriminator, defaultSettings, true), + arguments(oneOfCompositionWithDiscriminator, nonStrictSettings, false), + arguments(anyOfComposition, defaultSettings, false), + arguments(anyOfComposition, nonStrictSettings, false), + arguments(anyOfPojoWithDiscriminator, defaultSettings, false), + arguments(anyOfPojoWithDiscriminator, nonStrictSettings, false)); + } +}