Skip to content

Commit 56e5122

Browse files
authored
Add new openapi-normalizer rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY (#15039)
* add new rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY * update other attributes * minor refactoring
1 parent f2e0555 commit 56e5122

6 files changed

Lines changed: 127 additions & 2 deletions

File tree

docs/customization.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,6 @@ OpenAPI Normalizer (off by default) transforms the input OpenAPI doc/spec (which
458458
459459
- `REF_AS_PARENT_IN_ALLOF`: when set to `true`, child schemas in `allOf` is considered a parent if it's a `$ref` (instead of inline schema).
460460
461-
462461
Example:
463462
```
464463
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml -o /tmp/java-okhttp/ --openapi-normalizer REF_AS_PARENT_IN_ALLOF=true
@@ -512,3 +511,11 @@ Example:
512511
```
513512
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/addUnsignedToIntegerWithInvalidMaxValue_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE=true
514513
```
514+
515+
- `REFACTOR_ALLOF_WITH_PROPERTIES_ONLY`: When set to true, refactor schema with allOf and properties in the same level to a schema with allOf only and, the allOf contains a new schema containing the properties in the top level.
516+
517+
Example:
518+
```
519+
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml -o /tmp/java-okhttp/ --openapi-normalizer REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true
520+
```
521+

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ public class OpenAPINormalizer {
8282
final String ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE = "ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE";
8383
boolean addUnsignedToIntegerWithInvalidMaxValue;
8484

85+
// when set to true, refactor schema with allOf and properties in the same level to a schema with allOf only and
86+
// the allOf contains a new schema containing the properties in the top level
87+
final String REFACTOR_ALLOF_WITH_PROPERTIES_ONLY = "REFACTOR_ALLOF_WITH_PROPERTIES_ONLY";
88+
boolean refactorAllOfWithPropertiesOnly;
89+
8590
// ============= end of rules =============
8691

8792
/**
@@ -141,6 +146,10 @@ public void parseRules(Map<String, String> rules) {
141146
if (enableAll || "true".equalsIgnoreCase(rules.get(ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE))) {
142147
addUnsignedToIntegerWithInvalidMaxValue = true;
143148
}
149+
150+
if (enableAll || "true".equalsIgnoreCase(rules.get(REFACTOR_ALLOF_WITH_PROPERTIES_ONLY))) {
151+
refactorAllOfWithPropertiesOnly = true;
152+
}
144153
}
145154

146155
/**
@@ -346,6 +355,9 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
346355
return normalizeOneOf(schema, visitedSchemas);
347356
} else if (ModelUtils.isAnyOf(schema)) { // anyOf
348357
return normalizeAnyOf(schema, visitedSchemas);
358+
} else if (ModelUtils.isAllOfWithProperties(schema)) { // allOf with properties
359+
schema = normalizeAllOfWithProperties(schema, visitedSchemas);
360+
normalizeSchema(schema, visitedSchemas);
349361
} else if (ModelUtils.isAllOf(schema)) { // allOf
350362
return normalizeAllOf(schema, visitedSchemas);
351363
} else if (ModelUtils.isComposedSchema(schema)) { // composed schema
@@ -427,6 +439,20 @@ private Schema normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {
427439
return schema;
428440
}
429441

442+
private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSchemas) {
443+
for (Object item : schema.getAllOf()) {
444+
if (!(item instanceof Schema)) {
445+
throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
446+
}
447+
// normalize allOf sub schemas one by one
448+
normalizeSchema((Schema) item, visitedSchemas);
449+
}
450+
// process rules here
451+
schema = processRefactorAllOfWithPropertiesOnly(schema);
452+
453+
return schema;
454+
}
455+
430456
private Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
431457
for (Object item : schema.getOneOf()) {
432458
if (item == null) {
@@ -759,5 +785,46 @@ private void processAddUnsignedToIntegerWithInvalidMaxValue(Schema schema) {
759785
}
760786
}
761787

788+
/*
789+
* When set to true, refactor schema with allOf and properties in the same level to a schema with allOf only and
790+
* the allOf contains a new schema containing the properties in the top level.
791+
*
792+
* @param schema Schema
793+
* @return Schema
794+
*/
795+
private Schema processRefactorAllOfWithPropertiesOnly(Schema schema) {
796+
if (!refactorAllOfWithPropertiesOnly && !enableAll) {
797+
return schema;
798+
}
799+
800+
ObjectSchema os = new ObjectSchema();
801+
// set the properties, etc of the new schema to the properties of schema
802+
os.setProperties(schema.getProperties());
803+
os.setRequired(schema.getRequired());
804+
os.setAdditionalProperties(schema.getAdditionalProperties());
805+
os.setNullable(schema.getNullable());
806+
os.setDescription(schema.getDescription());
807+
os.setDeprecated(schema.getDeprecated());
808+
os.setExample(schema.getExample());
809+
os.setExamples(schema.getExamples());
810+
os.setTitle(schema.getTitle());
811+
schema.getAllOf().add(os); // move new schema as a child schema of allOf
812+
// clean up by removing properties, etc
813+
schema.setProperties(null);
814+
schema.setRequired(null);
815+
schema.setAdditionalProperties(null);
816+
schema.setNullable(null);
817+
schema.setDescription(null);
818+
schema.setDeprecated(null);
819+
schema.setExample(null);
820+
schema.setExamples(null);
821+
schema.setTitle(null);
822+
823+
// at this point the schema becomes a simple allOf (no properties) with an additional schema containing
824+
// the properties
825+
826+
return schema;
827+
}
828+
762829
// ===================== end of rules =====================
763830
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,19 @@ public static boolean hasAllOf(Schema schema) {
18111811
return false;
18121812
}
18131813

1814+
/**
1815+
* Returns true if the schema contains allOf and properties,
1816+
* and no oneOf/anyOf defined.
1817+
*
1818+
* @param schema the schema
1819+
* @return true if the schema contains allOf but no properties/oneOf/anyOf defined.
1820+
*/
1821+
public static boolean isAllOfWithProperties(Schema schema) {
1822+
return hasAllOf(schema) && (schema.getProperties() != null && !schema.getProperties().isEmpty()) &&
1823+
(schema.getOneOf() == null || schema.getOneOf().isEmpty()) &&
1824+
(schema.getAnyOf() == null || schema.getAnyOf().isEmpty());
1825+
}
1826+
18141827
/**
18151828
* Returns true if the schema contains oneOf but
18161829
* no properties/allOf/anyOf defined.

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,30 @@ public void testOpenAPINormalizerConvertEnumNullToNullable_test() {
278278
assertEquals(schema3.getAnyOf().size(), 2);
279279
assertTrue(schema3.getNullable());
280280
}
281+
282+
@Test
283+
public void testOpenAPINormalizerRefactorAllOfWithPropertiesOnly() {
284+
// to test the rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY
285+
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allOf_extension_parent.yaml");
286+
287+
ComposedSchema schema = (ComposedSchema) openAPI.getComponents().getSchemas().get("allOfWithProperties");
288+
assertEquals(schema.getAllOf().size(), 1);
289+
assertEquals(schema.getProperties().size(), 2);
290+
assertEquals(((Schema) schema.getProperties().get("isParent")).getType(), "boolean");
291+
assertEquals(((Schema) schema.getProperties().get("mum_or_dad")).getType(), "string");
292+
293+
Map<String, String> options = new HashMap<>();
294+
options.put("REFACTOR_ALLOF_WITH_PROPERTIES_ONLY", "true");
295+
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
296+
openAPINormalizer.normalize();
297+
298+
Schema schema2 = openAPI.getComponents().getSchemas().get("allOfWithProperties");
299+
assertEquals(schema2.getAllOf().size(), 2);
300+
assertNull(schema2.getProperties());
301+
302+
Schema newSchema = (Schema) (schema2.getAllOf().get(1));
303+
assertEquals(((Schema) newSchema.getProperties().get("isParent")).getType(), "boolean");
304+
assertEquals(((Schema) newSchema.getProperties().get("mum_or_dad")).getType(), "string");
305+
assertEquals(newSchema.getRequired().get(0), "isParent");
306+
}
281307
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,7 @@ public void testJdkHttpClientWithAndWithoutParentExtension() throws Exception {
17461746
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
17471747
List<File> files = generator.opts(clientOptInput).generate();
17481748

1749-
Assert.assertEquals(files.size(), 30);
1749+
Assert.assertEquals(files.size(), 33);
17501750
validateJavaSourceFiles(files);
17511751

17521752
TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"),

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,15 @@ components:
8585
type: boolean
8686
mum_or_dad:
8787
type: string
88+
allOfWithProperties:
89+
description: parent object without x-parent extension
90+
type: object
91+
allOf:
92+
- $ref: '#/components/schemas/AnotherParent'
93+
properties:
94+
isParent:
95+
type: boolean
96+
mum_or_dad:
97+
type: string
98+
required:
99+
- isParent

0 commit comments

Comments
 (0)