Add new openapi-normalizer rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY (#15039)

* add new rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY

* update other attributes

* minor refactoring
This commit is contained in:
William Cheng 2023-03-26 11:56:26 +08:00 committed by GitHub
parent f2e05555f3
commit 56e5122a6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 2 deletions

View File

@ -458,7 +458,6 @@ OpenAPI Normalizer (off by default) transforms the input OpenAPI doc/spec (which
- `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).
Example:
```
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:
```
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
```
- `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.
Example:
```
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
```

View File

@ -82,6 +82,11 @@ public class OpenAPINormalizer {
final String ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE = "ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE";
boolean addUnsignedToIntegerWithInvalidMaxValue;
// 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
final String REFACTOR_ALLOF_WITH_PROPERTIES_ONLY = "REFACTOR_ALLOF_WITH_PROPERTIES_ONLY";
boolean refactorAllOfWithPropertiesOnly;
// ============= end of rules =============
/**
@ -141,6 +146,10 @@ public class OpenAPINormalizer {
if (enableAll || "true".equalsIgnoreCase(rules.get(ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE))) {
addUnsignedToIntegerWithInvalidMaxValue = true;
}
if (enableAll || "true".equalsIgnoreCase(rules.get(REFACTOR_ALLOF_WITH_PROPERTIES_ONLY))) {
refactorAllOfWithPropertiesOnly = true;
}
}
/**
@ -346,6 +355,9 @@ public class OpenAPINormalizer {
return normalizeOneOf(schema, visitedSchemas);
} else if (ModelUtils.isAnyOf(schema)) { // anyOf
return normalizeAnyOf(schema, visitedSchemas);
} else if (ModelUtils.isAllOfWithProperties(schema)) { // allOf with properties
schema = normalizeAllOfWithProperties(schema, visitedSchemas);
normalizeSchema(schema, visitedSchemas);
} else if (ModelUtils.isAllOf(schema)) { // allOf
return normalizeAllOf(schema, visitedSchemas);
} else if (ModelUtils.isComposedSchema(schema)) { // composed schema
@ -427,6 +439,20 @@ public class OpenAPINormalizer {
return schema;
}
private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSchemas) {
for (Object item : schema.getAllOf()) {
if (!(item instanceof Schema)) {
throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item);
}
// normalize allOf sub schemas one by one
normalizeSchema((Schema) item, visitedSchemas);
}
// process rules here
schema = processRefactorAllOfWithPropertiesOnly(schema);
return schema;
}
private Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
for (Object item : schema.getOneOf()) {
if (item == null) {
@ -759,5 +785,46 @@ public class OpenAPINormalizer {
}
}
/*
* 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.
*
* @param schema Schema
* @return Schema
*/
private Schema processRefactorAllOfWithPropertiesOnly(Schema schema) {
if (!refactorAllOfWithPropertiesOnly && !enableAll) {
return schema;
}
ObjectSchema os = new ObjectSchema();
// set the properties, etc of the new schema to the properties of schema
os.setProperties(schema.getProperties());
os.setRequired(schema.getRequired());
os.setAdditionalProperties(schema.getAdditionalProperties());
os.setNullable(schema.getNullable());
os.setDescription(schema.getDescription());
os.setDeprecated(schema.getDeprecated());
os.setExample(schema.getExample());
os.setExamples(schema.getExamples());
os.setTitle(schema.getTitle());
schema.getAllOf().add(os); // move new schema as a child schema of allOf
// clean up by removing properties, etc
schema.setProperties(null);
schema.setRequired(null);
schema.setAdditionalProperties(null);
schema.setNullable(null);
schema.setDescription(null);
schema.setDeprecated(null);
schema.setExample(null);
schema.setExamples(null);
schema.setTitle(null);
// at this point the schema becomes a simple allOf (no properties) with an additional schema containing
// the properties
return schema;
}
// ===================== end of rules =====================
}

View File

@ -1811,6 +1811,19 @@ public class ModelUtils {
return false;
}
/**
* Returns true if the schema contains allOf and properties,
* and no oneOf/anyOf defined.
*
* @param schema the schema
* @return true if the schema contains allOf but no properties/oneOf/anyOf defined.
*/
public static boolean isAllOfWithProperties(Schema schema) {
return hasAllOf(schema) && (schema.getProperties() != null && !schema.getProperties().isEmpty()) &&
(schema.getOneOf() == null || schema.getOneOf().isEmpty()) &&
(schema.getAnyOf() == null || schema.getAnyOf().isEmpty());
}
/**
* Returns true if the schema contains oneOf but
* no properties/allOf/anyOf defined.

View File

@ -278,4 +278,30 @@ public class OpenAPINormalizerTest {
assertEquals(schema3.getAnyOf().size(), 2);
assertTrue(schema3.getNullable());
}
@Test
public void testOpenAPINormalizerRefactorAllOfWithPropertiesOnly() {
// to test the rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allOf_extension_parent.yaml");
ComposedSchema schema = (ComposedSchema) openAPI.getComponents().getSchemas().get("allOfWithProperties");
assertEquals(schema.getAllOf().size(), 1);
assertEquals(schema.getProperties().size(), 2);
assertEquals(((Schema) schema.getProperties().get("isParent")).getType(), "boolean");
assertEquals(((Schema) schema.getProperties().get("mum_or_dad")).getType(), "string");
Map<String, String> options = new HashMap<>();
options.put("REFACTOR_ALLOF_WITH_PROPERTIES_ONLY", "true");
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
openAPINormalizer.normalize();
Schema schema2 = openAPI.getComponents().getSchemas().get("allOfWithProperties");
assertEquals(schema2.getAllOf().size(), 2);
assertNull(schema2.getProperties());
Schema newSchema = (Schema) (schema2.getAllOf().get(1));
assertEquals(((Schema) newSchema.getProperties().get("isParent")).getType(), "boolean");
assertEquals(((Schema) newSchema.getProperties().get("mum_or_dad")).getType(), "string");
assertEquals(newSchema.getRequired().get(0), "isParent");
}
}

View File

@ -1746,7 +1746,7 @@ public class JavaClientCodegenTest {
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
List<File> files = generator.opts(clientOptInput).generate();
Assert.assertEquals(files.size(), 30);
Assert.assertEquals(files.size(), 33);
validateJavaSourceFiles(files);
TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"),

View File

@ -85,3 +85,15 @@ components:
type: boolean
mum_or_dad:
type: string
allOfWithProperties:
description: parent object without x-parent extension
type: object
allOf:
- $ref: '#/components/schemas/AnotherParent'
properties:
isParent:
type: boolean
mum_or_dad:
type: string
required:
- isParent