diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 434983fb5e06..3f0fd0f3585f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3098,6 +3098,7 @@ public CodegenModel fromModel(String name, Schema schema) { listOLists.add(m.requiredVars); listOLists.add(m.vars); listOLists.add(m.allVars); + listOLists.add(m.readWriteVars); for (List theseVars : listOLists) { for (CodegenProperty requiredVar : theseVars) { if (discPropName.equals(requiredVar.baseName)) { @@ -3131,6 +3132,63 @@ public CodegenModel fromModel(String name, Schema schema) { return m; } + /** + * Sets the default value for an enum discriminator property in the provided {@link CodegenModel}. + *

+ * If the model's discriminator is defined, this method identifies the discriminator properties among the model's + * variables and assigns the default value to reflect the corresponding enum value for the model type. + *

+ *

+ * Example: If the discriminator is for type `Animal`, and the model is `Cat`, the default value + * will be set to `Animal.Cat` for the properties that have the same name as the discriminator. + *

+ * + * @param model the {@link CodegenModel} whose discriminator property default value is to be set + */ + protected static void setEnumDiscriminatorDefaultValue(CodegenModel model) { + if (model.discriminator == null) { + return; + } + String discPropName = model.discriminator.getPropertyBaseName(); + Stream.of(model.requiredVars, model.vars, model.allVars, model.readWriteVars) + .flatMap(List::stream) + .filter(v -> discPropName.equals(v.baseName)) + .forEach(v -> v.defaultValue = getEnumValueForProperty(model.schemaName, model.discriminator, v)); + } + + /** + * Retrieves the appropriate default value for an enum discriminator property based on the model name. + *

+ * If the discriminator has a mapping defined, it attempts to find a mapping for the model name. + * Otherwise, it defaults to one of the allowable enum value associated with the property. + * If no suitable value is found, the original default value of the property is returned. + *

+ * + * @param modelName the name of the model to determine the default value for + * @param discriminator the {@link CodegenDiscriminator} containing the mapping and enum details + * @param var the {@link CodegenProperty} representing the discriminator property + * @return the default value for the enum discriminator property, or its original default value if none is found + */ + protected static String getEnumValueForProperty( + String modelName, CodegenDiscriminator discriminator, CodegenProperty var) { + if (!discriminator.getIsEnum() && !var.isEnum) { + return var.defaultValue; + } + Map mapping = Optional.ofNullable(discriminator.getMapping()).orElseGet(Collections::emptyMap); + for (Map.Entry e : mapping.entrySet()) { + String schemaName = e.getValue().indexOf('/') < 0 ? e.getValue() : ModelUtils.getSimpleRef(e.getValue()); + if (modelName.equals(schemaName)) { + return e.getKey(); + } + } + Object values = var.allowableValues.get("values"); + if (!(values instanceof List)) { + return var.defaultValue; + } + List valueList = (List) values; + return valueList.stream().filter(o -> o.equals(modelName)).map(o -> (String) o).findAny().orElse(var.defaultValue); + } + protected void SortModelPropertiesByRequiredFlag(CodegenModel model) { Comparator comparator = new Comparator() { @Override @@ -3201,15 +3259,19 @@ protected void setAddProps(Schema schema, IJsonSchemaValidationProperties proper * @param visitedSchemas A set of visited schema names */ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, String discPropName, Set visitedSchemas) { - if (visitedSchemas.contains(composedSchemaName)) { // recursive schema definition found + Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc); + String schemaName = Optional.ofNullable(composedSchemaName) + .or(() -> Optional.ofNullable(refSchema.getName())) + .or(() -> Optional.ofNullable(sc.get$ref()).map(ModelUtils::getSimpleRef)) + .orElseGet(sc::toString); + if (visitedSchemas.contains(schemaName)) { // recursive schema definition found return null; } else { - visitedSchemas.add(composedSchemaName); + visitedSchemas.add(schemaName); } - Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc); if (refSchema.getProperties() != null && refSchema.getProperties().get(discPropName) != null) { - Schema discSchema = (Schema) refSchema.getProperties().get(discPropName); + Schema discSchema = ModelUtils.getReferencedSchema(openAPI, (Schema)refSchema.getProperties().get(discPropName)); CodegenProperty cp = new CodegenProperty(); if (ModelUtils.isStringSchema(discSchema)) { cp.isString = true; @@ -3218,6 +3280,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, if (refSchema.getRequired() != null && refSchema.getRequired().contains(discPropName)) { cp.setRequired(true); } + cp.setIsEnum(discSchema.getEnum() != null && !discSchema.getEnum().isEmpty()); return cp; } if (ModelUtils.isComposedSchema(refSchema)) { @@ -3225,7 +3288,8 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, if (composedSchema.getAllOf() != null) { // If our discriminator is in one of the allOf schemas break when we find it for (Object allOf : composedSchema.getAllOf()) { - CodegenProperty cp = discriminatorFound(composedSchemaName, (Schema) allOf, discPropName, visitedSchemas); + Schema allOfSchema = (Schema) allOf; + CodegenProperty cp = discriminatorFound(allOfSchema.getName(), allOfSchema, discPropName, visitedSchemas); if (cp != null) { return cp; } @@ -3235,8 +3299,11 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, // All oneOf definitions must contain the discriminator CodegenProperty cp = new CodegenProperty(); for (Object oneOf : composedSchema.getOneOf()) { - String modelName = ModelUtils.getSimpleRef(((Schema) oneOf).get$ref()); - CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) oneOf, discPropName, visitedSchemas); + Schema oneOfSchema = (Schema) oneOf; + String modelName = ModelUtils.getSimpleRef((oneOfSchema).get$ref()); + // Must use a copied set as the oneOf schemas can point to the same discriminator. + Set visitedSchemasCopy = new TreeSet<>(visitedSchemas); + CodegenProperty thisCp = discriminatorFound(oneOfSchema.getName(), oneOfSchema, discPropName, visitedSchemasCopy); if (thisCp == null) { once(LOGGER).warn( "'{}' defines discriminator '{}', but the referenced OneOf schema '{}' is missing {}", @@ -3258,8 +3325,11 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, // All anyOf definitions must contain the discriminator because a min of one must be selected CodegenProperty cp = new CodegenProperty(); for (Object anyOf : composedSchema.getAnyOf()) { - String modelName = ModelUtils.getSimpleRef(((Schema) anyOf).get$ref()); - CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) anyOf, discPropName, visitedSchemas); + Schema anyOfSchema = (Schema) anyOf; + String modelName = ModelUtils.getSimpleRef(anyOfSchema.get$ref()); + // Must use a copied set as the anyOf schemas can point to the same discriminator. + Set visitedSchemasCopy = new TreeSet<>(visitedSchemas); + CodegenProperty thisCp = discriminatorFound(anyOfSchema.getName(), anyOfSchema, discPropName, visitedSchemasCopy); if (thisCp == null) { once(LOGGER).warn( "'{}' defines discriminator '{}', but the referenced AnyOf schema '{}' is missing {}", @@ -3542,13 +3612,11 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch discriminator.setPropertyType(propertyType); // check to see if the discriminator property is an enum string - if (schema.getProperties() != null && - schema.getProperties().get(discriminatorPropertyName) instanceof StringSchema) { - StringSchema s = (StringSchema) schema.getProperties().get(discriminatorPropertyName); - if (s.getEnum() != null && !s.getEnum().isEmpty()) { // it's an enum string - discriminator.setIsEnum(true); - } - } + boolean isEnum = Optional + .ofNullable(discriminatorFound(schemaName, schema, discriminatorPropertyName, new TreeSet<>())) + .map(CodegenProperty::getIsEnum) + .orElse(false); + discriminator.setIsEnum(isEnum); discriminator.setMapping(sourceDiscriminator.getMapping()); List uniqueDescendants = new ArrayList<>(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index 4c364a507258..2e29750d05c6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -1713,6 +1713,7 @@ public CodegenModel fromModel(String name, Schema model) { // additional import for different cases addAdditionalImports(codegenModel, codegenModel.getComposedSchemas()); + setEnumDiscriminatorDefaultValue(codegenModel); return codegenModel; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java index d7456a3f0bc5..1fe2a272caf0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java @@ -432,6 +432,7 @@ public String apiTestFileFolder() { public CodegenModel fromModel(String name, Schema model) { Map allDefinitions = ModelUtils.getSchemas(this.openAPI); CodegenModel codegenModel = super.fromModel(name, model); + setEnumDiscriminatorDefaultValue(codegenModel); if (allDefinitions != null && codegenModel != null && codegenModel.parent != null) { final Schema parentModel = allDefinitions.get(toModelName(codegenModel.parent)); if (parentModel != null) { 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 def572e2ba97..d9f77b3f5731 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 @@ -1611,6 +1611,12 @@ public static String getParentName(Schema composedSchema, Map al * @return the name of the parent model */ public static List getAllParentsName(Schema composedSchema, Map allSchemas, boolean includeAncestors) { + return getAllParentsName(composedSchema, allSchemas, includeAncestors, new HashSet<>()); + } + + // Use a set of seen names to avoid infinite recursion + private static List getAllParentsName( + Schema composedSchema, Map allSchemas, boolean includeAncestors, Set seenNames) { List interfaces = getInterfaces(composedSchema); List names = new ArrayList(); @@ -1619,6 +1625,10 @@ public static List getAllParentsName(Schema composedSchema, Map getAllParentsName(Schema composedSchema, Map results = default(Dictionary"); } + + @Test + public void testEnumDiscriminatorDefaultValueIsNotString() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/enum_discriminator_inheritance.yaml"); + final DefaultGenerator defaultGenerator = new DefaultGenerator(); + final ClientOptInput clientOptInput = new ClientOptInput(); + clientOptInput.openAPI(openAPI); + CSharpClientCodegen cSharpClientCodegen = new CSharpClientCodegen(); + cSharpClientCodegen.setOutputDir(output.getAbsolutePath()); + cSharpClientCodegen.setAutosetConstants(true); + clientOptInput.config(cSharpClientCodegen); + defaultGenerator.opts(clientOptInput); + + Map files = defaultGenerator.generate().stream() + .collect(Collectors.toMap(File::getPath, Function.identity())); + + Map expectedContents = Map.of( + "Cat", "PetTypeEnum petType = PetTypeEnum.Catty", + "Dog", "PetTypeEnum petType = PetTypeEnum.Dog", + "Gecko", "PetTypeEnum petType = PetTypeEnum.Gecko", + "Chameleon", "PetTypeEnum petType = PetTypeEnum.Camo", + "MiniVan", "CarType carType = CarType.MiniVan", + "CargoVan", "CarType carType = CarType.CargoVan", + "SUV", "CarType carType = CarType.SUV", + "Truck", "CarType carType = CarType.Truck", + "Sedan", "CarType carType = CarType.Sedan" + + ); + for (Map.Entry e : expectedContents.entrySet()) { + String modelName = e.getKey(); + String expectedContent = e.getValue(); + File file = files.get(Paths + .get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", modelName + ".cs") + .toString() + ); + assertNotNull(file, "Could not find file for model: " + modelName); + assertFileContains(file.toPath(), expectedContent); + } + } } 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 f050ffe78776..3ce56219fb49 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 @@ -2408,6 +2408,39 @@ public void testOpenapiGeneratorIgnoreListOption() { assertNull(files.get("pom.xml")); } + @Test + public void testEnumDiscriminatorDefaultValueIsNotString() { + final Path output = newTempFolder(); + final OpenAPI openAPI = TestUtils.parseFlattenSpec( + "src/test/resources/3_0/enum_discriminator_inheritance.yaml"); + JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setOutputDir(output.toString()); + + Map files = new DefaultGenerator().opts(new ClientOptInput().openAPI(openAPI).config(codegen)) + .generate().stream().collect(Collectors.toMap(File::getName, Function.identity())); + + Map expectedContents = Map.of( + "Cat", "this.petType = PetTypeEnum.CATTY", + "Dog", "this.petType = PetTypeEnum.DOG", + "Gecko", "this.petType = PetTypeEnum.GECKO", + "Chameleon", "this.petType = PetTypeEnum.CAMO", + "MiniVan", "this.carType = CarType.MINI_VAN", + "CargoVan", "this.carType = CarType.CARGO_VAN", + "SUV", "this.carType = CarType.SUV", + "Truck", "this.carType = CarType.TRUCK", + "Sedan", "this.carType = CarType.SEDAN" + + ); + for (Map.Entry e : expectedContents.entrySet()) { + String modelName = e.getKey(); + String expectedContent = e.getValue(); + File entityFile = files.get(modelName + ".java"); + assertNotNull(entityFile); + assertThat(entityFile).content().doesNotContain("Type = this.getClass().getSimpleName();"); + assertThat(entityFile).content().contains(expectedContent); + } + } + @Test public void testRestTemplateHandleURIEnum() { String[] expectedInnerEnumLines = new String[]{ diff --git a/modules/openapi-generator/src/test/resources/3_0/enum_discriminator_inheritance.yaml b/modules/openapi-generator/src/test/resources/3_0/enum_discriminator_inheritance.yaml new file mode 100644 index 000000000000..db89a48eeb09 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/enum_discriminator_inheritance.yaml @@ -0,0 +1,132 @@ +openapi: 3.0.3 +info: + title: Test schema with enum discriminator + version: v1 +paths: + "/animal": + get: + operationId: animalGet + responses: + '200': + description: OK + content: + application/json: + schema: + "$ref": "#/components/schemas/Animal" +components: + schemas: + Animal: + type: object + properties: + # Inline enum type + petType: + type: string + enum: + - Dog + - Catty + - Gecko + - Camo + discriminator: + propertyName: petType + # Mapping with implicit (Dog, Gecko), explicit ref (Catty -> Cat), and explicit schema name (Camo -> Chameleon) + mapping: + Catty: '#/components/schemas/Cat' + Camo: 'Chameleon' + required: + - petType + Cat: + type: object + allOf: + - $ref: '#/components/schemas/Animal' + properties: + meow: + type: string + Dog: + type: object + allOf: + - $ref: '#/components/schemas/Animal' + properties: + bark: + type: string + Lizard: + oneOf: + - $ref: '#/components/schemas/Gecko' + - $ref: '#/components/schemas/Chameleon' + Gecko: + type: object + allOf: + - $ref: '#/components/schemas/Animal' + properties: + lovesRocks: + type: string + Chameleon: + type: object + allOf: + - $ref: '#/components/schemas/Animal' + properties: + currentColor: + type: string + # Car inheritance tree: Car -> Truck -> SUV + # Car -> Van -> MiniVan + # Car -> Van -> CargoVan + # Car -> Sedan + Car: + type: object + required: + - carType + # Discriminator carType not defined in Car properties, but in child properties + discriminator: + propertyName: carType + CarType: + type: string + enum: + - Truck + - SUV + - Sedan + - MiniVan + - CargoVan + Truck: + type: object + allOf: + - $ref: '#/components/schemas/Car' + properties: + carType: + $ref: '#/components/schemas/CarType' + required: + - carType + SUV: + type: object + # SUV gets its discriminator property from Truck + allOf: + - $ref: '#/components/schemas/Truck' + Sedan: + type: object + allOf: + - $ref: '#/components/schemas/Car' + required: + - carType + properties: + carType: + $ref: '#/components/schemas/CarType' + Van: + oneOf: + - $ref: '#/components/schemas/MiniVan' + - $ref: '#/components/schemas/CargoVan' + MiniVan: + type: object + allOf: + - $ref: '#/components/schemas/Car' + properties: + carType: + $ref: '#/components/schemas/CarType' + required: + - carType + CargoVan: + type: object + allOf: + - $ref: '#/components/schemas/Car' + properties: + carType: + $ref: '#/components/schemas/CarType' + required: + - carType diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/EnumStringDiscriminator.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/EnumStringDiscriminator.java index 9ddf75d0afce..7b0f03493334 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/EnumStringDiscriminator.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/EnumStringDiscriminator.java @@ -108,6 +108,7 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti protected EnumStrTypeEnum enumStrType; public EnumStringDiscriminator() { + } public EnumStringDiscriminator enumStrType(@javax.annotation.Nonnull EnumStrTypeEnum enumStrType) {