Skip to content

Commit 85145d6

Browse files
committed
Adds addComposedMappedModels and testComposedSchemaOneOfDiscriminatorMap
Uses inlineModelResolver on openapi spec Adds ParseFlattenSpec Referts DefaultCodegenTest.java Adds null ref handling in addComposedMappedModels, adds test case Adds recursive search of dscriminator when examining a schema
1 parent e45a1d0 commit 85145d6

File tree

4 files changed

+422
-33
lines changed

4 files changed

+422
-33
lines changed

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

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,7 @@ public String toOneOfName(List<String> names, ComposedSchema composedSchema) {
16771677

16781678
/**
16791679
* Return a string representation of the schema type, resolving aliasing and references if necessary.
1680-
*
1680+
*
16811681
* @param schema
16821682
* @return the string representation of the schema type.
16831683
*/
@@ -2141,12 +2141,102 @@ public int compare(CodegenProperty one, CodegenProperty another) {
21412141
return m;
21422142
}
21432143

2144+
/**
2145+
* Recursively look in Schema sc for the discriminator discPropName
2146+
* @param sc The Schema that may contain the discriminator
2147+
* @param discPropName The String that is the discriminator propertyName in the schema
2148+
*/
2149+
private boolean discriminatorFound(Schema sc, String discPropName) {
2150+
Schema refSchema = ModelUtils.getReferencedSchema(this.openAPI, sc);
2151+
if (refSchema.getProperties() != null) {
2152+
Schema discSchema = (Schema) refSchema.getProperties().get(discPropName);
2153+
if (discSchema != null && ModelUtils.isStringSchema(discSchema)) {
2154+
return true;
2155+
}
2156+
}
2157+
if (ModelUtils.isComposedSchema(refSchema)) {
2158+
ComposedSchema composedSchema = (ComposedSchema) refSchema;
2159+
if (composedSchema.getAllOf() != null) {
2160+
// If our discriminator is in one of the allOf schemas break when we find it
2161+
for (Schema allOf: composedSchema.getAllOf()) {
2162+
if (discriminatorFound(allOf, discPropName)) {
2163+
return true;
2164+
}
2165+
}
2166+
}
2167+
if (composedSchema.getOneOf() != null && composedSchema.getOneOf().size() != 0) {
2168+
// All oneOf definitions must contain the discriminator
2169+
Integer hasDiscriminatorCnt = 0;
2170+
for (Schema oneOf: composedSchema.getOneOf()) {
2171+
if (discriminatorFound(oneOf, discPropName)) {
2172+
hasDiscriminatorCnt++;
2173+
}
2174+
}
2175+
if (hasDiscriminatorCnt == composedSchema.getOneOf().size()) {
2176+
return true;
2177+
}
2178+
}
2179+
if (composedSchema.getAnyOf() != null && composedSchema.getAnyOf().size() != 0) {
2180+
// All anyOf definitions must contain the discriminator because a min of one must be selected
2181+
Integer hasDiscriminatorCnt = 0;
2182+
for (Schema anyOf: composedSchema.getAnyOf()) {
2183+
if (discriminatorFound(anyOf, discPropName)) {
2184+
hasDiscriminatorCnt++;
2185+
}
2186+
}
2187+
if (hasDiscriminatorCnt == composedSchema.getAnyOf().size()) {
2188+
return true;
2189+
}
2190+
}
2191+
}
2192+
return false;
2193+
}
2194+
2195+
/**
2196+
* Process oneOf and anyOf models in a composed schema and adds them into
2197+
* their parent's discriminator map if the oneOf and anyOf models contain
2198+
* the required discriminator. If they don't contain the required
2199+
* discriminator or the discriminator is the wrong type then an error is
2200+
* thrown
2201+
* @param discriminator The CodegenDiscriminator that we will add models to
2202+
* @param c The ComposedSchema that contains the discriminator and oneOf/anyOf schemas
2203+
* @param discPropName The String that is the discriminator propertyName in the schema
2204+
*/
2205+
private void addComposedMappedModels(CodegenDiscriminator discriminator, ComposedSchema c, String discPropName) {
2206+
ArrayList<List<Schema>> listOLists = new ArrayList<List<Schema>>();
2207+
listOLists.add(c.getOneOf());
2208+
listOLists.add(c.getAnyOf());
2209+
for (List<Schema> schemaList: listOLists) {
2210+
if (schemaList == null) {
2211+
continue;
2212+
}
2213+
for (Schema sc: schemaList) {
2214+
String ref = sc.get$ref();
2215+
if (ref == null) {
2216+
// for schemas with no ref, it is not possible to build the discriminator map
2217+
// because ref is how we get the model name
2218+
// we only hit this use case for a schema with inline composed schemas, and one of those
2219+
// schemas also has inline composed schemas
2220+
continue;
2221+
}
2222+
Boolean discFound = discriminatorFound(sc, discPropName);
2223+
String modelName = ModelUtils.getSimpleRef(ref);
2224+
if (discFound == false) {
2225+
LOGGER.error("schema {} is lacking the required string discriminator {}", modelName, discPropName);
2226+
}
2227+
MappedModel mappedModel = new MappedModel(modelName, toModelName(modelName));
2228+
discriminator.getMappedModels().add(mappedModel);
2229+
}
2230+
}
2231+
}
2232+
21442233
protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
21452234
if (schema.getDiscriminator() == null) {
21462235
return null;
21472236
}
21482237
CodegenDiscriminator discriminator = new CodegenDiscriminator();
2149-
discriminator.setPropertyName(toVarName(schema.getDiscriminator().getPropertyName()));
2238+
String discPropName = schema.getDiscriminator().getPropertyName();
2239+
discriminator.setPropertyName(toVarName(discPropName));
21502240
discriminator.setPropertyBaseName(schema.getDiscriminator().getPropertyName());
21512241
discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName()));
21522242
// FIXME: for now, we assume that the discriminator property is String
@@ -2160,16 +2250,23 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
21602250
discriminator.getMappedModels().add(new MappedModel(e.getKey(), modelName));
21612251
}
21622252
} else {
2163-
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
2164-
allDefinitions.forEach((childName, child) -> {
2165-
if (child instanceof ComposedSchema && ((ComposedSchema) child).getAllOf() != null) {
2166-
2167-
final List<String> parentSchemas = ModelUtils.getAllParentsName((ComposedSchema) child, allDefinitions, true);
2168-
if (parentSchemas.contains(schemaName)) {
2169-
discriminator.getMappedModels().add(new MappedModel(childName, toModelName(childName)));
2253+
if (ModelUtils.isComposedSchema(schema)) {
2254+
// if all oneOf/amyOf schemas contain the discriminator, then use it when mapping
2255+
addComposedMappedModels(discriminator, (ComposedSchema) schema, discPropName);
2256+
} else {
2257+
// we have child models that include a parent with allOf: parent and the parent does NOT have composed schema
2258+
// If the parent has composed schema we go an infinite loop parent->child-> parent in getAllParentsName and addProperties
2259+
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
2260+
allDefinitions.forEach((childName, child) -> {
2261+
if (child instanceof ComposedSchema && ((ComposedSchema) child).getAllOf() != null) {
2262+
2263+
final List<String> parentSchemas = ModelUtils.getAllParentsName((ComposedSchema) child, allDefinitions, true);
2264+
if (parentSchemas.contains(schemaName)) {
2265+
discriminator.getMappedModels().add(new MappedModel(childName, toModelName(childName)));
2266+
}
21702267
}
2171-
}
2172-
});
2268+
});
2269+
}
21732270
}
21742271
return discriminator;
21752272
}
@@ -2189,20 +2286,26 @@ protected void addProperties(Map<String, Schema> properties, List<String> requir
21892286
if (schema instanceof ComposedSchema) {
21902287
ComposedSchema composedSchema = (ComposedSchema) schema;
21912288

2192-
for (Schema component : composedSchema.getAllOf()) {
2193-
addProperties(properties, required, component);
2289+
if (composedSchema.getAllOf() != null) {
2290+
for (Schema component : composedSchema.getAllOf()) {
2291+
addProperties(properties, required, component);
2292+
}
21942293
}
21952294

21962295
if (schema.getRequired() != null) {
21972296
required.addAll(schema.getRequired());
21982297
}
21992298

22002299
if (composedSchema.getOneOf() != null) {
2201-
throw new RuntimeException("Please report the issue: Cannot process oneOf (Composed Scheme) in addProperties: " + schema);
2300+
for (Schema component : composedSchema.getOneOf()) {
2301+
addProperties(properties, required, component);
2302+
}
22022303
}
22032304

22042305
if (composedSchema.getAnyOf() != null) {
2205-
throw new RuntimeException("Please report the issue: Cannot process anyOf (Composed Schema) in addProperties: " + schema);
2306+
for (Schema component : composedSchema.getAnyOf()) {
2307+
addProperties(properties, required, component);
2308+
}
22062309
}
22072310

22082311
return;

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ public static OpenAPI parseSpec(String specFilePath) {
3232
return new OpenAPIParser().readLocation(specFilePath, null, new ParseOptions()).getOpenAPI();
3333
}
3434

35+
public static OpenAPI parseFlattenSpec(String specFilePath) {
36+
OpenAPI openAPI = new OpenAPIParser().readLocation(specFilePath, null, new ParseOptions()).getOpenAPI();
37+
// resolve inline models
38+
InlineModelResolver inlineModelResolver = new InlineModelResolver();
39+
inlineModelResolver.flatten(openAPI);
40+
return openAPI;
41+
}
42+
3543
public static OpenAPI parseContent(String jsonOrYaml) {
3644
return new OpenAPIParser().readContents(jsonOrYaml, null, null).getOpenAPI();
3745
}

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

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,9 @@
3737
import io.swagger.v3.oas.models.responses.ApiResponse;
3838
import io.swagger.v3.parser.util.SchemaTypeUtil;
3939

40-
import org.openapitools.codegen.ClientOptInput;
41-
import org.openapitools.codegen.CodegenConstants;
42-
import org.openapitools.codegen.CodegenModel;
43-
import org.openapitools.codegen.CodegenOperation;
44-
import org.openapitools.codegen.CodegenParameter;
45-
import org.openapitools.codegen.CodegenProperty;
46-
import org.openapitools.codegen.CodegenResponse;
47-
import org.openapitools.codegen.CodegenSecurity;
48-
import org.openapitools.codegen.DefaultGenerator;
49-
import org.openapitools.codegen.MockDefaultGenerator;
40+
import org.openapitools.codegen.*;
5041
import org.openapitools.codegen.MockDefaultGenerator.WrittenTemplateBasedFile;
51-
import org.openapitools.codegen.TestUtils;
42+
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
5243
import org.openapitools.codegen.config.CodegenConfigurator;
5344
import org.openapitools.codegen.languages.AbstractJavaCodegen;
5445
import org.openapitools.codegen.languages.JavaClientCodegen;
@@ -228,7 +219,7 @@ public void testPackageNamesSetInvokerDerivedFromModel() {
228219

229220
@Test
230221
public void testGetSchemaTypeWithComposedSchemaWithAllOf() {
231-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/2_0/composed-allof.yaml");
222+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/composed-allof.yaml");
232223
final JavaClientCodegen codegen = new JavaClientCodegen();
233224

234225
Operation operation = openAPI.getPaths().get("/ping").getPost();
@@ -450,7 +441,7 @@ public void testJdkHttpClient() throws Exception {
450441

451442
@Test
452443
public void testReferencedHeader() {
453-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/issue855.yaml");
444+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue855.yaml");
454445
JavaClientCodegen codegen = new JavaClientCodegen();
455446
codegen.setOpenAPI(openAPI);
456447

@@ -465,7 +456,7 @@ public void testReferencedHeader() {
465456

466457
@Test
467458
public void testAuthorizationScopeValues_Issue392() {
468-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/issue392.yaml");
459+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue392.yaml");
469460

470461
final DefaultGenerator defaultGenerator = new DefaultGenerator();
471462

@@ -493,7 +484,7 @@ public void testAuthorizationScopeValues_Issue392() {
493484

494485
@Test
495486
public void testAuthorizationsHasMoreWhenFiltered() {
496-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/issue4584.yaml");
487+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue4584.yaml");
497488

498489
final DefaultGenerator defaultGenerator = new DefaultGenerator();
499490

@@ -513,7 +504,7 @@ public void testAuthorizationsHasMoreWhenFiltered() {
513504

514505
@Test
515506
public void testFreeFormObjects() {
516-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/issue796.yaml");
507+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue796.yaml");
517508
JavaClientCodegen codegen = new JavaClientCodegen();
518509

519510
Schema test1 = openAPI.getComponents().getSchemas().get("MapTest1");
@@ -546,16 +537,102 @@ public void testFreeFormObjects() {
546537

547538
@Test
548539
public void testBearerAuth() {
549-
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/pingBearerAuth.yaml");
540+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/pingBearerAuth.yaml");
550541
JavaClientCodegen codegen = new JavaClientCodegen();
551-
542+
552543
List<CodegenSecurity> security = codegen.fromSecurity(openAPI.getComponents().getSecuritySchemes());
553544
Assert.assertEquals(security.size(), 1);
554545
Assert.assertEquals(security.get(0).isBasic, Boolean.TRUE);
555546
Assert.assertEquals(security.get(0).isBasicBasic, Boolean.FALSE);
556547
Assert.assertEquals(security.get(0).isBasicBearer, Boolean.TRUE);
557548
}
558549

550+
@Test
551+
public void testComposedSchemaOneOfDiscriminatorMap() {
552+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/oneoOfDiscriminator.yaml");
553+
JavaClientCodegen codegen = new JavaClientCodegen();
554+
codegen.setOpenAPI(openAPI);
555+
556+
String modelName;
557+
Schema sc;
558+
CodegenModel cm;
559+
java.util.LinkedHashSet hs;
560+
String mn;
561+
562+
// inline oneOf models
563+
modelName = "FruitInlineDisc";
564+
sc = openAPI.getComponents().getSchemas().get(modelName);
565+
cm = codegen.fromModel(modelName, sc);
566+
hs = new java.util.LinkedHashSet();
567+
mn = "FruitInlineDisc_oneOf";
568+
hs.add(new MappedModel(mn, codegen.toModelName(mn)));
569+
mn = "FruitInlineDisc_oneOf_1";
570+
hs.add(new MappedModel(mn, codegen.toModelName(mn)));
571+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
572+
573+
// inline oneOf with inline oneOf model
574+
modelName = "FruitInlineInlineDisc";
575+
sc = openAPI.getComponents().getSchemas().get(modelName);
576+
cm = codegen.fromModel(modelName, sc);
577+
hs = new java.util.LinkedHashSet();
578+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
579+
580+
// ref oneOf models with discriminator in properties in those models
581+
modelName = "FruitReqDisc";
582+
sc = openAPI.getComponents().getSchemas().get(modelName);
583+
cm = codegen.fromModel(modelName, sc);
584+
hs = new java.util.LinkedHashSet();
585+
mn = "AppleReqDisc";
586+
hs.add(new MappedModel(mn, mn));
587+
mn = "BananaReqDisc";
588+
hs.add(new MappedModel(mn, mn));
589+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
590+
591+
// ref oneOf models with discriminator in allOf in those models
592+
modelName = "FruitAllOfDisc";
593+
sc = openAPI.getComponents().getSchemas().get(modelName);
594+
cm = codegen.fromModel(modelName, sc);
595+
hs = new java.util.LinkedHashSet();
596+
mn = "AppleAllOfDisc";
597+
hs.add(new MappedModel(mn, mn));
598+
mn = "BananaAllOfDisc";
599+
hs.add(new MappedModel(mn, mn));
600+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
601+
602+
// ref oneOf models with discriminator in anyOf in those models
603+
modelName = "FruitAnyOfDisc";
604+
sc = openAPI.getComponents().getSchemas().get(modelName);
605+
cm = codegen.fromModel(modelName, sc);
606+
hs = new java.util.LinkedHashSet();
607+
mn = "AppleAnyOfDisc";
608+
hs.add(new MappedModel(mn, mn));
609+
mn = "BananaAnyOfDisc";
610+
hs.add(new MappedModel(mn, mn));
611+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
612+
613+
// ref oneOf models with discriminator in oneOf in those models
614+
modelName = "FruitOneOfDisc";
615+
sc = openAPI.getComponents().getSchemas().get(modelName);
616+
cm = codegen.fromModel(modelName, sc);
617+
hs = new java.util.LinkedHashSet();
618+
mn = "AppleOneOfDisc";
619+
hs.add(new MappedModel(mn, mn));
620+
mn = "BananaOneOfDisc";
621+
hs.add(new MappedModel(mn, mn));
622+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
623+
624+
// ref oneOf models with discriminator in the grandparent schemas of those oneof models
625+
modelName = "FruitGrandparentDisc";
626+
sc = openAPI.getComponents().getSchemas().get(modelName);
627+
cm = codegen.fromModel(modelName, sc);
628+
hs = new java.util.LinkedHashSet();
629+
mn = "AppleGrandparentDisc";
630+
hs.add(new MappedModel(mn, mn));
631+
mn = "BananaGrandparentDisc";
632+
hs.add(new MappedModel(mn, mn));
633+
Assert.assertEquals(cm.discriminator.getMappedModels(), hs);
634+
}
635+
559636
private CodegenProperty codegenPropertyWithArrayOfIntegerValues() {
560637
CodegenProperty array = new CodegenProperty();
561638
final CodegenProperty items = new CodegenProperty();

0 commit comments

Comments
 (0)