From 2d53c26ee368ba891dfef90a6d00e04c88be3cb2 Mon Sep 17 00:00:00 2001 From: Carsten Wickner <11309681+CarstenWickner@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:38:24 +0100 Subject: [PATCH 1/4] fix: Possible null pointer in EnumModule --- .../jsonschema/generator/impl/module/EnumModule.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java index 3054aa79..74724a63 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java @@ -103,7 +103,10 @@ private static boolean isEnum(ResolvedType type) { private static List extractEnumValues(MethodScope method) { ResolvedType declaringType = method.getDeclaringType(); if (EnumModule.isEnum(declaringType)) { - return EnumModule.extractEnumValues(declaringType.getTypeParameters().get(0), Enum::name); + ResolvedType enumType = declaringType.getTypeParameters().stream().findFirst().orElse(null); + if (enumType != null) { + return EnumModule.extractEnumValues(enumType, Enum::name); + } } return null; } From f41f3d3e0c4912d84987bd34396074e58efee579 Mon Sep 17 00:00:00 2001 From: Carsten Wickner <11309681+CarstenWickner@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:41:14 +0100 Subject: [PATCH 2/4] chore: Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90376078..44e0e93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Changed - new `Option.DUPLICATE_MEMBER_ATTRIBUTE_CLEANUP_AT_THE_END` by default included in standard `OptionPreset`s +#### Fixed +- avoid exception when trying to collect supported enum values from raw `Enum` type (i.e., missing type parameter) + ## [4.33.1] - 2023-12-19 ### `jsonschema-module-jackson` #### Fixed From 7ca27a2c42df2038f7cb1d4d2dcc7bb1bb7f2f03 Mon Sep 17 00:00:00 2001 From: Carsten Wickner Date: Thu, 8 Aug 2024 00:25:29 +0200 Subject: [PATCH 3/4] chore(test): add unit test for avoided npe --- .../generator/impl/module/EnumModule.java | 9 +++++-- .../generator/impl/module/EnumModuleTest.java | 27 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java index 314ed956..9837c94b 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java @@ -26,6 +26,7 @@ import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaKeyword; import com.github.victools.jsonschema.generator.impl.AttributeCollector; +import com.github.victools.jsonschema.generator.impl.Util; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @@ -120,7 +121,7 @@ private static List extractEnumValues(MethodScope method) { */ private static List extractEnumValues(ResolvedType enumType, Function, String> enumConstantToString) { Class erasedType = enumType.getErasedType(); - if (erasedType.getEnumConstants() == null) { + if (Util.isNullOrEmpty(erasedType.getEnumConstants())) { return null; } return Stream.of(erasedType.getEnumConstants()) @@ -147,10 +148,14 @@ private static class EnumAsStringDefinitionProvider implements CustomDefinitionP @Override public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, SchemaGenerationContext context) { if (javaType.isInstanceOf(Enum.class)) { + List enumValues = EnumModule.extractEnumValues(javaType, this.enumConstantToString); + if (Util.isNullOrEmpty(enumValues)) { + return null; + } ObjectNode customNode = context.getGeneratorConfig().createObjectNode() .put(context.getKeyword(SchemaKeyword.TAG_TYPE), context.getKeyword(SchemaKeyword.TAG_TYPE_STRING)); new AttributeCollector(context.getGeneratorConfig().getObjectMapper()) - .setEnum(customNode, EnumModule.extractEnumValues(javaType, this.enumConstantToString), context); + .setEnum(customNode, enumValues, context); return new CustomDefinition(customNode); } return null; diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java index 6e749916..368d6839 100644 --- a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java +++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java @@ -26,21 +26,27 @@ import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.FieldScope; import com.github.victools.jsonschema.generator.MethodScope; +import com.github.victools.jsonschema.generator.Option; import com.github.victools.jsonschema.generator.OptionPreset; +import com.github.victools.jsonschema.generator.SchemaGenerator; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart; import com.github.victools.jsonschema.generator.SchemaGeneratorGeneralConfigPart; import com.github.victools.jsonschema.generator.SchemaKeyword; import com.github.victools.jsonschema.generator.SchemaVersion; import java.util.stream.Stream; +import org.json.JSONException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; /** * Test for the {@link EnumModule} class. @@ -74,7 +80,7 @@ public static void setUp() { */ private void initConfigBuilder(SchemaVersion schemaVersion) { this.prepareContextForVersion(schemaVersion); - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(schemaVersion, new OptionPreset()); + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(schemaVersion, OptionPreset.PLAIN_JSON); this.typeConfigPart = Mockito.spy(configBuilder.forTypesInGeneral()); this.fieldConfigPart = Mockito.spy(configBuilder.forFields()); this.methodConfigPart = Mockito.spy(configBuilder.forMethods()); @@ -162,6 +168,21 @@ public void testCustomSchemaDefinition_asStrings(SchemaVersion schemaVersion, En Assertions.assertEquals(value3, enumNode.get(2).textValue()); } + @Test + public void testRawEnumType() throws JSONException { + this.initConfigBuilder(SchemaVersion.DRAFT_2020_12); + // test all three modules at once, as they are all expected to be unable to detect values for the raw enum type + instanceAsStringsFromName.applyToConfigBuilder(this.builder); + instanceAsStringsFromToString.applyToConfigBuilder(this.builder); + instanceAsObjects.applyToConfigBuilder(this.builder); + + JsonNode enumSchema = new SchemaGenerator(this.builder.build()).generateSchema(TestType.class) + .get(SchemaKeyword.TAG_PROPERTIES.forVersion(SchemaVersion.DRAFT_2020_12)) + .get("rawEnum"); + JSONAssert.assertEquals("{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"ordinal\":{\"type\":\"integer\"}}}", + enumSchema.toString(), JSONCompareMode.STRICT); + } + private enum TestEnum { VALUE1, VALUE2, VALUE3; @@ -170,4 +191,8 @@ public String toString() { return this.name().toLowerCase() + "_toString"; } } + + private static class TestType { + public Enum rawEnum; + } } From 0b27f10410d0832c80f4624a62c1031b399ebbf1 Mon Sep 17 00:00:00 2001 From: Carsten Wickner Date: Thu, 8 Aug 2024 00:42:21 +0200 Subject: [PATCH 4/4] fix: cleaner handling for raw enum edge case --- .../generator/impl/module/EnumModule.java | 6 +----- .../generator/impl/module/EnumModuleTest.java | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java index 9837c94b..234fc906 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/module/EnumModule.java @@ -148,14 +148,10 @@ private static class EnumAsStringDefinitionProvider implements CustomDefinitionP @Override public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, SchemaGenerationContext context) { if (javaType.isInstanceOf(Enum.class)) { - List enumValues = EnumModule.extractEnumValues(javaType, this.enumConstantToString); - if (Util.isNullOrEmpty(enumValues)) { - return null; - } ObjectNode customNode = context.getGeneratorConfig().createObjectNode() .put(context.getKeyword(SchemaKeyword.TAG_TYPE), context.getKeyword(SchemaKeyword.TAG_TYPE_STRING)); new AttributeCollector(context.getGeneratorConfig().getObjectMapper()) - .setEnum(customNode, enumValues, context); + .setEnum(customNode, EnumModule.extractEnumValues(javaType, this.enumConstantToString), context); return new CustomDefinition(customNode); } return null; diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java index 368d6839..06489dac 100644 --- a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java +++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/impl/module/EnumModuleTest.java @@ -80,7 +80,7 @@ public static void setUp() { */ private void initConfigBuilder(SchemaVersion schemaVersion) { this.prepareContextForVersion(schemaVersion); - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(schemaVersion, OptionPreset.PLAIN_JSON); + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(schemaVersion, new OptionPreset()); this.typeConfigPart = Mockito.spy(configBuilder.forTypesInGeneral()); this.fieldConfigPart = Mockito.spy(configBuilder.forFields()); this.methodConfigPart = Mockito.spy(configBuilder.forMethods()); @@ -169,17 +169,27 @@ public void testCustomSchemaDefinition_asStrings(SchemaVersion schemaVersion, En } @Test - public void testRawEnumType() throws JSONException { + public void testRawEnumType_asString() throws JSONException { this.initConfigBuilder(SchemaVersion.DRAFT_2020_12); - // test all three modules at once, as they are all expected to be unable to detect values for the raw enum type + this.builder.with(Option.PUBLIC_NONSTATIC_FIELDS, Option.NONSTATIC_NONVOID_NONGETTER_METHODS); instanceAsStringsFromName.applyToConfigBuilder(this.builder); - instanceAsStringsFromToString.applyToConfigBuilder(this.builder); + + JsonNode enumSchema = new SchemaGenerator(this.builder.build()).generateSchema(TestType.class) + .get(SchemaKeyword.TAG_PROPERTIES.forVersion(SchemaVersion.DRAFT_2020_12)) + .get("rawEnum"); + JSONAssert.assertEquals("{\"type\":\"string\"}", enumSchema.toString(), JSONCompareMode.STRICT); + } + + @Test + public void testRawEnumType_asObject() throws JSONException { + this.initConfigBuilder(SchemaVersion.DRAFT_2020_12); + this.builder.with(Option.PUBLIC_NONSTATIC_FIELDS, Option.NONSTATIC_NONVOID_NONGETTER_METHODS); instanceAsObjects.applyToConfigBuilder(this.builder); JsonNode enumSchema = new SchemaGenerator(this.builder.build()).generateSchema(TestType.class) .get(SchemaKeyword.TAG_PROPERTIES.forVersion(SchemaVersion.DRAFT_2020_12)) .get("rawEnum"); - JSONAssert.assertEquals("{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"ordinal\":{\"type\":\"integer\"}}}", + JSONAssert.assertEquals("{\"type\":\"object\",\"properties\":{\"compareTo(Enum)\":{\"type\":\"integer\"},\"name()\":{\"type\":\"string\"}}}", enumSchema.toString(), JSONCompareMode.STRICT); }