Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OpenAPI Normalizer] update SIMPLIFY_ONEOF_ANYOF to convert enum of null to nullable #14898

Merged
merged 3 commits into from Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/customization.md
Expand Up @@ -485,7 +485,7 @@ Example:
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/simplifyBooleanEnum_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer SIMPLIFY_BOOLEAN_ENUM=true
```

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

Example:
```
Expand Down
Expand Up @@ -65,6 +65,7 @@ public class OpenAPINormalizer {

// when set to true, oneOf/anyOf 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_ONEOF_ANYOF = "SIMPLIFY_ONEOF_ANYOF";
boolean simplifyOneOfAnyOf;

Expand Down Expand Up @@ -578,7 +579,7 @@ private Schema processSimplifyAnyOfStringAndEnumString(Schema schema) {
return schema;
}

Schema s0 = null, s1 = null;
Schema result = null, s0 = null, s1 = null;
if (schema.getAnyOf().size() == 2) {
s0 = ModelUtils.unaliasSchema(openAPI, (Schema) schema.getAnyOf().get(0));
s1 = ModelUtils.unaliasSchema(openAPI, (Schema) schema.getAnyOf().get(1));
Expand All @@ -592,15 +593,27 @@ private Schema processSimplifyAnyOfStringAndEnumString(Schema schema) {
// find the string schema (not enum)
if (s0 instanceof StringSchema && s1 instanceof StringSchema) {
if (((StringSchema) s0).getEnum() != null) { // s0 is enum, s1 is string
return (StringSchema) s1;
result = (StringSchema) s1;
} else if (((StringSchema) s1).getEnum() != null) { // s1 is enum, s0 is string
return (StringSchema) s0;
result = (StringSchema) s0;
} else { // both are string
return schema;
result = schema;
}
} else {
return schema;
result = schema;
}

// set nullable
if (schema.getNullable() != null) {
result.setNullable(schema.getNullable());
}

// set default
if (schema.getDefault() != null) {
result.setDefault(schema.getDefault());
}

return result;
}

/**
Expand All @@ -616,11 +629,22 @@ private Schema processSimplifyOneOf(Schema schema) {
}

if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) {
// convert null sub-schema to `nullable: true`
for (int i = 0; i < schema.getOneOf().size(); i++) {
// convert null sub-schema to `nullable: true`
if (schema.getOneOf().get(i) == null || ((Schema) schema.getOneOf().get(i)).getType() == null) {
schema.getOneOf().remove(i);
schema.setNullable(true);
continue;
}

// convert enum of null only to `nullable:true`
Schema oneOfElement = ModelUtils.getReferencedSchema(openAPI, (Schema) schema.getOneOf().get(i));
if (oneOfElement.getEnum() != null && oneOfElement.getEnum().size() == 1) {
if ("null".equals(String.valueOf(oneOfElement.getEnum().get(0)))) {
schema.setNullable(true);
schema.getOneOf().remove(i);
continue;
}
}
}

Expand Down Expand Up @@ -649,11 +673,22 @@ private Schema processSimplifyAnyOf(Schema schema) {
}

if (schema.getAnyOf() != null && !schema.getAnyOf().isEmpty()) {
// convert null sub-schema to `nullable: true`
for (int i = 0; i < schema.getAnyOf().size(); i++) {
// convert null sub-schema to `nullable: true`
if (schema.getAnyOf().get(i) == null || ((Schema) schema.getAnyOf().get(i)).getType() == null) {
schema.getAnyOf().remove(i);
schema.setNullable(true);
continue;
}

// convert enum of null only to `nullable:true`
Schema anyOfElement = ModelUtils.getReferencedSchema(openAPI, (Schema) schema.getAnyOf().get(i));
if (anyOfElement.getEnum() != null && anyOfElement.getEnum().size() == 1) {
if ("null".equals(String.valueOf(anyOfElement.getEnum().get(0)))) {
schema.setNullable(true);
schema.getAnyOf().remove(i);
continue;
}
}
}

Expand Down Expand Up @@ -721,4 +756,4 @@ private void processAddUnsignedToIntegerWithInvalidMaxValue(Schema schema) {
}

// ===================== end of rules =====================
}
}
Expand Up @@ -4342,184 +4342,4 @@ public void testInlineEnumType() {
Assert.assertFalse(inlineEnumSchemaProperty.isPrimitiveType);
}

@Test
public void testOpenAPINormalizerRefAsParentInAllOf() {
// to test the rule REF_AS_PARENT_IN_ALLOF
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allOf_extension_parent.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("AnotherPerson");
assertNull(schema.getExtensions());

Schema schema2 = openAPI.getComponents().getSchemas().get("Person");
assertEquals(schema2.getExtensions().get("x-parent"), "abstract");

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

Schema schema3 = openAPI.getComponents().getSchemas().get("AnotherPerson");
assertEquals(schema3.getExtensions().get("x-parent"), true);

Schema schema4 = openAPI.getComponents().getSchemas().get("AnotherParent");
assertEquals(schema4.getExtensions().get("x-parent"), true);

Schema schema5 = openAPI.getComponents().getSchemas().get("Person");
assertEquals(schema5.getExtensions().get("x-parent"), "abstract");
}

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

assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().size(), 2);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getTags().size(), 1);

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

assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().size(), 1);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getTags().size(), 1);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().get(0), "person");
}

@Test
public void testOpenAPINormalizerRemoveAnyOfOneOfAndKeepPropertiesOnly() {
// to test the rule REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIIES_ONLY
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/removeAnyOfOneOfAndKeepPropertiesOnly_test.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("Person");
assertEquals(schema.getAnyOf().size(), 2);

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

Schema schema3 = openAPI.getComponents().getSchemas().get("Person");
assertNull(schema.getAnyOf());
}

@Test
public void testOpenAPINormalizerSimplifyOneOfAnyOfStringAndEnumString() {
// to test the rule SIMPLIFY_ONEOF_ANYOF_STRING_AND_ENUM_STRING
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyAnyOfStringAndEnumString_test.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("AnyOfTest");
assertEquals(schema.getAnyOf().size(), 2);

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

Schema schema3 = openAPI.getComponents().getSchemas().get("AnyOfTest");
assertNull(schema3.getAnyOf());
assertTrue(schema3 instanceof StringSchema);
}

@Test
public void testOpenAPINormalizerSimplifyOneOfAnyOf() {
// to test the rule SIMPLIFY_ONEOF_ANYOF
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("AnyOfTest");
assertEquals(schema.getAnyOf().size(), 2);
assertNull(schema.getNullable());

Schema schema2 = openAPI.getComponents().getSchemas().get("OneOfTest");
assertEquals(schema2.getOneOf().size(), 2);
assertNull(schema2.getNullable());

Schema schema5 = openAPI.getComponents().getSchemas().get("OneOfNullableTest");
assertEquals(schema5.getOneOf().size(), 3);
assertNull(schema5.getNullable());

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

Schema schema3 = openAPI.getComponents().getSchemas().get("AnyOfTest");
assertNull(schema3.getAnyOf());
assertTrue(schema3 instanceof StringSchema);
assertTrue(schema3.getNullable());

Schema schema4 = openAPI.getComponents().getSchemas().get("OneOfTest");
assertNull(schema4.getOneOf());
assertTrue(schema4 instanceof IntegerSchema);

Schema schema6 = openAPI.getComponents().getSchemas().get("OneOfNullableTest");
assertEquals(schema6.getOneOf().size(), 2);
assertTrue(schema6.getNullable());
}

@Test
public void testOpenAPINormalizerSimplifyBooleanEnum() {
// to test the rule SIMPLIFY_BOOLEAN_ENUM
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyBooleanEnum_test.yaml");

Schema schema = openAPI.getComponents().getSchemas().get("BooleanEnumTest");
assertEquals(schema.getProperties().size(), 3);
assertTrue(schema.getProperties().get("boolean_enum") instanceof BooleanSchema);
BooleanSchema bs = (BooleanSchema) schema.getProperties().get("boolean_enum");
assertEquals(bs.getEnum().size(), 2);

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

Schema schema3 = openAPI.getComponents().getSchemas().get("BooleanEnumTest");
assertEquals(schema.getProperties().size(), 3);
assertTrue(schema.getProperties().get("boolean_enum") instanceof BooleanSchema);
BooleanSchema bs2 = (BooleanSchema) schema.getProperties().get("boolean_enum");
assertNull(bs2.getEnum()); //ensure the enum has been erased
}

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

assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().size(), 2);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getTags().size(), 1);

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

assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().size(), 1);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getTags().size(), 1);
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getTags().get(0), "core");
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getTags().get(0), "core");
}

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

Schema person = openAPI.getComponents().getSchemas().get("Person");
assertNull(((Schema)person.getProperties().get("integer")).getExtensions());
assertNull(((Schema)person.getProperties().get("int32")).getExtensions());
assertNull(((Schema)person.getProperties().get("int64")).getExtensions());
assertNull(((Schema)person.getProperties().get("integer_max")).getExtensions());
assertNull(((Schema)person.getProperties().get("int32_max")).getExtensions());
assertNull(((Schema)person.getProperties().get("int64_max")).getExtensions());

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

Schema person2 = openAPI.getComponents().getSchemas().get("Person");
assertNull(((Schema)person2.getProperties().get("integer")).getExtensions());
assertNull(((Schema)person2.getProperties().get("int32")).getExtensions());
assertNull(((Schema)person2.getProperties().get("int64")).getExtensions());
assertTrue((Boolean)((Schema)person2.getProperties().get("integer_max")).getExtensions().get("x-unsigned"));
assertTrue((Boolean)((Schema)person2.getProperties().get("int32_max")).getExtensions().get("x-unsigned"));
assertTrue((Boolean)((Schema)person2.getProperties().get("int64_max")).getExtensions().get("x-unsigned"));
}
}