Skip to content

Commit 62c0258

Browse files
authored
Better handling of allOf with unsupported schemas (OpenAPITools#19964)
* better handling of allOf with unsupported schemas * add test spec * better messages
1 parent 30ff0d7 commit 62c0258

File tree

5 files changed

+186
-0
lines changed

5 files changed

+186
-0
lines changed

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,21 +665,62 @@ private void normalizeProperties(Map<String, Schema> properties, Set<Schema> vis
665665
}
666666
}
667667

668+
/*
669+
* Remove unsupported schemas (e.g. if, then) from allOf.
670+
*
671+
* @param schema Schema
672+
*/
673+
private void removeUnsupportedSchemasFromAllOf(Schema schema) {
674+
if (schema.getAllOf() == null) {
675+
return;
676+
}
677+
678+
Iterator<Schema> iterator = schema.getAllOf().iterator();
679+
while (iterator.hasNext()) {
680+
Schema item = iterator.next();
681+
682+
// remove unsupported schemas (e.g. if, then)
683+
if (ModelUtils.isUnsupportedSchema(openAPI, item)) {
684+
LOGGER.debug("Removed allOf sub-schema that's not yet supported: {}", item);
685+
iterator.remove();
686+
}
687+
}
688+
689+
if (schema.getAllOf().size() == 0) {
690+
// no more schema in allOf so reset to null instead
691+
LOGGER.info("Unset/Removed allOf after cleaning up allOf sub-schemas that are not yet supported.");
692+
schema.setAllOf(null);
693+
}
694+
}
695+
668696
private Schema normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {
697+
removeUnsupportedSchemasFromAllOf(schema);
698+
699+
if (schema.getAllOf() == null) {
700+
return schema;
701+
}
702+
669703
for (Object item : schema.getAllOf()) {
670704
if (!(item instanceof Schema)) {
671705
throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
672706
}
673707
// normalize allOf sub schemas one by one
674708
normalizeSchema((Schema) item, visitedSchemas);
675709
}
710+
676711
// process rules here
677712
processUseAllOfRefAsParent(schema);
678713

679714
return schema;
680715
}
681716

682717
private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSchemas) {
718+
removeUnsupportedSchemasFromAllOf(schema);
719+
720+
if (schema.getAllOf() == null) {
721+
return schema;
722+
}
723+
683724
for (Object item : schema.getAllOf()) {
684725
if (!(item instanceof Schema)) {
685726
throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,16 @@ public static boolean isSet(Schema schema) {
616616
return ModelUtils.isArraySchema(schema) && Boolean.TRUE.equals(schema.getUniqueItems());
617617
}
618618

619+
/**
620+
* Return true if the schema is a string/integer/number/boolean type in OpenAPI.
621+
*
622+
* @param schema the OAS schema
623+
* @return true if the schema is a string/integer/number/boolean type in OpenAPI.
624+
*/
625+
public static boolean isPrimitiveType(Schema schema) {
626+
return (isStringSchema(schema) || isIntegerSchema(schema) || isNumberSchema(schema) || isBooleanSchema(schema));
627+
}
628+
619629
public static boolean isStringSchema(Schema schema) {
620630
return schema instanceof StringSchema || SchemaTypeUtil.STRING_TYPE.equals(getType(schema));
621631
}
@@ -2262,6 +2272,35 @@ public static boolean isNullTypeSchema(OpenAPI openAPI, Schema schema) {
22622272
return false;
22632273
}
22642274

2275+
/**
2276+
* Check if the schema is supported by OpenAPI Generator.
2277+
* <p>
2278+
* Return true if the schema can be handled by OpenAPI Generator
2279+
*
2280+
* @param schema Schema
2281+
* @param openAPI OpenAPIs
2282+
*
2283+
* @return true if schema is null type
2284+
*/
2285+
public static boolean isUnsupportedSchema(OpenAPI openAPI, Schema schema) {
2286+
if (schema == null) {
2287+
return true;
2288+
}
2289+
2290+
// dereference the schema
2291+
schema = ModelUtils.getReferencedSchema(openAPI, schema);
2292+
2293+
if (schema.getTypes() == null && hasValidation(schema)) {
2294+
// just validation without type
2295+
return true;
2296+
} else if (schema.getIf() != null && schema.getThen() != null) {
2297+
// if, then in 3.1 spec
2298+
return true;
2299+
}
2300+
2301+
return false;
2302+
}
2303+
22652304
@FunctionalInterface
22662305
private interface OpenAPISchemaVisitor {
22672306

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,4 +774,24 @@ public void testOpenAPINormalizerSingleConstEnum31Spec() {
774774
assertEquals(normalizedTypeSchema.getEnum().size(), 1);
775775
assertEquals(Arrays.asList(originalConst), normalizedTypeSchema.getEnum());
776776
}
777+
778+
@Test
779+
public void testOpenAPINormalizerProcessingAllOfSchema31Spec() {
780+
// to test array schema processing in 3.1 spec
781+
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml");
782+
783+
Schema schema = openAPI.getComponents().getSchemas().get("Dummy");
784+
assertEquals(((Schema) schema.getProperties().get("property1")).getAllOf().size(), 2);
785+
assertNotEquals(((Schema) ((Schema) schema.getProperties().get("property2")).getAllOf().get(0)).getIf(), null); // if is set before normalization
786+
assertNotEquals(((Schema) ((Schema) schema.getProperties().get("property2")).getAllOf().get(1)).getThen(), null); // then is set before normalization
787+
788+
Map<String, String> inputRules = Map.of("NORMALIZE_31SPEC", "true");
789+
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules);
790+
openAPINormalizer.normalize();
791+
792+
Schema schema2 = openAPI.getComponents().getSchemas().get("Dummy");
793+
assertEquals(((Schema) schema2.getProperties().get("property1")).getAllOf(), null);
794+
assertEquals(((Schema) schema2.getProperties().get("property2")).getAllOf(), null);
795+
assertEquals(((Schema) schema2.getProperties().get("property2")).getAllOf(), null);
796+
}
777797
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,4 +501,26 @@ public void isNullTypeSchemaTest() {
501501
assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(2)));
502502
assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(3)));
503503
}
504+
505+
@Test
506+
public void isUnsupportedSchemaTest() {
507+
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml");
508+
Map<String, String> options = new HashMap<>();
509+
Schema schema = openAPI.getComponents().getSchemas().get("Dummy");
510+
Schema property1 = (Schema) schema.getProperties().get("property1");
511+
Schema property2 = (Schema) schema.getProperties().get("property2");
512+
513+
// a test of string type with allOf (2 patterns)
514+
assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property1.getAllOf().get(0)));
515+
assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property1.getAllOf().get(1)));
516+
517+
// if, then test
518+
assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getAllOf().get(0)));
519+
assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getAllOf().get(1)));
520+
521+
// typical schemas, e.g. boolean, string, array of string (enum)
522+
assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("aBooleanCheck")));
523+
assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("condition")));
524+
assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("purpose")));
525+
}
504526
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
openapi: 3.1.0
2+
info:
3+
version: 1.0.0
4+
title: Example
5+
servers:
6+
- url: http://api.example.xyz/v1
7+
paths:
8+
/person/display/{personId}:
9+
get:
10+
parameters:
11+
- name: personId
12+
in: path
13+
required: true
14+
description: The id of the person to retrieve
15+
schema:
16+
type: string
17+
operationId: list
18+
responses:
19+
'200':
20+
description: OK
21+
content:
22+
application/json:
23+
schema:
24+
$ref: "#/components/schemas/Dummy"
25+
components:
26+
schemas:
27+
Dummy:
28+
type: object
29+
properties:
30+
property1:
31+
type: string
32+
allOf:
33+
- pattern: "[abc]"
34+
- pattern: "[a-z]"
35+
property2:
36+
type: object
37+
allOf:
38+
- if:
39+
properties:
40+
aBooleanCheck:
41+
const: false
42+
then:
43+
required:
44+
- condition
45+
- if:
46+
properties:
47+
aBooleanCheck:
48+
const: true
49+
then:
50+
required:
51+
- purpose
52+
properties:
53+
aBooleanCheck:
54+
type: boolean
55+
condition:
56+
type: string
57+
purpose:
58+
type: array
59+
items:
60+
type: string
61+
enum:
62+
- FIRST
63+
- SECOND
64+
- THIRD

0 commit comments

Comments
 (0)