Skip to content

Commit ad6c2dd

Browse files
authored
[normalizer] bug fixes (isNullTypeSchema, handling of primitive types with oneOf) (#19781)
* better handling of primivitype type with oneOf * fix null check, add tests * add check for properties
1 parent 8e10dd7 commit ad6c2dd

File tree

3 files changed

+92
-28
lines changed

3 files changed

+92
-28
lines changed

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

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -642,22 +642,29 @@ private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSc
642642
}
643643

644644
private Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
645-
for (int i = 0; i < schema.getOneOf().size(); i++) {
646-
// normalize oneOf sub schemas one by one
647-
Object item = schema.getOneOf().get(i);
645+
// simplify first as the schema may no longer be a oneOf after processing the rule below
646+
schema = processSimplifyOneOf(schema);
648647

649-
if (item == null) {
650-
continue;
651-
}
652-
if (!(item instanceof Schema)) {
653-
throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item);
654-
}
648+
// if it's still a oneOf, loop through the sub-schemas
649+
if (schema.getOneOf() != null) {
650+
for (int i = 0; i < schema.getOneOf().size(); i++) {
651+
// normalize oneOf sub schemas one by one
652+
Object item = schema.getOneOf().get(i);
655653

656-
// update sub-schema with the updated schema
657-
schema.getOneOf().set(i, normalizeSchema((Schema) item, visitedSchemas));
654+
if (item == null) {
655+
continue;
656+
}
657+
if (!(item instanceof Schema)) {
658+
throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item);
659+
}
660+
661+
// update sub-schema with the updated schema
662+
schema.getOneOf().set(i, normalizeSchema((Schema) item, visitedSchemas));
663+
}
664+
} else {
665+
// normalize it as it's no longer an oneOf
666+
schema = normalizeSchema(schema, visitedSchemas);
658667
}
659-
// process rules here
660-
schema = processSimplifyOneOf(schema);
661668

662669
return schema;
663670
}
@@ -683,7 +690,7 @@ private Schema normalizeAnyOf(Schema schema, Set<Schema> visitedSchemas) {
683690
schema = processSimplifyAnyOf(schema);
684691

685692
// last rule to process as the schema may become String schema (not "anyOf") after the completion
686-
return processSimplifyAnyOfStringAndEnumString(schema);
693+
return normalizeSchema(processSimplifyAnyOfStringAndEnumString(schema), visitedSchemas);
687694
}
688695

689696
private Schema normalizeComplexComposedSchema(Schema schema, Set<Schema> visitedSchemas) {
@@ -694,7 +701,7 @@ private Schema normalizeComplexComposedSchema(Schema schema, Set<Schema> visited
694701

695702
processRemoveAnyOfOneOfAndKeepPropertiesOnly(schema);
696703

697-
return schema;
704+
return normalizeSchema(schema, visitedSchemas);
698705
}
699706

700707
// ===================== a list of rules =====================
@@ -893,21 +900,40 @@ private Schema processSimplifyAnyOfStringAndEnumString(Schema schema) {
893900
}
894901

895902
/**
896-
* Check if the schema is of type 'null'
903+
* Check if the schema is of type 'null' or schema itself is pointing to null
897904
* <p>
898905
* Return true if the schema's type is 'null' or not specified
899906
*
900907
* @param schema Schema
908+
* @param openAPI OpenAPI
909+
*
910+
* @return true if schema is null type
901911
*/
902-
public boolean isNullTypeSchema(Schema schema) {
912+
public boolean isNullTypeSchema(OpenAPI openAPI, Schema schema) {
903913
if (schema == null) {
904914
return true;
905915
}
906916

917+
// dereference the schema
918+
schema = ModelUtils.getReferencedSchema(openAPI, schema);
919+
920+
// allOf/anyOf/oneOf
907921
if (ModelUtils.hasAllOf(schema) || ModelUtils.hasOneOf(schema) || ModelUtils.hasAnyOf(schema)) {
908922
return false;
909923
}
910924

925+
// schema with properties
926+
if (schema.getProperties() != null) {
927+
return false;
928+
}
929+
930+
// convert referenced enum of null only to `nullable:true`
931+
if (schema.getEnum() != null && schema.getEnum().size() == 1) {
932+
if ("null".equals(String.valueOf(schema.getEnum().get(0)))) {
933+
return true;
934+
}
935+
}
936+
911937
if (schema.getTypes() != null && !schema.getTypes().isEmpty()) {
912938
// 3.1 spec
913939
if (schema.getTypes().size() == 1) { // 1 type only
@@ -933,14 +959,6 @@ public boolean isNullTypeSchema(Schema schema) {
933959
}
934960
}
935961

936-
// convert referenced enum of null only to `nullable:true`
937-
Schema referencedSchema = ModelUtils.getReferencedSchema(openAPI, schema);
938-
if (referencedSchema.getEnum() != null && referencedSchema.getEnum().size() == 1) {
939-
if ("null".equals(String.valueOf(referencedSchema.getEnum().get(0)))) {
940-
return true;
941-
}
942-
}
943-
944962
return false;
945963
}
946964

@@ -986,7 +1004,7 @@ private Schema processSimplifyOneOf(Schema schema) {
9861004
}
9871005
}
9881006

989-
if (oneOfSchemas.removeIf(oneOf -> isNullTypeSchema(oneOf))) {
1007+
if (oneOfSchemas.removeIf(oneOf -> isNullTypeSchema(openAPI, oneOf))) {
9901008
schema.setNullable(true);
9911009

9921010
// if only one element left, simplify to just the element (schema)
@@ -997,6 +1015,11 @@ private Schema processSimplifyOneOf(Schema schema) {
9971015
return (Schema) oneOfSchemas.get(0);
9981016
}
9991017
}
1018+
1019+
if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) {
1020+
// TODO convert oneOf const to enum
1021+
schema.setOneOf(null);
1022+
}
10001023
}
10011024

10021025
return schema;
@@ -1117,7 +1140,7 @@ private Schema processSimplifyAnyOf(Schema schema) {
11171140
}
11181141
}
11191142

1120-
if (anyOfSchemas.removeIf(anyOf -> isNullTypeSchema(anyOf))) {
1143+
if (anyOfSchemas.removeIf(anyOf -> isNullTypeSchema(openAPI, anyOf))) {
11211144
schema.setNullable(true);
11221145
}
11231146

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public void isNullTypeSchemaTest() {
113113
Map<String, String> options = new HashMap<>();
114114
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
115115
Schema schema = openAPI.getComponents().getSchemas().get("AnyOfStringArrayOfString");
116-
assertFalse(openAPINormalizer.isNullTypeSchema(schema));
116+
assertFalse(openAPINormalizer.isNullTypeSchema(openAPI, schema));
117117
}
118118

119119
@Test
@@ -705,6 +705,12 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() {
705705
Schema schema13 = openAPI.getComponents().getSchemas().get("OneOfAnyType");
706706
assertEquals(schema13.getOneOf().size(), 6);
707707

708+
Schema schema15 = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf");
709+
assertEquals(schema15.getOneOf().size(), 3);
710+
711+
Schema schema17 = openAPI.getComponents().getSchemas().get("OneOfNullAndRef3");
712+
assertEquals(schema17.getOneOf().size(), 2);
713+
708714
Map<String, String> options = new HashMap<>();
709715
options.put("SIMPLIFY_ONEOF_ANYOF", "true");
710716
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
@@ -742,5 +748,14 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() {
742748
Schema schema14 = openAPI.getComponents().getSchemas().get("OneOfAnyType");
743749
assertEquals(schema14.getOneOf(), null);
744750
assertEquals(schema14.getType(), null);
751+
752+
Schema schema16 = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf");
753+
// oneOf should have been removed as the schema is essentially a primitive type
754+
assertEquals(schema16.getOneOf(), null);
755+
756+
Schema schema18 = openAPI.getComponents().getSchemas().get("OneOfNullAndRef3");
757+
// original oneOf removed and simplified to just $ref (oneOf sub-schema) instead
758+
assertEquals(schema18.getOneOf(), null);
759+
assertEquals(schema18.get$ref(), "#/components/schemas/Parent");
745760
}
746761
}

modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,29 @@ components:
112112
- type: integer
113113
- type: array
114114
items: {}
115+
TypeIntegerWithOneOf:
116+
type: integer
117+
oneOf:
118+
- title: ITEM A
119+
description: This permission is for item A.
120+
const: 1
121+
- title: ITEM B
122+
description: This permission is for item B.
123+
const: 2
124+
- title: ITEM C
125+
description: This permission is for item C.
126+
const: 4
127+
format: int32
128+
# need to repeat the issue when it only occurs with the 3rd, 4th, 5th, etc schemas with oneOf(type: null, $ref)
129+
OneOfNullAndRef:
130+
oneOf:
131+
- $ref: '#/components/schemas/Parent'
132+
- type: "null"
133+
OneOfNullAndRef2:
134+
oneOf:
135+
- $ref: '#/components/schemas/Parent'
136+
- type: "null"
137+
OneOfNullAndRef3:
138+
oneOf:
139+
- $ref: '#/components/schemas/Parent'
140+
- type: "null"

0 commit comments

Comments
 (0)