Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,13 @@ Example:
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer SIMPLIFY_ONEOF_ANYOF=true
```

- `SIMPLIFY_ALLOF`: when set to `true`, simplify allOf by 1) removing null (sub-schema) or enum of null (sub-schema) and setting nullable to true instead, and 2) simplifying allOf with a single sub-schema to just the sub-schema itself.

Example:
```
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/simplifyAllOf_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer SIMPLIFY_ALLOF=true
```

- `KEEP_ONLY_FIRST_TAG_IN_OPERATION`: when set to `true`, only keep the first tag in operation if there are more than one tag defined.

Example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import java.util.stream.Collectors;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: SIMPLIFY_ALLOF is not default-enabled after the rule split, causing a default-behavior regression for allOf primitive cleanup

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java, line 236:

<comment>`SIMPLIFY_ALLOF` is not default-enabled after the rule split, causing a default-behavior regression for allOf primitive cleanup</comment>

<file context>
@@ -227,7 +233,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
 
         // rules that are default to true
-        rules.put(SIMPLIFY_ONEOF_ANYOF_ALLOF, true);
+        rules.put(SIMPLIFY_ONEOF_ANYOF, true);
         rules.put(SIMPLIFY_BOOLEAN_ENUM, true);
         rules.put(SIMPLIFY_ONEOF_ANYOF_ENUM, true);
</file context>


import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.utils.ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema;
import static org.openapitools.codegen.utils.ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema;
import static org.openapitools.codegen.utils.StringUtils.getUniqueString;

public class OpenAPINormalizer {
Expand Down Expand Up @@ -75,7 +75,7 @@ public class OpenAPINormalizer {
// when set to true, only keep the first tag in operation if there are more than one tag defined.
final String KEEP_ONLY_FIRST_TAG_IN_OPERATION = "KEEP_ONLY_FIRST_TAG_IN_OPERATION";

// when set to true, complex composed schemas (a mix of oneOf/anyOf/anyOf and properties) with
// when set to true, complex composed schemas (a mix of oneOf/anyOf and properties) with
// oneOf/anyOf containing only `required` and no properties (these are properties inter-dependency rules)
// are removed as most generators cannot handle such case at the moment
final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY";
Expand All @@ -93,6 +93,11 @@ public class OpenAPINormalizer {
// and if sub-schema contains enum of "null", remove it and set nullable to true instead
final String SIMPLIFY_ONEOF_ANYOF = "SIMPLIFY_ONEOF_ANYOF";

// when set to true, allOf schema with only one sub-schema is simplified to just the sub-schema
// and if sub-schema contains "null", remove it and set nullable to true instead
// and if sub-schema contains enum of "null", remove it and set nullable to true instead
final String SIMPLIFY_ALLOF = "SIMPLIFY_ALLOF";

// when set to true, boolean enum will be converted to just boolean
final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM";

Expand Down Expand Up @@ -206,6 +211,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
ruleNames.add(REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY);
ruleNames.add(SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING);
ruleNames.add(SIMPLIFY_ONEOF_ANYOF);
ruleNames.add(SIMPLIFY_ALLOF);
ruleNames.add(SIMPLIFY_BOOLEAN_ENUM);
ruleNames.add(KEEP_ONLY_FIRST_TAG_IN_OPERATION);
ruleNames.add(SET_TAGS_FOR_ALL_OPERATIONS);
Expand Down Expand Up @@ -929,7 +935,7 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {

if (ModelUtils.isArraySchema(schema)) { // array
Schema result = normalizeArraySchema(schema);
normalizeSchema(result.getItems(), visitedSchemas);
result.setItems(normalizeSchema(result.getItems(), visitedSchemas));
return result;
} else if (ModelUtils.isOneOf(schema)) { // oneOf
return normalizeOneOf(schema, visitedSchemas);
Expand Down Expand Up @@ -1203,6 +1209,7 @@ protected Schema normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {

// process rules here
processUseAllOfRefAsParent(schema);
schema = processSimplifyAllOf(schema);

return schema;
}
Expand Down Expand Up @@ -1306,6 +1313,43 @@ protected Schema normalizeComplexComposedSchema(Schema schema, Set<Schema> visit
// ===================== a list of rules =====================
// all rules (functions ) start with the word "process"


/**
* If the schema is allOf and the sub-schemas is null, set `nullable: true`
* instead.
* If there's only one sub-schema, simply return the sub-schema directly.
*
* @param schema Schema
* @return Schema
*/
protected Schema processSimplifyAllOf(Schema schema) {
List<Schema> allOfSchemas = schema.getAllOf();
if (allOfSchemas == null) {
return schema;
}

// allOf containing $refs is intentional composition (inheritance, or $ref+sibling wrapping
// from normalizeReferenceSchema) — never simplify these, as the referenced schema may have
// no type/description yet still be a real model (e.g. after oneOf removal by discriminator mapping)
if (allOfSchemas.stream().anyMatch(s -> StringUtils.isNotEmpty(s.get$ref()))) {
return schema;
}
schema = simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, schema, allOfSchemas);

if (!getRule(SIMPLIFY_ALLOF)) {
return schema;
}

// clear allOf on primitive types (opt-in)
if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema) || ModelUtils.isBooleanSchema(schema)) {
if (schema.getSpecVersion().equals(SpecVersion.V30)) {
schema.setAllOf(null);
}
}

return schema;
}

/**
* Child schemas in `allOf` is considered a parent if it's a `$ref` (instead of inline schema).
*
Expand Down Expand Up @@ -1736,8 +1780,8 @@ protected Schema processSimplifyOneOf(Schema schema) {
}
}

schema = simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, oneOfSchemas);
if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) {
schema = simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, schema, oneOfSchemas);
if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema) || ModelUtils.isBooleanSchema(schema)) {
if (schema.getSpecVersion().equals(SpecVersion.V30)) {
schema.setOneOf(null);
} //else {
Expand Down Expand Up @@ -2104,7 +2148,7 @@ protected Schema processSimplifyAnyOf(Schema schema) {
}
}

schema = simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, anyOfSchemas);
schema = simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, schema, anyOfSchemas);
}

return schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2323,7 +2323,7 @@ public static Schema cloneSchema(Schema schema, boolean openapi31) {
* @param subSchemas The oneOf or AnyOf schemas
* @return The simplified schema
*/
public static Schema simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(OpenAPI openAPI, Schema schema, List<Schema> subSchemas) {
public static Schema simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(OpenAPI openAPI, Schema schema, List<Schema> subSchemas) {
if (subSchemas.removeIf(subSchema -> isNullTypeSchema(openAPI, subSchema))) {
schema.setNullable(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,49 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf() {
assertEquals(schema20.getEnum().size(), 2);
}

@Test
public void testOpenAPINormalizerSimplifyAllOf() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyAllOf_test.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("AllOfTest");
assertEquals(schema.getAllOf().size(), 4);
assertNull(schema.getNullable());

Schema schema3 = openAPI.getComponents().getSchemas().get("SingleAllOfTest");
assertEquals(schema3.getAllOf().size(), 1);

Schema schema5 = openAPI.getComponents().getSchemas().get("ArrayAllOfTest");
Schema schema5_outer = ((Schema) schema5.getProperties().get("outer"));
assertNotNull(schema5_outer.getItems().getAllOf());
assertEquals(schema5_outer.getItems().getAllOf().size(), 1);
Schema schema5_outer_first = (Schema) schema5_outer.getItems().getAllOf().get(0);
assertEquals(schema5_outer_first.getType(), "array");
assertEquals(schema5_outer_first.getMinItems(), 1);
assertEquals(schema5_outer_first.getItems().getType(), "string");

Map<String, String> options = new HashMap<>();
options.put("SIMPLIFY_ALLOF", "true");
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
openAPINormalizer.normalize();

Schema schema2 = openAPI.getComponents().getSchemas().get("AllOfTest");
assertNull(schema2.getAllOf());
assertTrue(schema2 instanceof StringSchema);
assertTrue(schema2.getNullable());

Schema schema4 = openAPI.getComponents().getSchemas().get("SingleAllOfTest");
assertEquals(schema4.getAllOf(), null);
assertEquals(schema4.getType(), "string");
assertEquals(schema4.getEnum().size(), 2);

Schema schema6 = openAPI.getComponents().getSchemas().get("ArrayAllOfTest");
Schema schema6_outer = ((Schema) schema6.getProperties().get("outer"));
assertNull(schema6_outer.getItems().getAllOf());
assertEquals(schema6_outer.getItems().getType(), "array");
assertEquals(schema6_outer.getItems().getMinItems(), 1);
assertEquals(schema6_outer.getItems().getItems().getType(), "string");
}

@Test
public void testOpenAPINormalizerSimplifyOneOfWithSingleRef() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,39 +487,39 @@ public void simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema() {

Schema anyOfWithSeveralSubSchemasButSingleNonNull = ModelUtils.getSchema(openAPI, "AnyOfTest");
subSchemas = anyOfWithSeveralSubSchemasButSingleNonNull.getAnyOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSeveralSubSchemasButSingleNonNull, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSeveralSubSchemasButSingleNonNull, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
assertEquals("string", schema.getType());

Schema anyOfWithSingleNonNullSubSchema = ModelUtils.getSchema(openAPI, "Parent");
subSchemas = ((Schema) anyOfWithSingleNonNullSubSchema.getProperties().get("number")).getAnyOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSingleNonNullSubSchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSingleNonNullSubSchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertNull(schema.getNullable());
assertEquals(schema.get$ref(), "#/components/schemas/Number");

Schema oneOfWithSeveralSubSchemasButSingleNonNull = ModelUtils.getSchema(openAPI, "OneOfTest");
subSchemas = oneOfWithSeveralSubSchemasButSingleNonNull.getOneOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemasButSingleNonNull, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemasButSingleNonNull, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
assertEquals("integer", schema.getType());

Schema oneOfWithSingleNonNullSubSchema = ModelUtils.getSchema(openAPI, "ParentWithOneOfProperty");
subSchemas = ((Schema) oneOfWithSingleNonNullSubSchema.getProperties().get("number")).getOneOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSingleNonNullSubSchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSingleNonNullSubSchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertNull(schema.getNullable());
assertEquals(schema.get$ref(), "#/components/schemas/Number");

Schema oneOfWithSeveralSubSchemas = ModelUtils.getSchema(openAPI, "ParentWithPluralOneOfProperty");
subSchemas = ((Schema) oneOfWithSeveralSubSchemas.getProperties().get("number")).getOneOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemas, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemas, subSchemas);
assertNull(schema.getOneOf());
assertNotNull(oneOfWithSeveralSubSchemas.getProperties().get("number"));
assertNull(schema.getAnyOf());
Expand All @@ -536,7 +536,7 @@ public void simplifyOneOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttrib
Schema oneOfWithNullAndRefSubSchema = ModelUtils.getSchema(openAPI, "OneOfParentRefTest");
Schema numberPropertySchema = ((Schema) oneOfWithNullAndRefSubSchema.getProperties().get("number"));
subSchemas = numberPropertySchema.getOneOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
Expand All @@ -546,7 +546,7 @@ public void simplifyOneOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttrib

Schema number2PropertySchema = ((Schema) oneOfWithNullAndRefSubSchema.getProperties().get("number2"));
subSchemas = number2PropertySchema.getOneOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
Expand All @@ -564,7 +564,7 @@ public void simplifyAnyOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttrib
Schema anyOfWithNullAndRefSubSchema = ModelUtils.getSchema(openAPI, "AnyOfParentRefTest");
Schema numberPropertySchema = ((Schema) anyOfWithNullAndRefSubSchema.getProperties().get("number"));
subSchemas = numberPropertySchema.getAnyOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
Expand All @@ -574,7 +574,7 @@ public void simplifyAnyOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttrib

Schema number2PropertySchema = ((Schema) anyOfWithNullAndRefSubSchema.getProperties().get("number2"));
subSchemas = number2PropertySchema.getAnyOf();
schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas);
schema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas);
assertNull(schema.getOneOf());
assertNull(schema.getAnyOf());
assertTrue(schema.getNullable());
Expand All @@ -592,23 +592,23 @@ public void simplifyOneOfAnyOfWithOnlyOneNonNullSubSchemaKeepsParentDescription(
new StringSchema(),
new Schema<>().type("null")
)));
Schema anyOfSchema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfParent, anyOfParent.getAnyOf());
Schema anyOfSchema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, anyOfParent, anyOfParent.getAnyOf());
assertEquals(anyOfSchema.getDescription(), "Access token");

Schema oneOfParent = new Schema().description("Expires at");
oneOfParent.setOneOf(new ArrayList<>(Arrays.asList(
new IntegerSchema(),
new Schema<>().type("null")
)));
Schema oneOfSchema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfParent, oneOfParent.getOneOf());
Schema oneOfSchema = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(openAPI, oneOfParent, oneOfParent.getOneOf());
assertEquals(oneOfSchema.getDescription(), "Expires at");

Schema anyOfParentWithChildDescription = new Schema().description("Parent description");
anyOfParentWithChildDescription.setAnyOf(new ArrayList<>(Arrays.asList(
new StringSchema().description("Child description"),
new Schema<>().type("null")
)));
Schema anyOfSchemaWithChildDescription = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(
Schema anyOfSchemaWithChildDescription = ModelUtils.simplifyOneOfAnyOfAllOfWithOnlyOneNonNullSubSchema(
openAPI,
anyOfParentWithChildDescription,
anyOfParentWithChildDescription.getAnyOf());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
openapi: 3.0.1
info:
version: 1.0.0
title: Example
license:
name: MIT
servers:
- url: http://api.example.xyz/v1
paths:
/person/display/{personId}:
get:
parameters:
- name: personId
in: path
required: true
description: The id of the person to retrieve
schema:
type: string
operationId: list
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/AllOfTest"
components:
schemas:
AllOfTest:
description: to test allOf
allOf:
- type: string
- type: 'null'
- type: null
- $ref: null
SingleAllOfTest:
description: to test allOf (enum string only)
allOf:
- type: string
enum:
- A
- B
ArrayAllOfTest:
type: object
properties:
outer:
type: array
items:
allOf:
- type: array
minItems: 1
items:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ components:
oneOf:
- type: string
- type: 'null'
AllOfTest:
description: to test allOf
allOf:
- type: string
- type: 'null'
- type: null
- $ref: null
OneOfNullableTest:
description: to test oneOf nullable
oneOf:
Expand All @@ -58,6 +65,13 @@ components:
enum:
- A
- B
SingleAllOfTest:
description: to test allOf (enum string only)
allOf:
- type: string
enum:
- A
- B
Parent:
type: object
properties:
Expand Down