Skip to content

Commit e478171

Browse files
Make it more robust
1 parent 2d112da commit e478171

File tree

5 files changed

+164
-64
lines changed

5 files changed

+164
-64
lines changed

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

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3270,11 +3270,11 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
32703270
* @param sc The Schema that may contain the discriminator
32713271
* @param visitedSchemas An array list of visited schemas
32723272
*/
3273-
private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, ArrayList<Schema> visitedSchemas) {
3273+
private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList<Schema> visitedSchemas) {
32743274
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
3275-
Discriminator foundRefDisc = refSchema.getDiscriminator();
3276-
if (foundRefDisc != null) {
3277-
return new DiscriminatorAndReferenceSchema(foundRefDisc, refSchema);
3275+
Discriminator foundDisc = refSchema.getDiscriminator();
3276+
if (foundDisc != null) {
3277+
return foundDisc;
32783278
}
32793279

32803280
if (this.getLegacyDiscriminatorBehavior()) {
@@ -3288,15 +3288,17 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
32883288
}
32893289
visitedSchemas.add(refSchema);
32903290

3291+
Discriminator disc = new Discriminator();
32913292
if (ModelUtils.isComposedSchema(refSchema)) {
32923293
Schema composedSchema = refSchema;
32933294
if (composedSchema.getAllOf() != null) {
32943295
// If our discriminator is in one of the allOf schemas break when we find it
32953296
for (Object allOf : composedSchema.getAllOf()) {
3296-
DiscriminatorAndReferenceSchema foundDisc =
3297-
recursiveGetDiscriminator((Schema) allOf, visitedSchemas);
3297+
foundDisc = recursiveGetDiscriminator((Schema) allOf, visitedSchemas);
32983298
if (foundDisc != null) {
3299-
return foundDisc;
3299+
disc.setPropertyName(foundDisc.getPropertyName());
3300+
disc.setMapping(foundDisc.getMapping());
3301+
return disc;
33003302
}
33013303
}
33023304
}
@@ -3305,7 +3307,6 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33053307
Integer hasDiscriminatorCnt = 0;
33063308
Integer hasNullTypeCnt = 0;
33073309
Set<String> discriminatorsPropNames = new HashSet<>();
3308-
DiscriminatorAndReferenceSchema foundDisc = null;
33093310
for (Object oneOf : composedSchema.getOneOf()) {
33103311
if (ModelUtils.isNullType((Schema) oneOf)) {
33113312
// The null type does not have a discriminator. Skip.
@@ -3314,7 +3315,7 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33143315
}
33153316
foundDisc = recursiveGetDiscriminator((Schema) oneOf, visitedSchemas);
33163317
if (foundDisc != null) {
3317-
discriminatorsPropNames.add(foundDisc.discriminator.getPropertyName());
3318+
discriminatorsPropNames.add(foundDisc.getPropertyName());
33183319
hasDiscriminatorCnt++;
33193320
}
33203321
}
@@ -3323,7 +3324,9 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33233324
"oneOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
33243325
}
33253326
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getOneOf().size() && discriminatorsPropNames.size() == 1) {
3326-
return foundDisc;
3327+
disc.setPropertyName(foundDisc.getPropertyName());
3328+
disc.setMapping(foundDisc.getMapping());
3329+
return disc;
33273330
}
33283331
// If the scenario when oneOf has two children and one of them is the 'null' type,
33293332
// there is no need for a discriminator.
@@ -3333,7 +3336,6 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33333336
Integer hasDiscriminatorCnt = 0;
33343337
Integer hasNullTypeCnt = 0;
33353338
Set<String> discriminatorsPropNames = new HashSet<>();
3336-
DiscriminatorAndReferenceSchema foundDisc = null;
33373339
for (Object anyOf : composedSchema.getAnyOf()) {
33383340
if (ModelUtils.isNullType((Schema) anyOf)) {
33393341
// The null type does not have a discriminator. Skip.
@@ -3342,7 +3344,7 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33423344
}
33433345
foundDisc = recursiveGetDiscriminator((Schema) anyOf, visitedSchemas);
33443346
if (foundDisc != null) {
3345-
discriminatorsPropNames.add(foundDisc.discriminator.getPropertyName());
3347+
discriminatorsPropNames.add(foundDisc.getPropertyName());
33463348
hasDiscriminatorCnt++;
33473349
}
33483350
}
@@ -3351,7 +3353,9 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33513353
"anyOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
33523354
}
33533355
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getAnyOf().size() && discriminatorsPropNames.size() == 1) {
3354-
return foundDisc;
3356+
disc.setPropertyName(foundDisc.getPropertyName());
3357+
disc.setMapping(foundDisc.getMapping());
3358+
return disc;
33553359
}
33563360
// If the scenario when anyOf has two children and one of them is the 'null' type,
33573361
// there is no need for a discriminator.
@@ -3360,16 +3364,6 @@ private DiscriminatorAndReferenceSchema recursiveGetDiscriminator(Schema sc, Arr
33603364
return null;
33613365
}
33623366

3363-
private static class DiscriminatorAndReferenceSchema{
3364-
private final Discriminator discriminator;
3365-
private final Schema referenceSchema;
3366-
3367-
private DiscriminatorAndReferenceSchema(Discriminator discriminator, Schema referenceSchema) {
3368-
this.discriminator = discriminator;
3369-
this.referenceSchema = referenceSchema;
3370-
}
3371-
}
3372-
33733367
/**
33743368
* This function is only used for composed schemas which have a discriminator
33753369
* Process oneOf and anyOf models in a composed schema and adds them into
@@ -3503,12 +3497,10 @@ protected List<MappedModel> getAllOfDescendants(String thisSchemaName) {
35033497
}
35043498

35053499
protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
3506-
DiscriminatorAndReferenceSchema discriminatorAndReferenceSchema =
3507-
recursiveGetDiscriminator(schema, new ArrayList<Schema>());
3508-
if (discriminatorAndReferenceSchema == null) {
3500+
Discriminator sourceDiscriminator = recursiveGetDiscriminator(schema, new ArrayList<Schema>());
3501+
if (sourceDiscriminator == null) {
35093502
return null;
35103503
}
3511-
Discriminator sourceDiscriminator = discriminatorAndReferenceSchema.discriminator;
35123504
CodegenDiscriminator discriminator = new CodegenDiscriminator();
35133505
String discriminatorPropertyName = sourceDiscriminator.getPropertyName();
35143506
discriminator.setPropertyName(toVarName(discriminatorPropertyName));
@@ -3531,21 +3523,6 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
35313523
.orElseGet(() -> typeMapping.get("string"));
35323524
discriminator.setPropertyType(propertyType);
35333525

3534-
// check to see if the discriminator property is an enum string
3535-
// If it is not in the schema it should be in the reference schema of sourceDiscriminator.
3536-
for (Schema s : List.of(schema, discriminatorAndReferenceSchema.referenceSchema)) {
3537-
Optional<Schema> discriminatorSchema = Optional.ofNullable(s.getProperties()).map(p -> (Schema) p.get(discriminatorPropertyName));
3538-
if (discriminatorSchema.isPresent()) {
3539-
Optional<Schema> refSchema = Optional.ofNullable(discriminatorSchema.get().get$ref())
3540-
.map(ModelUtils::getSimpleRef)
3541-
.map(n -> ModelUtils.getSchema(openAPI, n));
3542-
Schema correctSchema = refSchema.orElse(discriminatorSchema.get());
3543-
if (correctSchema instanceof StringSchema && correctSchema.getEnum() != null && !correctSchema.getEnum().isEmpty()) {
3544-
discriminator.setIsEnum(true);
3545-
}
3546-
}
3547-
}
3548-
35493526
discriminator.setMapping(sourceDiscriminator.getMapping());
35503527
List<MappedModel> uniqueDescendants = new ArrayList<>();
35513528
if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
@@ -3597,6 +3574,32 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
35973574
}
35983575
discriminator.getMappedModels().addAll(uniqueDescendants);
35993576

3577+
// check current schema, all parents and descendants to see if the discriminator property is an enum string
3578+
List<Schema> schemasToCheckForEnumDiscriminator = new ArrayList<>();
3579+
schemasToCheckForEnumDiscriminator.add(schema);
3580+
if (ModelUtils.isComposedSchema(schema)) {
3581+
ModelUtils.getAllParentsName(schema, openAPI.getComponents().getSchemas(), true).stream()
3582+
.map(n -> ModelUtils.getSchema(openAPI, n))
3583+
.forEach(schemasToCheckForEnumDiscriminator::add);
3584+
}
3585+
uniqueDescendants.stream().map(MappedModel::getModelName)
3586+
.map(n->ModelUtils.getSchema(openAPI, n))
3587+
.forEach(schemasToCheckForEnumDiscriminator::add);
3588+
for (Schema schemaToCheck : schemasToCheckForEnumDiscriminator) {
3589+
if (schemaToCheck == null) {
3590+
continue;
3591+
}
3592+
boolean hasDiscriminatorEnum = Optional.ofNullable(schemaToCheck.getProperties())
3593+
.map(p -> (Schema) p.get(discriminatorPropertyName))
3594+
.map(s -> ModelUtils.getReferencedSchema(openAPI, s))
3595+
.filter(s -> s instanceof StringSchema && s.getEnum() != null && !s.getEnum().isEmpty())
3596+
.isPresent();
3597+
if (hasDiscriminatorEnum) {
3598+
discriminator.setIsEnum(true);
3599+
break;
3600+
}
3601+
}
3602+
36003603
return discriminator;
36013604
}
36023605

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,11 @@ public static String getParentName(Schema composedSchema, Map<String, Schema> al
15951595
* @return the name of the parent model
15961596
*/
15971597
public static List<String> getAllParentsName(Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors) {
1598+
return getAllParentsName(composedSchema, allSchemas, includeAncestors, new HashSet<>());
1599+
}
1600+
1601+
public static List<String> getAllParentsName(
1602+
Schema composedSchema, Map<String, Schema> allSchemas, boolean includeAncestors, Set<String> seenNames) {
15981603
List<Schema> interfaces = getInterfaces(composedSchema);
15991604
List<String> names = new ArrayList<String>();
16001605

@@ -1603,6 +1608,10 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
16031608
// get the actual schema
16041609
if (StringUtils.isNotEmpty(schema.get$ref())) {
16051610
String parentName = getSimpleRef(schema.get$ref());
1611+
if (seenNames.contains(parentName)) {
1612+
continue;
1613+
}
1614+
seenNames.add(parentName);
16061615
Schema s = allSchemas.get(parentName);
16071616
if (s == null) {
16081617
LOGGER.error("Failed to obtain schema from {}", parentName);
@@ -1611,7 +1620,7 @@ public static List<String> getAllParentsName(Schema composedSchema, Map<String,
16111620
// discriminator.propertyName is used or x-parent is used
16121621
names.add(parentName);
16131622
if (includeAncestors && isComposedSchema(s)) {
1614-
names.addAll(getAllParentsName(s, allSchemas, true));
1623+
names.addAll(getAllParentsName(s, allSchemas, true, seenNames));
16151624
}
16161625
} else {
16171626
// not a parent since discriminator.propertyName is not set

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,26 @@ public void testEnumDiscriminatorDefaultValueIsNotString() throws IOException {
159159
Map<String, File> files = defaultGenerator.generate().stream()
160160
.collect(Collectors.toMap(File::getPath, Function.identity()));
161161

162-
Map<String, String> expectedMapping = Map.of(
163-
"Catty", "Cat",
164-
"Lizzy", "Lizard",
165-
"Dog", "Dog"
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+
166172
);
167-
for (Map.Entry<String, String> e : expectedMapping.entrySet()) {
168-
String modelName = e.getValue();
169-
String enumValue = e.getKey();
173+
for (Map.Entry<String, String> e : expectedContents.entrySet()) {
174+
String modelName = e.getKey();
175+
String expectedContent = e.getValue();
170176
File file = files.get(Paths
171177
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", modelName + ".cs")
172178
.toString()
173179
);
174180
assertNotNull(file, "Could not find file for model: " + modelName);
175-
assertFileContains(file.toPath(), "AnimalType petType = AnimalType." + enumValue);
181+
assertFileContains(file.toPath(), expectedContent);
176182
}
177183
}
178184
}

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,10 +2297,20 @@ public void testAllOfWithSinglePrimitiveTypeRef() {
22972297
Map<String, File> files = new DefaultGenerator().opts(new ClientOptInput().openAPI(openAPI).config(codegen))
22982298
.generate().stream().collect(Collectors.toMap(File::getName, Function.identity()));
22992299

2300-
File fooEntityFile = files.get("Cat.java");
2301-
assertNotNull(fooEntityFile);
2302-
assertThat(fooEntityFile).content()
2303-
.doesNotContain("this.petType = this.getClass().getSimpleName();");
2300+
List<String> entities = List.of(
2301+
"Cat",
2302+
"Dog",
2303+
"Gecko",
2304+
"Chameleon",
2305+
"MiniVan",
2306+
"CargoVan",
2307+
"SUV",
2308+
"Truck");
2309+
for (String entity : entities) {
2310+
File entityFile = files.get(entity + ".java");
2311+
assertNotNull(entityFile);
2312+
assertThat(entityFile).content().doesNotContain("Type = this.getClass().getSimpleName();");
2313+
}
23042314
}
23052315

23062316
@Test

modules/openapi-generator/src/test/resources/3_0/enum_discriminator_inheritance.yaml

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@ components:
1717
Animal:
1818
type: object
1919
properties:
20+
# Inline enum type
2021
petType:
21-
$ref: '#/components/schemas/AnimalType'
22+
type: string
23+
enum:
24+
- Dog
25+
- Catty
26+
- Gecko
27+
- Camo
2228
discriminator:
2329
propertyName: petType
24-
# Mapping with implicit (Dog), explicit ref (Catty -> Cat), and explicit schema name (Lizzy -> Lizard)
30+
# Mapping with implicit (Dog, Gecko), explicit ref (Catty -> Cat), and explicit schema name (Camo -> Chameleon)
2531
mapping:
2632
Catty: '#/components/schemas/Cat'
27-
Lizzy: 'Lizard'
33+
Camo: 'Chameleon'
2834
required:
2935
- petType
30-
AnimalType:
31-
type: string
32-
enum:
33-
- Dog
34-
- Catty
35-
- Lizzy
3636
Cat:
3737
type: object
3838
allOf:
@@ -48,9 +48,81 @@ components:
4848
bark:
4949
type: string
5050
Lizard:
51+
oneOf:
52+
- $ref: '#/components/schemas/Gecko'
53+
- $ref: '#/components/schemas/Chameleon'
54+
Gecko:
5155
type: object
5256
allOf:
5357
- $ref: '#/components/schemas/Animal'
5458
properties:
5559
lovesRocks:
5660
type: string
61+
Chameleon:
62+
type: object
63+
allOf:
64+
- $ref: '#/components/schemas/Animal'
65+
properties:
66+
currentColor:
67+
type: string
68+
# Car inheritance tree: Car -> Truck -> SUV
69+
# Car -> Van -> MiniVan
70+
# Car -> Van -> CargoVan
71+
Car:
72+
type: object
73+
required:
74+
- carType
75+
# Discriminator carType not defined in Car properties, but in child properties
76+
discriminator:
77+
propertyName: carType
78+
CarType:
79+
type: string
80+
enum:
81+
- Truck
82+
- SUV
83+
- Sedan
84+
- MiniVan
85+
- CargoVan
86+
Truck:
87+
type: object
88+
allOf:
89+
- $ref: '#/components/schemas/Car'
90+
properties:
91+
carType:
92+
$ref: '#/components/schemas/CarType'
93+
required:
94+
- carType
95+
SUV:
96+
type: object
97+
# SUV gets its discriminator property from Truck
98+
allOf:
99+
- $ref: '#/components/schemas/Truck'
100+
Sedan:
101+
type: object
102+
allOf:
103+
- $ref: '#/components/schemas/Car'
104+
properties:
105+
carType:
106+
$ref: '#/components/schemas/CarType'
107+
Van:
108+
oneOf:
109+
- $ref: '#/components/schemas/MiniVan'
110+
- $ref: '#/components/schemas/CargoVan'
111+
MiniVan:
112+
type: object
113+
allOf:
114+
- $ref: '#/components/schemas/Car'
115+
properties:
116+
carType:
117+
$ref: '#/components/schemas/CarType'
118+
required:
119+
- carType
120+
CargoVan:
121+
type: object
122+
allOf:
123+
- $ref: '#/components/schemas/Car'
124+
properties:
125+
carType:
126+
$ref: '#/components/schemas/CarType'
127+
required:
128+
- carType

0 commit comments

Comments
 (0)