From d18729e6b0770a0f3af1af6bf9b97af332bd22a6 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Sat, 30 Mar 2024 22:09:22 +0800 Subject: [PATCH] add rule to set primitive types to nullable --- docs/customization.md | 7 ++ .../codegen/OpenAPINormalizer.java | 86 +++++++++++++++---- .../codegen/OpenAPINormalizerTest.java | 42 +++++++++ .../3_0/setPrimitiveTypesToNullable_test.yaml | 44 ++++++++++ 4 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/setPrimitiveTypesToNullable_test.yaml diff --git a/docs/customization.md b/docs/customization.md index 515e3c7f2f5e..12bc5b0b88ee 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -605,3 +605,10 @@ Example: ``` java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer SET_CONTAINER_TO_NULLABLE="array|map" ``` + +- `SET_PRIMITIVE_TYPES_TO_NULLABLE`: When set to `string|integer|number|boolean` (or just `string`) for example, it will set the type to `nullable` (nullable: true) + +Example: +``` +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer SET_PRIMITIVE_TYPES_TO_NULLABLE="integer|number" +``` diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index 83277ace932a..37d8e8068012 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -112,6 +112,14 @@ public class OpenAPINormalizer { boolean updateSetToNullable; boolean updateMapToNullable; + // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else + final String SET_PRIMITIVE_TYPES_TO_NULLABLE = "SET_PRIMITIVE_TYPES_TO_NULLABLE"; + HashSet setPrimitiveTypesToNullable = new HashSet<>(); + boolean updateStringToNullable; + boolean updateIntegerToNullable; + boolean updateNumberToNullable; + boolean updateBooleanToNullable; + // ============= end of rules ============= /** @@ -143,6 +151,9 @@ public OpenAPINormalizer(OpenAPI openAPI, Map inputRules) { ruleNames.add(NORMALIZE_31SPEC); ruleNames.add(REMOVE_X_INTERNAL); ruleNames.add(FILTER); + ruleNames.add(SET_CONTAINER_TO_NULLABLE); + ruleNames.add(SET_PRIMITIVE_TYPES_TO_NULLABLE); + // rules that are default to true rules.put(SIMPLIFY_ONEOF_ANYOF, true); @@ -223,6 +234,26 @@ public void processRules(Map inputRules) { LOGGER.error("SET_CONTAINER_TO_NULLABLE rule must be in the form of `array|set|map`, e.g. `set`, `array|map`: {}", inputRules.get(SET_CONTAINER_TO_NULLABLE)); } } + + if (inputRules.get(SET_PRIMITIVE_TYPES_TO_NULLABLE) != null) { + rules.put(SET_PRIMITIVE_TYPES_TO_NULLABLE, true); + setPrimitiveTypesToNullable = new HashSet<>(Arrays.asList(inputRules.get(SET_PRIMITIVE_TYPES_TO_NULLABLE).split("[|]"))); + if (setPrimitiveTypesToNullable.contains("string")) { + updateStringToNullable = true; + } + if (setPrimitiveTypesToNullable.contains("integer")) { + updateIntegerToNullable = true; + } + if (setPrimitiveTypesToNullable.contains("number")) { + updateNumberToNullable = true; + } + if (setPrimitiveTypesToNullable.contains("boolean")) { + updateBooleanToNullable = true; + } + if (!updateStringToNullable && !updateIntegerToNullable && !updateNumberToNullable && !updateBooleanToNullable) { + LOGGER.error("SET_PRIMITIVE_TYPES_TO_NULLABLE rule must be in the form of `string|integer|number|boolean`, e.g. `string`, `integer|number`: {}", inputRules.get(SET_PRIMITIVE_TYPES_TO_NULLABLE)); + } + } } /** @@ -533,15 +564,18 @@ private Schema normalizeMapSchema(Schema schema) { } private Schema normalizeSimpleSchema(Schema schema, Set visitedSchemas) { - return processNormalize31Spec(schema, visitedSchemas); + Schema result = processNormalize31Spec(schema, visitedSchemas); + return processSetPrimitiveTypesToNullable(result); } private void normalizeBooleanSchema(Schema schema, Set visitedSchemas) { processSimplifyBooleanEnum(schema); + processSetPrimitiveTypesToNullable(schema); } private void normalizeIntegerSchema(Schema schema, Set visitedSchemas) { processAddUnsignedToIntegerWithInvalidMaxValue(schema); + processSetPrimitiveTypesToNullable(schema); } private void normalizeProperties(Map properties, Set visitedSchemas) { @@ -917,25 +951,49 @@ private Schema processSetArraytoNullable(Schema schema) { if (Boolean.TRUE.equals(schema.getUniqueItems())) { // a set if (updateSetToNullable) { - if (schema.getNullable() != null || (schema.getExtensions() != null && schema.getExtensions().containsKey("x-nullable"))) { - // already set, don't overwrite - return schema; - } - schema.setNullable(true); + return setNullable(schema); } } else { // array if (updateArrayToNullable) { - if (schema.getNullable() != null || (schema.getExtensions() != null && schema.getExtensions().containsKey("x-nullable"))) { - // already set, don't overwrite - return schema; - } - schema.setNullable(true); + return setNullable(schema); } } return schema; } + /** + * Set nullable to true in primitive types (e.g. string) if needed. + * + * @param schema Schema + * @return Schema + */ + private Schema processSetPrimitiveTypesToNullable(Schema schema) { + if (!getRule(SET_PRIMITIVE_TYPES_TO_NULLABLE)) { + return schema; + } + + if (updateStringToNullable && "string".equals(schema.getType())) { + return setNullable(schema); + } else if (updateIntegerToNullable && "integer".equals(schema.getType())) { + return setNullable(schema); + } else if (updateNumberToNullable && "number".equals(schema.getType())) { + return setNullable(schema); + } else if (updateBooleanToNullable && "boolean".equals(schema.getType())) { + return setNullable(schema); + } + + return schema; + } + + private Schema setNullable(Schema schema) { + if (schema.getNullable() != null || (schema.getExtensions() != null && schema.getExtensions().containsKey("x-nullable"))) { + // already set, don't overwrite + return schema; + } + schema.setNullable(true); + return schema; + } /** * Set nullable to true in map if needed. * @@ -948,11 +1006,7 @@ private Schema processSetMapToNullable(Schema schema) { } if (updateMapToNullable) { - if (schema.getNullable() != null || (schema.getExtensions() != null && schema.getExtensions().containsKey("x-nullable"))) { - // already set, don't override - return schema; - } - schema.setNullable(true); + return setNullable(schema); } return schema; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index ee3dae2b4b3b..b84b7c36d6f1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -514,6 +514,48 @@ public void testSetContainerToNullable() { assertEquals(((Schema) schema4.getProperties().get("map_property")).getNullable(), null); } + @Test + public void testSetPrimitiveTypesToNullable() { + // test `string|integer|number|boolean` + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0//setPrimitiveTypesToNullable_test.yaml"); + + Schema schema = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema.getProperties().get("lastName")).getNullable(), null); + assertEquals(((Schema) schema.getProperties().get("first_integer")).getNullable(), null); + assertEquals(((Schema) schema.getProperties().get("first_number")).getNullable(), null); + assertEquals(((Schema) schema.getProperties().get("first_boolean")).getNullable(), null); + + Map options = new HashMap<>(); + options.put("SET_PRIMITIVE_TYPES_TO_NULLABLE", "string|integer|number|boolean"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema schema2 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema2.getProperties().get("lastName")).getNullable(), true); + assertEquals(((Schema) schema2.getProperties().get("first_integer")).getNullable(), true); + assertEquals(((Schema) schema2.getProperties().get("first_number")).getNullable(), true); + assertEquals(((Schema) schema2.getProperties().get("first_boolean")).getNullable(), true); + + // test `number` only + OpenAPI openAPI2 = TestUtils.parseSpec("src/test/resources/3_0//setPrimitiveTypesToNullable_test.yaml"); + + Schema schema3 = openAPI2.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema3.getProperties().get("lastName")).getNullable(), null); + assertEquals(((Schema) schema3.getProperties().get("first_integer")).getNullable(), null); + assertEquals(((Schema) schema3.getProperties().get("first_number")).getNullable(), null); + assertEquals(((Schema) schema3.getProperties().get("first_boolean")).getNullable(), null); + + options.put("SET_PRIMITIVE_TYPES_TO_NULLABLE", "number"); + OpenAPINormalizer openAPINormalizer2 = new OpenAPINormalizer(openAPI2, options); + openAPINormalizer2.normalize(); + + Schema schema4 = openAPI2.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema4.getProperties().get("lastName")).getNullable(), null); + assertEquals(((Schema) schema4.getProperties().get("first_integer")).getNullable(), null); + assertEquals(((Schema) schema4.getProperties().get("first_number")).getNullable(), true); + assertEquals(((Schema) schema4.getProperties().get("first_boolean")).getNullable(), null); + } + @Test public void testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() { // to test the rule SIMPLIFY_ONEOF_ANYOF in 3.1 spec diff --git a/modules/openapi-generator/src/test/resources/3_0/setPrimitiveTypesToNullable_test.yaml b/modules/openapi-generator/src/test/resources/3_0/setPrimitiveTypesToNullable_test.yaml new file mode 100644 index 000000000000..fb00a2b7df4d --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/setPrimitiveTypesToNullable_test.yaml @@ -0,0 +1,44 @@ +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: + tags: + - person + - basic + 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/Person" + +components: + schemas: + Person: + description: person + type: object + properties: + lastName: + type: string + first_integer: + type: integer + first_number: + type: number + first_boolean: + type: boolean