diff --git a/docs/customization.md b/docs/customization.md index 23a712105a3..515e3c7f2f5 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -598,3 +598,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 FILTER="operationId:addPet|getPetById" ``` + +- `SET_CONTAINER_TO_NULLABLE`: When set to `array|set|map` (or just `array`) for example, it will set `nullable` in array, set and map to 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_CONTAINER_TO_NULLABLE="array|map" +``` 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 eb5886d5fb2..b22c33a73f1 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 @@ -101,10 +101,17 @@ public class OpenAPINormalizer { final String X_INTERNAL = "x-internal"; boolean removeXInternal; - // when set (e.g. operationId:getPetById, addPet), filter out (or remove) everything else + // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else final String FILTER = "FILTER"; HashSet operationIdFilters = new HashSet<>(); + // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else + final String SET_CONTAINER_TO_NULLABLE = "SET_CONTAINER_TO_NULLABLE"; + HashSet setContainerToNullable = new HashSet<>(); + boolean updateArrayToNullable; + boolean updateSetToNullable; + boolean updateMapToNullable; + // ============= end of rules ============= /** @@ -199,6 +206,23 @@ public class OpenAPINormalizer { } } } + + if (inputRules.get(SET_CONTAINER_TO_NULLABLE) != null) { + rules.put(SET_CONTAINER_TO_NULLABLE, true); + setContainerToNullable = new HashSet<>(Arrays.asList(inputRules.get(SET_CONTAINER_TO_NULLABLE).split("[|]"))); + if (setContainerToNullable.contains("array")) { + updateArrayToNullable = true; + } + if (setContainerToNullable.contains("set")) { + updateSetToNullable = true; + } + if (setContainerToNullable.contains("map")) { + updateMapToNullable = true; + } + if (!updateArrayToNullable && !updateSetToNullable && !updateMapToNullable) { + 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)); + } + } } /** @@ -445,8 +469,10 @@ public class OpenAPINormalizer { } if (schema instanceof ArraySchema) { // array + normalizeArraySchema(schema); normalizeSchema(schema.getItems(), visitedSchemas); } else if (schema.getAdditionalProperties() instanceof Schema) { // map + normalizeMapSchema(schema); normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); } else if (ModelUtils.isOneOf(schema)) { // oneOf return normalizeOneOf(schema, visitedSchemas); @@ -498,6 +524,14 @@ public class OpenAPINormalizer { return schema; } + private Schema normalizeArraySchema(Schema schema) { + return processSetArraytoNullable(schema); + } + + private Schema normalizeMapSchema(Schema schema) { + return processSetMapToNullable(schema); + } + private Schema normalizeSimpleSchema(Schema schema, Set visitedSchemas) { return processNormalize31Spec(schema, visitedSchemas); } @@ -864,6 +898,60 @@ public class OpenAPINormalizer { return schema; } + /** + * Set nullable to true in array/set if needed. + * + * @param schema Schema + * @return Schema + */ + private Schema processSetArraytoNullable(Schema schema) { + if (!getRule(SET_CONTAINER_TO_NULLABLE)) { + return 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); + } + } 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 schema; + } + + /** + * Set nullable to true in map if needed. + * + * @param schema Schema + * @return Schema + */ + private Schema processSetMapToNullable(Schema schema) { + if (!getRule(SET_CONTAINER_TO_NULLABLE)) { + return 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 schema; + } + /** * If the schema is anyOf and the sub-schemas is null, set `nullable: true` instead. * If there's only one sub-schema, simply return the sub-schema directly. 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 05253ca2f19..d2a86d205e4 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 @@ -475,4 +475,42 @@ public class OpenAPINormalizerTest { OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, Collections.emptyMap()); openAPINormalizer.normalize(); } + + @Test + public void testSetContainerToNullable() { + // test `array|map` + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/setContainerToNullable_test.yaml"); + + Schema schema = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema.getProperties().get("array_property")).getNullable(), null); + assertEquals(((Schema) schema.getProperties().get("set_property")).getNullable(), null); + assertEquals(((Schema) schema.getProperties().get("map_property")).getNullable(), null); + + Map options = new HashMap<>(); + options.put("SET_CONTAINER_TO_NULLABLE", "array|map"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema schema2 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema2.getProperties().get("array_property")).getNullable(), true); + assertEquals(((Schema) schema2.getProperties().get("set_property")).getNullable(), null); + assertEquals(((Schema) schema2.getProperties().get("map_property")).getNullable(), true); + + // test `set` + OpenAPI openAPI2 = TestUtils.parseSpec("src/test/resources/3_0/setContainerToNullable_test.yaml"); + + Schema schema3 = openAPI2.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema3.getProperties().get("array_property")).getNullable(), null); + assertEquals(((Schema) schema3.getProperties().get("set_property")).getNullable(), null); + assertEquals(((Schema) schema3.getProperties().get("map_property")).getNullable(), null); + + options.put("SET_CONTAINER_TO_NULLABLE", "set"); + OpenAPINormalizer openAPINormalizer2 = new OpenAPINormalizer(openAPI2, options); + openAPINormalizer2.normalize(); + + Schema schema4 = openAPI2.getComponents().getSchemas().get("Person"); + assertEquals(((Schema) schema4.getProperties().get("array_property")).getNullable(), null); + assertEquals(((Schema) schema4.getProperties().get("set_property")).getNullable(), true); + assertEquals(((Schema) schema4.getProperties().get("map_property")).getNullable(), null); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/setContainerToNullable_test.yaml b/modules/openapi-generator/src/test/resources/3_0/setContainerToNullable_test.yaml new file mode 100644 index 00000000000..d277648f073 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/setContainerToNullable_test.yaml @@ -0,0 +1,90 @@ +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" + delete: + tags: + - person + x-internal: true + parameters: + - name: personId + in: path + required: true + description: The id of the person to retrieve + schema: + type: string + operationId: delete + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + put: + tags: + - person + parameters: + - name: personId + in: path + required: true + description: The id of the person to retrieve + schema: + type: string + operationId: put + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + +components: + schemas: + Person: + description: person + type: object + properties: + lastName: + type: string + firstName: + type: string + array_property: + type: array + items: + type: string + set_property: + type: array + uniqueItems: true + items: + type: string + map_property: + type: object + additionalProperties: + type: string