diff --git a/docs/customization.md b/docs/customization.md index ba113c6705fc..5981132b6519 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -651,6 +651,13 @@ Example: java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/required-properties.yaml -o /tmp/java-okhttp/ --openapi-normalizer REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT=true ``` +- `REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING`: when set to true, oneOf is removed and is converted into mappings in a discriminator mapping. + +Example: +``` +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g spring -i modules/openapi-generator/src/test/resources/3_0/spring/issue_23527.yaml -o /tmp/java-spring/ --openapi-normalizer REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING=true +``` + - `FILTER` The `FILTER` parameter allows selective inclusion of API operations based on specific criteria. It applies the `x-internal: true` property to operations that do **not** match the specified values, preventing them from being generated. Multiple filters can be separated by a semicolon. diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index 95f793c195da..cb010967e826 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -78,6 +78,10 @@ public class OpenAPINormalizer { // are removed as most generators cannot handle such case at the moment final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY"; + // when set to true, oneOf is removed and is converted into mappings in a discriminator mapping + final String REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING = "REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING"; + + // when set to true, oneOf/anyOf with either string or enum string as sub schemas will be simplified // to just string final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING"; @@ -214,6 +218,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map inputRules) { ruleNames.add(SIMPLIFY_ONEOF_ANYOF_ENUM); ruleNames.add(REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT); ruleNames.add(SORT_MODEL_PROPERTIES); + ruleNames.add(REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING); // rules that are default to true rules.put(SIMPLIFY_ONEOF_ANYOF, true); @@ -639,6 +644,10 @@ protected void normalizeComponentsSchemas() { // normalize the schemas schemas.put(schemaName, normalizeSchema(schema, new HashSet<>())); + + if (getRule(REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING)) { + ensureInheritanceForDiscriminatorMappings(schema, schemaName); + } } } } @@ -1053,6 +1062,8 @@ protected Schema normalizeOneOf(Schema schema, Set visitedSchemas) { // simplify first as the schema may no longer be a oneOf after processing the rule below schema = processSimplifyOneOf(schema); + schema = processReplaceOneOfByMapping(schema); + // if it's still a oneOf, loop through the sub-schemas if (schema.getOneOf() != null) { for (int i = 0; i < schema.getOneOf().size(); i++) { @@ -1569,6 +1580,245 @@ protected Schema processSimplifyOneOf(Schema schema) { return schema; } + + /** + * Ensure inheritance is correctly defined for OneOf and Discriminators. + * + * For schemas containing oneOf and discriminator.propertyName: + * + */ + protected Schema processReplaceOneOfByMapping(Schema schema) { + if (!getRule(REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING) || schema.getOneOf() == null) { + return schema; + } + Discriminator discriminator = schema.getDiscriminator(); + if (discriminator != null) { + boolean inlineSchema = isInlineSchema(schema); + if (inlineSchema) { + // the For referenced schemas, ensure that there is an allOf with this schema. + LOGGER.warn("Inline oneOf schema not supported by REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING normalization"); + return schema; + } + if (discriminator.getMapping() == null && discriminator.getPropertyName() != null) { + List oneOfs = schema.getOneOf(); + if (oneOfs.stream().anyMatch(oneOf -> oneOf.get$ref() == null)) { + LOGGER.warn("oneOf should only contain $ref for REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING normalization"); + return schema; + } + Map mappings = new TreeMap<>(); + // is the discriminator qttribute qlready in this schema? + // if yes, it will be deleted in references oneOf to avoid duplicates + boolean hasProperty = findProperty(schema, discriminator.getPropertyName(), false, new HashSet<>()) != null; + discriminator.setMapping(mappings); + for (Schema oneOf : oneOfs) { + String refSchema = oneOf.get$ref(); + String name = getDiscriminatorValue(refSchema, discriminator.getPropertyName(), hasProperty, new HashSet<>(List.of(schema))); + mappings.put(name, refSchema); + + } + // remove oneOf and only keep the new discriminator mapping + schema.setOneOf(null); + } else if (discriminator.getPropertyName() == null) { + LOGGER.warn("Missing property name in discriminator"); + } else if (discriminator.getMapping() != null && discriminator.getMapping().size() != schema.getOneOf().size()) { + LOGGER.warn("Discriminator mapping size " + discriminator.getMapping().size() + " mismatch with oneOf size " + schema.getOneOf().size()); + } else { + // remove oneOf and only keep the discriminator mapping + LOGGER.info("Removing oneOf, discriminator mapping takes precedences on OneOfs"); + schema.setOneOf(null); + } + } + + return schema; + } + + private boolean isInlineSchema(Schema schema) { + if (openAPI.getComponents()!=null && openAPI.getComponents().getSchemas()!=null) { + int identity = System.identityHashCode(schema); + for (Schema componentSchema: openAPI.getComponents().getSchemas().values()) { + if (System.identityHashCode(componentSchema) == identity) { + return false; + } + } + } + return true; + } + + /** + * Best effort to retrieve a good discriminator value. + * By order of precedence: + *
    + *
  • x-discriminator-value
  • + *
  • single enum value for attribute used by the discriminator.propertyName
  • + *
  • hame of the schema
  • + *
+ * + * @param refSchema $ref value like #/components/schemas/Dog + * @param discriminatorPropertyName name of the property used in the discriminator mapping + * @param propertyAlreadyPresent if true, delete the property in the referenced schemas to avoid duplicates + * + * @return the name + */ + protected String getDiscriminatorValue(String refSchema, String discriminatorPropertyName, boolean propertyAlreadyPresent, Set visitedSchemas) { + String schemaName = ModelUtils.getSimpleRef(refSchema); + Schema schema = ModelUtils.getSchema(openAPI, schemaName); + Schema property = findProperty(schema, discriminatorPropertyName, propertyAlreadyPresent, visitedSchemas); + if (schema != null && schema.getExtensions() != null) { + Object discriminatorValue = schema.getExtensions().get("x-discriminator-value"); + if (discriminatorValue != null) { + return discriminatorValue.toString(); + } + } + + // find the discriminator value as a unique enum value + property = ModelUtils.getReferencedSchema(openAPI, property); + if (property != null) { + List enums = property.getEnum(); + if (enums != null && enums.size() == 1) { + return enums.get(0).toString(); + } + } + + return schemaName; + } + + /** + * find a property under the schema. + * + * @param schema + * @param propertyName property to find + * @param toDelete if true delete the found property + * @param visitedSchemas avoid infinite recursion + * @return found property or null if not found. + */ + private Schema findProperty(Schema schema, String propertyName, boolean toDelete, Set visitedSchemas) { + schema = ModelUtils.getReferencedSchema(openAPI, schema); + if (propertyName == null || schema == null || visitedSchemas.contains(schema)) { + return null; + } + visitedSchemas.add(schema); + Map properties = schema.getProperties(); + if (properties != null) { + Schema property = ModelUtils.getReferencedSchema(openAPI, properties.get(propertyName)); + if (property != null) { + if (toDelete) { + if (schema.getProperties().remove(propertyName) != null) { + LOGGER.info("property " + propertyName + " has been removed in REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING normalization"); + if (schema.getProperties().isEmpty()) { + schema.setProperties(null); + } + } + } + return property; + } + } + List allOfs = schema.getAllOf(); + if (allOfs != null) { + for (Schema child : allOfs) { + Schema found = findProperty(child, propertyName, toDelete, visitedSchemas); + if (found != null) { + return found; + } + } + } + + return null; + } + + + /** + * ensure that all schemas referenced in the discriminator mapping has an allOf to the parent schema. + * + * This allows DefaultCodeGen to detect inheritance. + * + * @param parent parent schma + * @param parentName name of the parent schema + */ + protected void ensureInheritanceForDiscriminatorMappings(Schema parent, String parentName) { + Discriminator discriminator = parent.getDiscriminator(); + if (discriminator != null && discriminator.getMapping() != null) { + for (String mapping : discriminator.getMapping().values()) { + String refSchemaName = ModelUtils.getSimpleRef(mapping); + Schema child = ModelUtils.getSchema(openAPI, refSchemaName); + if (child != null) { + if (parentName != null) { + ensureInheritanceForDiscriminatorMapping(parent, child, parentName, new HashSet<>()); + } + } + } + } + } + + /** + * If not already present, add in the child an allOf referencing the parent. + */ + protected void ensureInheritanceForDiscriminatorMapping(Schema parent, Schema child, String parentName, Set visitedSchemas) { + String reference = "#/components/schemas/" + parentName; + List allOf = child.getAllOf(); + if (allOf != null) { + if (isParentReferencedInChild(parent, child, reference, visitedSchemas)) { + // already done, so no need to add + return; + } + Schema refToParent = new Schema<>().$ref(reference); + allOf.add(refToParent); + } else { + allOf = new ArrayList<>(); + child.setAllOf(allOf); + Schema refToParent = new Schema<>().$ref(reference); + allOf.add(refToParent); + Map childProperties = child.getProperties(); + if (childProperties != null) { + // move the properties inside the new allOf. + Schema newChildProperties = new Schema<>() + .properties(childProperties) + .additionalProperties(child.getAdditionalProperties()); + ModelUtils.copyMetadata(child, newChildProperties); + allOf.add(newChildProperties); + child.properties(null) + .type(null) + .additionalProperties(null) + .description(null) + ._default(null) + .deprecated(null) + .example(null) + .examples(null) + .readOnly(null) + .writeOnly(null) + .title(null); + } + } + } + + /** + * return true if the child as an allOf referencing the parent schema. + */ + private boolean isParentReferencedInChild(Schema parent, Schema child, String reference, Set visitedSchemas) { + if (child == null || visitedSchemas.contains(child)) { + return false; + } + if (child.get$ref() != null && child.get$ref().equals(reference)) { + return true; + } + child = ModelUtils.getReferencedSchema(openAPI, child); + if (visitedSchemas.contains(child)) { + return false; + } + visitedSchemas.add(child); + List allOf = child.getAllOf(); + if (allOf != null) { + for (Schema schema : allOf) { + if (isParentReferencedInChild(parent, schema, reference, visitedSchemas)) { + return true; + } + } + } + return false; + } + /** * Set nullable to true in array/set if needed. * diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index 84d8cf484f73..de529b43f976 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -17,8 +17,10 @@ package org.openapitools.codegen.utils; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.util.Yaml; import io.swagger.v3.core.util.AnnotationsUtils; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -2645,4 +2647,20 @@ public LinkedHashSet build() { } } } + + + /* + * Simplest dump of an openApi contract on the console. + * + * Only use for debugging. + */ + public static void dumpAsYaml(OpenAPI openAPI) { + ObjectMapper mapper = Yaml.mapper(); + try { + String yaml = mapper.writeValueAsString(openAPI); + System.out.println(yaml); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index 28829fb748d8..2e25bb53b85c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -26,7 +26,6 @@ import org.openapitools.codegen.utils.ModelUtils; import org.testng.annotations.Test; -import java.lang.reflect.Array; import java.math.BigDecimal; import java.util.*; @@ -1502,4 +1501,47 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { } } + @Test + public void testREPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/oneOf_issue_23527.yaml"); + + Map inputRules = Map.of("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); + + Schema geoJsonObject = openAPI.getComponents().getSchemas().get("GeoJsonObject"); + Map mapping = geoJsonObject.getDiscriminator().getMapping(); + assertEquals(mapping, Map.of("MultiPolygon", "#/components/schemas/Multi-Polygon", "Polygon", "#/components/schemas/Polygon" )); + } + + @Test + public void issue_14769() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/oneOf_issue_14769.yaml"); + Map inputRules = Map.of("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); +// ModelUtils.dumpAsYaml(openAPI); + Schema vehicle = openAPI.getComponents().getSchemas().get("Vehicle"); + Map mapping = vehicle.getDiscriminator().getMapping(); + assertEquals(mapping, Map.of("car", "#/components/schemas/Car", "plane", "#/components/schemas/Plane" )); + Schema car = openAPI.getComponents().getSchemas().get("Car"); + assertNull(car.getProperties()); + assertEquals(car.getAllOf().size(), 2); + assertEquals(((Schema)car.getAllOf().get(0)).get$ref(), "#/components/schemas/Vehicle"); + assertEquals(((Schema)car.getAllOf().get(1)).getProperties().size(), 1); + assertEquals(((Schema)car.getAllOf().get(1)).getProperties().keySet(), Set.of("has_4_wheel_drive")); + } + + @Test + public void oneOf_issue_23276() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/oneOf_issue_23276.yaml"); + Map inputRules = Map.of("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); +// ModelUtils.dumpAsYaml(openAPI); + Schema payload = (Schema)openAPI.getComponents().getSchemas().get("DeviceLifecycleEvent").getProperties().get("payload"); + // inline oneOf are not converted + assertNotNull(payload.getOneOf()); + } + } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index b34fce2ee042..41a010f1db54 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -4325,4 +4325,52 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe .fileContains("@org.jspecify.annotations.NullMarked"); } + + @DataProvider(name = "replaceOneOf") + public Object[][] replaceOneOf() { + return new Object[][]{ + {"src/test/resources/3_0/oneOf_issue_23527.yaml"}, + {"src/test/resources/3_0/oneOf_issue_23527_1.yaml"}, + {"src/test/resources/3_0/oneOf_issue_23527_2.yaml"} + }; + } + + @Test(dataProvider = "replaceOneOf" ) + void replaceOneOfByDiscriminatorMapping(String file) { + Map files = generateFromContract(file, APACHE, Map.of(), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + + JavaFileAssert.assertThat(files.get("GeoJsonObject.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileContains("String type") + .fileDoesNotContain("coordinates") + .assertTypeAnnotations() + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "MultiPolygon.class", "name", "\"MultiPolygon\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Polygon.class", "name", "\"Polygon\"")); + + JavaFileAssert.assertThat(files.get("Polygon.java")) + .extendsClass("GeoJsonObject") + .doesNotImplementInterfaces("GeoJsonObject") + .fileContains("List coordinates"); + } + + @Test + void oneOf_issue_912() { + Map files = generateFromContract("src/test/resources/3_0/java/issue_912.yaml", APACHE, + Map.of(), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true") + .addInlineSchemaOption("REFACTOR_ALLOF_INLINE_SCHEMAS", "true")); + JavaFileAssert.assertThat(files.get("CatalogEntity.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileContains("String entityType") + .assertTypeAnnotations() + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("include", "JsonTypeInfo.As.PROPERTY", "property", "\"entityType\"")) + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Folder.class", "name", "\"folder\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Source.class", "name", "\"source\"")); + } + } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java index 2275945f64c9..22c1027aef25 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java @@ -99,6 +99,14 @@ public ACTUAL recursivelyContainsWithName(String name) { return myself(); } + public ACTUAL recursivelyContainsWithNameAndAttributes(String name, Map attributes) { + super + .withFailMessage("Should have annotation with name: " + name) + .anyMatch(annotation -> containsSpecificAnnotationNameAndAttributes(annotation, name, attributes)); + + return myself(); + } + private boolean containsSpecificAnnotationName(Node node, String name) { if (node == null || name == null) return false; @@ -118,4 +126,27 @@ private boolean containsSpecificAnnotationName(Node node, String name) { return false; } + + private boolean containsSpecificAnnotationNameAndAttributes(Node node, String name, Map attributes) { + if (node == null || name == null) + return false; + + if (node instanceof AnnotationExpr) { + AnnotationExpr annotation = (AnnotationExpr) node; + + if (annotation.getNameAsString().equals(name)) + + if (hasAttributes(annotation, attributes)) { + return true; + } + + } + + for(Node child: node.getChildNodes()) { + if (containsSpecificAnnotationNameAndAttributes(child, name, attributes)) + return true; + } + + return false; + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java index db79fecc3128..df14326125e6 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java @@ -2,6 +2,8 @@ import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.nodeTypes.NodeWithName; import com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAbstractModifier; @@ -59,6 +61,43 @@ public JavaFileAssert isNormalClass() { return this; } + public JavaFileAssert extendsClass(String parentClass) { + String actualParent = actual.getType(0) + .asClassOrInterfaceDeclaration().getExtendedTypes() + .stream() + .filter(JavaFileAssert::isClass) + .map(ClassOrInterfaceType::getNameWithScope) + .findFirst() + .orElse(null); + + Assertions.assertThat(actualParent) + .withFailMessage("Expected type %s to extends %s, but found %s", + actual.getType(0).getName().asString(), parentClass, actualParent) + .isEqualTo(parentClass); + return this; + } + + private static boolean isClass(ClassOrInterfaceType cit) { + return cit.asClassOrInterfaceType().getParentNode() + .map(node -> node instanceof ClassOrInterfaceDeclaration && !((ClassOrInterfaceDeclaration)node).isInterface()) + .orElse(false); + } + + public JavaFileAssert doesNotExtendsClasses() { + String actualParent = actual.getType(0) + .asClassOrInterfaceDeclaration().getExtendedTypes() + .stream() + .filter(JavaFileAssert::isClass) + .map(ClassOrInterfaceType::getNameWithScope) + .findFirst() + .orElse(null); + Assertions.assertThat(actualParent) + .withFailMessage("Expected type %s to extends a class, but found %s", + actual.getType(0).getName().asString(), actualParent) + .isNull(); + return this; + } + public JavaFileAssert implementsInterfaces(String... implementedInterfaces) { Set expectedInterfaces = Stream.of(implementedInterfaces) .collect(Collectors.toSet()); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index 96444d9aee77..5d05199926c4 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -59,6 +59,7 @@ import static java.util.stream.Collectors.groupingBy; import static org.assertj.core.api.Assertions.assertThat; +import static org.openapitools.codegen.CodegenConstants.*; import static org.openapitools.codegen.TestUtils.*; import static org.openapitools.codegen.languages.AbstractJavaCodegen.GENERATE_BUILDERS; import static org.openapitools.codegen.languages.AbstractJavaCodegen.GENERATE_CONSTRUCTOR_WITH_ALL_ARGS; @@ -1978,45 +1979,33 @@ public void testOneOf5381() throws IOException { @Test public void testOneOfAndAllOf() throws IOException { - File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); - output.deleteOnExit(); - String outputPath = output.getAbsolutePath().replace('\\', '/'); - OpenAPI openAPI = new OpenAPIParser() - .readLocation("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", null, new ParseOptions()).getOpenAPI(); - - SpringCodegen codegen = new SpringCodegen(); - codegen.setOutputDir(output.getAbsolutePath()); - codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); - codegen.setUseOneOfInterfaces(true); - - ClientOptInput input = new ClientOptInput(); - input.openAPI(openAPI); - input.config(codegen); - - DefaultGenerator generator = new DefaultGenerator(); - codegen.setHateoas(true); - generator.setGenerateMetadata(false); // skip metadata and ↓ only generate models - generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); - generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); - generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); - generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); - - codegen.setUseOneOfInterfaces(true); - codegen.setUseDeductionForOneOfInterfaces(true); - codegen.setLegacyDiscriminatorBehavior(false); - - generator.opts(input).generate(); - + Map files = generateFromContract("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", SPRING_BOOT, + Map.of(HATEOAS, true, GENERATE_MODEL_TESTS, false, GENERATE_MODEL_DOCS, false, LEGACY_DISCRIMINATOR_BEHAVIOR, false, + AbstractJavaCodegen.USE_ONE_OF_INTERFACES, true, + USE_DEDUCTION_FOR_ONE_OF_INTERFACES, true, + CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true") + ); + JavaFileAssert.assertThat(files.get("Fruit.java")) + .isInterface() + .assertTypeAnnotations().containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Apple.class", "name", "\"APPLE\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Banana.class", "name", "\"BANANA\"")); // test deduction - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Animal.java"), "@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)", "@JsonSubTypes.Type(value = Dog.class),", "@JsonSubTypes.Type(value = Cat.class)"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public class Foo extends Entity implements FooRefOrValue"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public class FooRef extends EntityRef implements FooRefOrValue"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue"); + JavaFileAssert.assertThat(files.get("Animal.java")) + .isInterface() + .assertTypeAnnotations().containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Dog.class")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Cat.class")) + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("use", "JsonTypeInfo.Id.DEDUCTION")); + + assertFileContains(files.get("Foo.java").toPath(), "public class Foo extends Entity implements FooRefOrValue"); + assertFileContains(files.get("FooRef.java").toPath(), "public class FooRef extends EntityRef implements FooRefOrValue"); + assertFileContains(files.get("FooRefOrValue.java").toPath(), "public interface FooRefOrValue"); // previous bugs - JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java")) + JavaFileAssert.assertThat(files.get("BarRef.java")) .fileDoesNotContain("atTypesuper.hashCode", "private String atBaseType"); // imports for inherited properties - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); + assertFileContains(files.get("PizzaSpeziale.java").toPath(), "import java.math.BigDecimal"); } @Test @@ -6664,4 +6653,132 @@ public void testJspecify(String library, int springBootVersion, String fooApiFil JavaFileAssert.assertThat(files.get("model/package-info.java")) .fileContains("@org.jspecify.annotations.NullMarked"); } + + + @DataProvider(name = "replaceOneOf") + public Object[][] replaceOneOf() { + return new Object[][]{ + {"src/test/resources/3_0/oneOf_issue_23527.yaml"}, + {"src/test/resources/3_0/oneOf_issue_23527_1.yaml"}, + {"src/test/resources/3_0/oneOf_issue_23527_2.yaml"} + }; + } + + @Test(dataProvider = "replaceOneOf" ) + void replaceOneOfByDiscriminatorMapping(String file) throws IOException { + Map files = generateFromContract(file, SPRING_BOOT, + Map.of(GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + + JavaFileAssert.assertThat(files.get("GeoJsonObject.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileContains("String type") + .fileDoesNotContain("coordinates") + .assertTypeAnnotations() + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("include", "JsonTypeInfo.As.PROPERTY", "property", "\"type\"")) + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "MultiPolygon.class", "name", "\"MultiPolygon\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "Polygon.class", "name", "\"Polygon\"")); + ; + + JavaFileAssert.assertThat(files.get("Polygon.java")) + .extendsClass("GeoJsonObject") + .fileDoesNotContain(" type;") + .doesNotImplementInterfaces("GeoJsonObject") + .fileContains("List coordinates") + .fileDoesNotContain("@JsonSubTypes"); + } + + @Test + void oneOf_issue_19261() throws IOException { + Map files = generateFromContract("src/test/resources/3_0/oneOf_issue_19261.yaml", SPRING_BOOT, + Map.of(GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + JavaFileAssert.assertThat(files.get("Product.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileContains("AboType type") + .assertTypeAnnotations() + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("include", "JsonTypeInfo.As.PROPERTY", "property", "\"type\"")) + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "HomeProduct.class", "name", "\"home\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "InternetProduct.class", "name", "\"internet\"")); + JavaFileAssert.assertThat(files.get("InternetProduct.java")) + .extendsClass("Product"); + } + + @Test + void oneOf_issue_22013() throws IOException { + Map files = generateFromContract("src/test/resources/3_0/oneOf_issue_22013.yaml", SPRING_BOOT, + Map.of(GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + JavaFileAssert.assertThat(files.get("Main.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileDoesNotContain("String jobType") + .assertTypeAnnotations() + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("include", "JsonTypeInfo.As.PROPERTY", "property", "\"jobType\"")) + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "A.class", "name", "\"A\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "B.class", "name", "\"B\"")); + JavaFileAssert.assertThat(files.get("B.java")) + .extendsClass("Main") + .fileContains("String jobType;"); + } + + @Test + void oneOf_issue_23577() throws IOException { + Map files = generateFromContract("src/test/resources/3_0/oneOf_issue_23577.yaml", SPRING_BOOT, + Map.of(GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + JavaFileAssert.assertThat(files.get("Event.java")) + .isNormalClass() + .doesNotExtendsClasses() + .fileDoesNotContain("String type") + .assertTypeAnnotations() + .containsWithNameAndAttributes("JsonTypeInfo", Map.of("include", "JsonTypeInfo.As.PROPERTY", "property", "\"type\"")) + .containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "CreatedEvent.class", "name", "\"created\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "UpdatedEvent.class", "name", "\"updated\"")); + JavaFileAssert.assertThat(files.get("CreatedEvent.java")) + .extendsClass("Event") + .implementsInterfaces("com.example.Notification") + .fileContains("String type;"); + } + + @Test + void oneof_polymorphism_and_inheritance() throws IOException { + Map files = generateFromContract("src/test/resources/3_0/oneof_polymorphism_and_inheritance.yaml", SPRING_BOOT, + Map.of(MODEL_NAME_SUFFIX, "Dto", + GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + JavaFileAssert.assertThat(files.get("FruitDto.java")) + .isNormalClass() + .assertTypeAnnotations().containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "AppleDto.class", "name", "\"APPLE\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "BananaDto.class", "name", "\"BANANA\"")); + + JavaFileAssert.assertThat(files.get("BananaDto.java")) + .isNormalClass() + .extendsClass("FruitDto"); + } + + @Test + void oneOf_issue_14769() throws IOException { + Map files = generateFromContract("src/test/resources/3_0/oneOf_issue_14769.yaml", SPRING_BOOT, + Map.of(MODEL_NAME_SUFFIX, "Dto", + GENERATE_MODEL_DOCS, false, GENERATE_APIS, false, INTERFACE_ONLY, true), + codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true")); + + JavaFileAssert.assertThat(files.get("VehicleDto.java")) + .isNormalClass() + .assertTypeAnnotations().containsWithName("JsonSubTypes") + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "CarDto.class", "name", "\"car\"")) + .recursivelyContainsWithNameAndAttributes("JsonSubTypes.Type", Map.of("value", "PlaneDto.class", "name", "\"plane\"")); + + JavaFileAssert.assertThat(files.get("CarDto.java")) + .isNormalClass() + .extendsClass("VehicleDto"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/java/issue_912.yaml b/modules/openapi-generator/src/test/resources/3_0/java/issue_912.yaml new file mode 100644 index 000000000000..fdbac0616860 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/java/issue_912.yaml @@ -0,0 +1,79 @@ +--- +openapi: 3.0.1 +info: + title: Mukul + version: '2.0' +paths: + "/catalog/{id}": + get: + operationId: getCatalogItem + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + default: + description: default response + content: + application/json: + schema: + "$ref": "#/components/schemas/CatalogEntity" +components: + schemas: + CatalogEntity: + type: object + properties: + id: + type: string + entityType: + type: string + discriminator: + propertyName: entityType + mapping: + folder: "#/components/schemas/Folder" + source: "#/components/schemas/Source" + oneOf: + - "$ref": "#/components/schemas/Folder" + - "$ref": "#/components/schemas/Source" + Folder: + type: object + properties: + path: + type: array + items: + type: string + tag: + type: string + children: + type: array + items: + "$ref": "#/components/schemas/CatalogItem" + allOf: + - "$ref": "#/components/schemas/CatalogEntity" + CatalogItem: + type: object + properties: + name: + type: string + Source: + type: object + properties: + tag: + type: string + type: + type: string + name: + type: string + description: + type: string + createdAt: + type: string + format: date-time + children: + type: array + items: + "$ref": "#/components/schemas/CatalogItem" + allOf: + - "$ref": "#/components/schemas/CatalogEntity" diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_14769.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_14769.yaml new file mode 100644 index 000000000000..a11ff04c8826 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_14769.yaml @@ -0,0 +1,49 @@ +openapi: 3.0.1 +info: + title: discriminator_enums + version: '1.0.0' +components: + schemas: + Vehicle: + type: object + required: + - id + - type + properties: + id: + type: integer + type: + type: string + model: + type: string + name: + type: string + oneOf: + - $ref: '#/components/schemas/Car' + - $ref: '#/components/schemas/Plane' + discriminator: + propertyName: type + + Car: + type: object + properties: + type: + enum: + - car + has_4_wheel_drive: + type: boolean + + Plane: + type: object + allOf: + - properties: + type: + $ref: '#/components/schemas/PlaneEnum' + has_reactor: + type: boolean + nb_passengers: + type: integer + PlaneEnum: + type: string + enum: + - plane \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_19261.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_19261.yaml new file mode 100644 index 000000000000..44a8223a8042 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_19261.yaml @@ -0,0 +1,77 @@ +openapi: 3.0.3 +info: + title: test + description: Test + version: "2.0" +servers: + - url: 'https://example.com' +security: + - APIKeyHeader: [] +paths: + /products/{identifier}: + get: + tags: + - Product Item + summary: Get single product + parameters: + - name: identifier + in: path + required: true + schema: + type: string + example: "100001951" + description: The product identifier + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Product' +components: + schemas: +#v + HomeProduct: + allOf: + - $ref: "#/components/schemas/Product" + - type: object + required: + - newField + properties: + newField: + type: string + InternetProduct: + allOf: + - $ref: "#/components/schemas/Product" + - type: object + required: + - extraField + properties: + extraField: + type: string + Product: + type: object + required: + - productId + - name + - type + properties: + productId: + type: string + name: + type: string + type: + $ref: '#/components/schemas/AboType' + oneOf: + - $ref: '#/components/schemas/HomeProduct' + - $ref: '#/components/schemas/InternetProduct' + discriminator: + propertyName: type + mapping: + home: '#/components/schemas/HomeProduct' + internet: '#/components/schemas/InternetProduct' + AboType: + type: string + enum: + - "internet" + - "home" \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_22013.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_22013.yaml new file mode 100644 index 000000000000..5901ceb6a282 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_22013.yaml @@ -0,0 +1,49 @@ +openapi: 3.0.1 +info: + title: some + version: 1.0.0 +servers: + - url: http://localhost:8080 + description: Local server +paths: + "/someEndponint": + get: + summary: Get the specified device job of a single device + description: | + Obtain a specified device job of a single device + + operationId: someEndpoint + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/main' + +components: + schemas: + main: + oneOf: + - "$ref": "#/components/schemas/A" + - "$ref": "#/components/schemas/B" + discriminator: + propertyName: jobType + mapping: + A: "#/components/schemas/A" + B: "#/components/schemas/B" + A: + type: object + properties: + jobType: + type: string + propertyA: + type: string + + B: + type: object + properties: + jobType: + type: string + propertyB: + type: string \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23276.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23276.yaml new file mode 100644 index 000000000000..e42904e90ec8 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23276.yaml @@ -0,0 +1,120 @@ +openapi: 3.0.3 +info: + version: v1 + title: Device service events +paths: {} +components: + schemas: + DeviceLifecycleEvent: + type: object + description: Notification about device lifecycle events + required: + - type + - header + - payload + properties: + type: + type: string + enum: + - device_datatransfer_status_changed + - device_communication_mode_status_changed + header: + $ref: "#/components/schemas/DeviceMessageHeader" + payload: +# $ref: "#/components/schemas/DeviceLifecycleEventPayload" +# DeviceLifecycleEventPayload: + oneOf: + - $ref: "#/components/schemas/DeviceDataTransferEventPayload" + - $ref: "#/components/schemas/DeviceCommModeStatusEventPayload" + discriminator: + propertyName: type + mapping: + device_datatransfer_status_changed: "#/components/schemas/DeviceDataTransferEventPayload" + device_communication_mode_status_changed: "#/components/schemas/DeviceCommModeStatusEventPayload" + + DeviceMessageHeader: + type: object + required: + - ts + - correlationId + - deviceId + properties: + correlationId: + type: string + deviceId: + type: string + ts: + type: string + format: date-time + + DeviceDataTransferEventPayload: + type: object + required: + - deviceId + - serialNumber + - updatedAt + - eventData + properties: + deviceId: + type: string + serialNumber: + type: string + updatedAt: + type: string + eventData: + $ref: "#/components/schemas/DeviceDataTransferEventData" + + DeviceCommModeStatusEventPayload: + type: object + required: + - deviceId + - serialNumber + - updatedAt + - eventData + properties: + deviceId: + type: string + serialNumber: + type: string + updatedAt: + type: string + eventData: + $ref: "#/components/schemas/DeviceCommModeStatusEventData" + + DeviceDataTransferEventData: + type: object + required: + - StatusDetails + properties: + StatusDetails: + type: object + properties: + dataTransfer: + type: object + properties: + status: + type: string + enum: [capable, incapable, Not Applicable] + statusReason: + type: string + updatedAt: + type: string + isMuted: + type: boolean + + DeviceCommModeStatusEventData: + type: object + required: + - StatusDetails + properties: + StatusDetails: + type: object + properties: + communicationMode: + type: object + properties: + runtimeCommMode: + type: string + enum: [limited, normal] + updatedAt: + type: string \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527.yaml new file mode 100644 index 000000000000..8c923f70cf08 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.3 +info: + title: GeoJSON Discriminator Test + version: 1.0.0 +paths: {} +components: + schemas: + GeoJsonObject: + type: object + properties: + type: + type: string + required: + - type + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/Polygon' + - $ref: '#/components/schemas/Multi-Polygon' + Polygon: + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: number + format: double + Multi-Polygon: + x-discriminator-value: MultiPolygon + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: array + items: + type: number + format: double \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_1.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_1.yaml new file mode 100644 index 000000000000..3d1fb4603b8e --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_1.yaml @@ -0,0 +1,40 @@ +openapi: 3.0.3 +info: + title: GeoJSON Discriminator Test + version: 1.0.0 +paths: {} +components: + schemas: + GeoJsonObject: + type: object + properties: + type: + type: string + required: + - type + discriminator: + propertyName: type + mapping: + Polygon: '#/components/schemas/Polygon' + MultiPolygon: '#/components/schemas/MultiPolygon' + oneOf: + - $ref: '#/components/schemas/Polygon' + - $ref: '#/components/schemas/MultiPolygon' + Polygon: + type: object + properties: + coordinates: + type: array + items: + type: number + format: double + MultiPolygon: + type: object + properties: + coordinates: + type: array + items: + type: array + items: + type: number + format: double \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_2.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_2.yaml new file mode 100644 index 000000000000..5465880c6753 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23527_2.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.3 +info: + title: GeoJSON Discriminator Test + version: 1.0.0 +paths: {} +components: + schemas: + GeoJsonObject: + type: object + properties: + type: + type: string + required: + - type + discriminator: + propertyName: type + mapping: + Polygon: '#/components/schemas/Polygon' + MultiPolygon: '#/components/schemas/MultiPolygon' + Polygon: + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: number + format: double + MultiPolygon: + allOf: + - $ref: '#/components/schemas/GeoJsonObject' + - type: object + properties: + coordinates: + type: array + items: + type: array + items: + type: number + format: double \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23577.yaml b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23577.yaml new file mode 100644 index 000000000000..544991ba6c64 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/oneOf_issue_23577.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.3 +info: + title: Issue reproducer + description: Issue reproducer + version: 0.1.0 +paths: + /reproducer: + get: + operationId: 'reproduce' + responses: + '200': + description: result + content: + application/json: + schema: + $ref: '#/components/schemas/Event' + +components: + schemas: + Event: + oneOf: + - $ref: '#/components/schemas/CreatedEvent' + - $ref: '#/components/schemas/UpdatedEvent' + discriminator: + propertyName: type + mapping: + created: '#/components/schemas/CreatedEvent' + updated: '#/components/schemas/UpdatedEvent' + + CreatedEvent: + x-implements: com.example.Notification + type: object + properties: + type: + type: string + value: + type: string + required: + - type + - value + + UpdatedEvent: + type: object + properties: + type: + type: string + value: + type: string + required: + - type + - value \ No newline at end of file diff --git a/pom.xml b/pom.xml index 23cbd292a01a..82f97a68c6cb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,15 @@ scm:git:git@github.com:openapitools/openapi-generator.git https://github.com/openapitools/openapi-generator + + modules/openapi-generator-core + modules/openapi-generator + modules/openapi-generator-cli + modules/openapi-generator-maven-plugin + + + + @@ -408,7 +417,6 @@ com.gradle develocity-maven-extension - ${develocity-maven-extension.version} @@ -1235,7 +1243,6 @@ 2.20.0 3.18.0 1.10.0 - 1.23.2 1.3.0 1.0.2 4.9.10