Skip to content

Commit c75fbb3

Browse files
[csharp][java] Fix enum discriminator default value (#19614)
* Fix enum discriminator default value * Remove system out call * Add case when discriminator type is ref * Use correct schema * Handle different use cases of mappings * Add missing enum type Lizzy * Make it more robust * Add missing test for Sedan * Refactor some code to make it cleaner * Initialize discriminator enum field * Don't override existing default value * Fix issue with finding discriminators * Move setIsEnum back to its original location * Be smarter about figuring out the model name * Fix final warnings * Add javadocs to introduced methods
1 parent 1fa07bf commit c75fbb3

File tree

9 files changed

+310
-17
lines changed

9 files changed

+310
-17
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

+84-16
Original file line numberDiff line numberDiff line change
@@ -3098,6 +3098,7 @@ public CodegenModel fromModel(String name, Schema schema) {
30983098
listOLists.add(m.requiredVars);
30993099
listOLists.add(m.vars);
31003100
listOLists.add(m.allVars);
3101+
listOLists.add(m.readWriteVars);
31013102
for (List<CodegenProperty> theseVars : listOLists) {
31023103
for (CodegenProperty requiredVar : theseVars) {
31033104
if (discPropName.equals(requiredVar.baseName)) {
@@ -3131,6 +3132,63 @@ public CodegenModel fromModel(String name, Schema schema) {
31313132
return m;
31323133
}
31333134

3135+
/**
3136+
* Sets the default value for an enum discriminator property in the provided {@link CodegenModel}.
3137+
* <p>
3138+
* If the model's discriminator is defined, this method identifies the discriminator properties among the model's
3139+
* variables and assigns the default value to reflect the corresponding enum value for the model type.
3140+
* </p>
3141+
* <p>
3142+
* Example: If the discriminator is for type `Animal`, and the model is `Cat`, the default value
3143+
* will be set to `Animal.Cat` for the properties that have the same name as the discriminator.
3144+
* </p>
3145+
*
3146+
* @param model the {@link CodegenModel} whose discriminator property default value is to be set
3147+
*/
3148+
protected static void setEnumDiscriminatorDefaultValue(CodegenModel model) {
3149+
if (model.discriminator == null) {
3150+
return;
3151+
}
3152+
String discPropName = model.discriminator.getPropertyBaseName();
3153+
Stream.of(model.requiredVars, model.vars, model.allVars, model.readWriteVars)
3154+
.flatMap(List::stream)
3155+
.filter(v -> discPropName.equals(v.baseName))
3156+
.forEach(v -> v.defaultValue = getEnumValueForProperty(model.schemaName, model.discriminator, v));
3157+
}
3158+
3159+
/**
3160+
* Retrieves the appropriate default value for an enum discriminator property based on the model name.
3161+
* <p>
3162+
* If the discriminator has a mapping defined, it attempts to find a mapping for the model name.
3163+
* Otherwise, it defaults to one of the allowable enum value associated with the property.
3164+
* If no suitable value is found, the original default value of the property is returned.
3165+
* </p>
3166+
*
3167+
* @param modelName the name of the model to determine the default value for
3168+
* @param discriminator the {@link CodegenDiscriminator} containing the mapping and enum details
3169+
* @param var the {@link CodegenProperty} representing the discriminator property
3170+
* @return the default value for the enum discriminator property, or its original default value if none is found
3171+
*/
3172+
protected static String getEnumValueForProperty(
3173+
String modelName, CodegenDiscriminator discriminator, CodegenProperty var) {
3174+
if (!discriminator.getIsEnum() && !var.isEnum) {
3175+
return var.defaultValue;
3176+
}
3177+
Map<String, String> mapping = Optional.ofNullable(discriminator.getMapping()).orElseGet(Collections::emptyMap);
3178+
for (Map.Entry<String, String> e : mapping.entrySet()) {
3179+
String schemaName = e.getValue().indexOf('/') < 0 ? e.getValue() : ModelUtils.getSimpleRef(e.getValue());
3180+
if (modelName.equals(schemaName)) {
3181+
return e.getKey();
3182+
}
3183+
}
3184+
Object values = var.allowableValues.get("values");
3185+
if (!(values instanceof List<?>)) {
3186+
return var.defaultValue;
3187+
}
3188+
List<?> valueList = (List<?>) values;
3189+
return valueList.stream().filter(o -> o.equals(modelName)).map(o -> (String) o).findAny().orElse(var.defaultValue);
3190+
}
3191+
31343192
protected void SortModelPropertiesByRequiredFlag(CodegenModel model) {
31353193
Comparator<CodegenProperty> comparator = new Comparator<CodegenProperty>() {
31363194
@Override
@@ -3201,15 +3259,19 @@ protected void setAddProps(Schema schema, IJsonSchemaValidationProperties proper
32013259
* @param visitedSchemas A set of visited schema names
32023260
*/
32033261
private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, String discPropName, Set<String> visitedSchemas) {
3204-
if (visitedSchemas.contains(composedSchemaName)) { // recursive schema definition found
3262+
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
3263+
String schemaName = Optional.ofNullable(composedSchemaName)
3264+
.or(() -> Optional.ofNullable(refSchema.getName()))
3265+
.or(() -> Optional.ofNullable(sc.get$ref()).map(ModelUtils::getSimpleRef))
3266+
.orElseGet(sc::toString);
3267+
if (visitedSchemas.contains(schemaName)) { // recursive schema definition found
32053268
return null;
32063269
} else {
3207-
visitedSchemas.add(composedSchemaName);
3270+
visitedSchemas.add(schemaName);
32083271
}
32093272

3210-
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
32113273
if (refSchema.getProperties() != null && refSchema.getProperties().get(discPropName) != null) {
3212-
Schema discSchema = (Schema) refSchema.getProperties().get(discPropName);
3274+
Schema discSchema = ModelUtils.getReferencedSchema(openAPI, (Schema)refSchema.getProperties().get(discPropName));
32133275
CodegenProperty cp = new CodegenProperty();
32143276
if (ModelUtils.isStringSchema(discSchema)) {
32153277
cp.isString = true;
@@ -3218,14 +3280,16 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
32183280
if (refSchema.getRequired() != null && refSchema.getRequired().contains(discPropName)) {
32193281
cp.setRequired(true);
32203282
}
3283+
cp.setIsEnum(discSchema.getEnum() != null && !discSchema.getEnum().isEmpty());
32213284
return cp;
32223285
}
32233286
if (ModelUtils.isComposedSchema(refSchema)) {
32243287
Schema composedSchema = refSchema;
32253288
if (composedSchema.getAllOf() != null) {
32263289
// If our discriminator is in one of the allOf schemas break when we find it
32273290
for (Object allOf : composedSchema.getAllOf()) {
3228-
CodegenProperty cp = discriminatorFound(composedSchemaName, (Schema) allOf, discPropName, visitedSchemas);
3291+
Schema allOfSchema = (Schema) allOf;
3292+
CodegenProperty cp = discriminatorFound(allOfSchema.getName(), allOfSchema, discPropName, visitedSchemas);
32293293
if (cp != null) {
32303294
return cp;
32313295
}
@@ -3235,8 +3299,11 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
32353299
// All oneOf definitions must contain the discriminator
32363300
CodegenProperty cp = new CodegenProperty();
32373301
for (Object oneOf : composedSchema.getOneOf()) {
3238-
String modelName = ModelUtils.getSimpleRef(((Schema) oneOf).get$ref());
3239-
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) oneOf, discPropName, visitedSchemas);
3302+
Schema oneOfSchema = (Schema) oneOf;
3303+
String modelName = ModelUtils.getSimpleRef((oneOfSchema).get$ref());
3304+
// Must use a copied set as the oneOf schemas can point to the same discriminator.
3305+
Set<String> visitedSchemasCopy = new TreeSet<>(visitedSchemas);
3306+
CodegenProperty thisCp = discriminatorFound(oneOfSchema.getName(), oneOfSchema, discPropName, visitedSchemasCopy);
32403307
if (thisCp == null) {
32413308
once(LOGGER).warn(
32423309
"'{}' defines discriminator '{}', but the referenced OneOf schema '{}' is missing {}",
@@ -3258,8 +3325,11 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
32583325
// All anyOf definitions must contain the discriminator because a min of one must be selected
32593326
CodegenProperty cp = new CodegenProperty();
32603327
for (Object anyOf : composedSchema.getAnyOf()) {
3261-
String modelName = ModelUtils.getSimpleRef(((Schema) anyOf).get$ref());
3262-
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) anyOf, discPropName, visitedSchemas);
3328+
Schema anyOfSchema = (Schema) anyOf;
3329+
String modelName = ModelUtils.getSimpleRef(anyOfSchema.get$ref());
3330+
// Must use a copied set as the anyOf schemas can point to the same discriminator.
3331+
Set<String> visitedSchemasCopy = new TreeSet<>(visitedSchemas);
3332+
CodegenProperty thisCp = discriminatorFound(anyOfSchema.getName(), anyOfSchema, discPropName, visitedSchemasCopy);
32633333
if (thisCp == null) {
32643334
once(LOGGER).warn(
32653335
"'{}' defines discriminator '{}', but the referenced AnyOf schema '{}' is missing {}",
@@ -3542,13 +3612,11 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
35423612
discriminator.setPropertyType(propertyType);
35433613

35443614
// check to see if the discriminator property is an enum string
3545-
if (schema.getProperties() != null &&
3546-
schema.getProperties().get(discriminatorPropertyName) instanceof StringSchema) {
3547-
StringSchema s = (StringSchema) schema.getProperties().get(discriminatorPropertyName);
3548-
if (s.getEnum() != null && !s.getEnum().isEmpty()) { // it's an enum string
3549-
discriminator.setIsEnum(true);
3550-
}
3551-
}
3615+
boolean isEnum = Optional
3616+
.ofNullable(discriminatorFound(schemaName, schema, discriminatorPropertyName, new TreeSet<>()))
3617+
.map(CodegenProperty::getIsEnum)
3618+
.orElse(false);
3619+
discriminator.setIsEnum(isEnum);
35523620

35533621
discriminator.setMapping(sourceDiscriminator.getMapping());
35543622
List<MappedModel> uniqueDescendants = new ArrayList<>();

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

+1
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,7 @@ public CodegenModel fromModel(String name, Schema model) {
17131713

17141714
// additional import for different cases
17151715
addAdditionalImports(codegenModel, codegenModel.getComposedSchemas());
1716+
setEnumDiscriminatorDefaultValue(codegenModel);
17161717
return codegenModel;
17171718
}
17181719

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java

+1
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ public String apiTestFileFolder() {
432432
public CodegenModel fromModel(String name, Schema model) {
433433
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
434434
CodegenModel codegenModel = super.fromModel(name, model);
435+
setEnumDiscriminatorDefaultValue(codegenModel);
435436
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null) {
436437
final Schema<?> parentModel = allDefinitions.get(toModelName(codegenModel.parent));
437438
if (parentModel != null) {

modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,12 @@ public static String getParentName(Schema composedSchema, Map<String, Schema> al
16111611
* @return the name of the parent model
16121612
*/
16131613
public static List<String> getAllParentsName(Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors) {
1614+
return getAllParentsName(composedSchema, allSchemas, includeAncestors, new HashSet<>());
1615+
}
1616+
1617+
// Use a set of seen names to avoid infinite recursion
1618+
private static List<String> getAllParentsName(
1619+
Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors, Set<String> seenNames) {
16141620
List<Schema> interfaces = getInterfaces(composedSchema);
16151621
List<String> names = new ArrayList<String>();
16161622

@@ -1619,6 +1625,10 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
16191625
// get the actual schema
16201626
if (StringUtils.isNotEmpty(schema.get$ref())) {
16211627
String parentName = getSimpleRef(schema.get$ref());
1628+
if (seenNames.contains(parentName)) {
1629+
continue;
1630+
}
1631+
seenNames.add(parentName);
16221632
Schema s = allSchemas.get(parentName);
16231633
if (s == null) {
16241634
LOGGER.error("Failed to obtain schema from {}", parentName);
@@ -1627,7 +1637,7 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
16271637
// discriminator.propertyName is used or x-parent is used
16281638
names.add(parentName);
16291639
if (includeAncestors && isComposedSchema(s)) {
1630-
names.addAll(getAllParentsName(s, allSchemas, true));
1640+
names.addAll(getAllParentsName(s, allSchemas, true, seenNames));
16311641
}
16321642
} else {
16331643
// not a parent since discriminator.propertyName is not set

modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/pojo.mustache

+5
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
8181
{{/parcelableModel}}
8282
{{/parent}}
8383
{{#discriminator}}
84+
{{#discriminator.isEnum}}
85+
{{#readWriteVars}}{{#isDiscriminator}}{{#defaultValue}}
86+
this.{{name}} = {{defaultValue}};
87+
{{/defaultValue}}{{/isDiscriminator}}{{/readWriteVars}}
88+
{{/discriminator.isEnum}}
8489
{{^discriminator.isEnum}}
8590
this.{{{discriminatorName}}} = this.getClass().getSimpleName();
8691
{{/discriminator.isEnum}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/csharpnetcore/CSharpClientCodegenTest.java

+42
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,46 @@ public void test31specAdditionalPropertiesOfOneOf() throws IOException {
140140
assertFileContains(modelFile.toPath(),
141141
" Dictionary<string, ResponseResultsValue> results = default(Dictionary<string, ResponseResultsValue>");
142142
}
143+
144+
@Test
145+
public void testEnumDiscriminatorDefaultValueIsNotString() throws IOException {
146+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
147+
output.deleteOnExit();
148+
final OpenAPI openAPI = TestUtils.parseFlattenSpec(
149+
"src/test/resources/3_0/enum_discriminator_inheritance.yaml");
150+
final DefaultGenerator defaultGenerator = new DefaultGenerator();
151+
final ClientOptInput clientOptInput = new ClientOptInput();
152+
clientOptInput.openAPI(openAPI);
153+
CSharpClientCodegen cSharpClientCodegen = new CSharpClientCodegen();
154+
cSharpClientCodegen.setOutputDir(output.getAbsolutePath());
155+
cSharpClientCodegen.setAutosetConstants(true);
156+
clientOptInput.config(cSharpClientCodegen);
157+
defaultGenerator.opts(clientOptInput);
158+
159+
Map<String, File> files = defaultGenerator.generate().stream()
160+
.collect(Collectors.toMap(File::getPath, Function.identity()));
161+
162+
Map<String, String> expectedContents = Map.of(
163+
"Cat", "PetTypeEnum petType = PetTypeEnum.Catty",
164+
"Dog", "PetTypeEnum petType = PetTypeEnum.Dog",
165+
"Gecko", "PetTypeEnum petType = PetTypeEnum.Gecko",
166+
"Chameleon", "PetTypeEnum petType = PetTypeEnum.Camo",
167+
"MiniVan", "CarType carType = CarType.MiniVan",
168+
"CargoVan", "CarType carType = CarType.CargoVan",
169+
"SUV", "CarType carType = CarType.SUV",
170+
"Truck", "CarType carType = CarType.Truck",
171+
"Sedan", "CarType carType = CarType.Sedan"
172+
173+
);
174+
for (Map.Entry<String, String> e : expectedContents.entrySet()) {
175+
String modelName = e.getKey();
176+
String expectedContent = e.getValue();
177+
File file = files.get(Paths
178+
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", modelName + ".cs")
179+
.toString()
180+
);
181+
assertNotNull(file, "Could not find file for model: " + modelName);
182+
assertFileContains(file.toPath(), expectedContent);
183+
}
184+
}
143185
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java

+33
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,39 @@ public void testOpenapiGeneratorIgnoreListOption() {
24082408
assertNull(files.get("pom.xml"));
24092409
}
24102410

2411+
@Test
2412+
public void testEnumDiscriminatorDefaultValueIsNotString() {
2413+
final Path output = newTempFolder();
2414+
final OpenAPI openAPI = TestUtils.parseFlattenSpec(
2415+
"src/test/resources/3_0/enum_discriminator_inheritance.yaml");
2416+
JavaClientCodegen codegen = new JavaClientCodegen();
2417+
codegen.setOutputDir(output.toString());
2418+
2419+
Map<String, File> files = new DefaultGenerator().opts(new ClientOptInput().openAPI(openAPI).config(codegen))
2420+
.generate().stream().collect(Collectors.toMap(File::getName, Function.identity()));
2421+
2422+
Map<String, String> expectedContents = Map.of(
2423+
"Cat", "this.petType = PetTypeEnum.CATTY",
2424+
"Dog", "this.petType = PetTypeEnum.DOG",
2425+
"Gecko", "this.petType = PetTypeEnum.GECKO",
2426+
"Chameleon", "this.petType = PetTypeEnum.CAMO",
2427+
"MiniVan", "this.carType = CarType.MINI_VAN",
2428+
"CargoVan", "this.carType = CarType.CARGO_VAN",
2429+
"SUV", "this.carType = CarType.SUV",
2430+
"Truck", "this.carType = CarType.TRUCK",
2431+
"Sedan", "this.carType = CarType.SEDAN"
2432+
2433+
);
2434+
for (Map.Entry<String, String> e : expectedContents.entrySet()) {
2435+
String modelName = e.getKey();
2436+
String expectedContent = e.getValue();
2437+
File entityFile = files.get(modelName + ".java");
2438+
assertNotNull(entityFile);
2439+
assertThat(entityFile).content().doesNotContain("Type = this.getClass().getSimpleName();");
2440+
assertThat(entityFile).content().contains(expectedContent);
2441+
}
2442+
}
2443+
24112444
@Test
24122445
public void testRestTemplateHandleURIEnum() {
24132446
String[] expectedInnerEnumLines = new String[]{

0 commit comments

Comments
 (0)