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

Add rule to set container (array, set, map) to true #18128

Merged
merged 2 commits into from
Mar 17, 2024
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.
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 @@ -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"
```
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<String> setContainerToNullable = new HashSet<>();
boolean updateArrayToNullable;
boolean updateSetToNullable;
boolean updateMapToNullable;

// ============= end of rules =============

/**
Expand Down Expand Up @@ -199,6 +206,23 @@ public void processRules(Map<String, String> inputRules) {
}
}
}

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));
}
}
}

/**
Expand Down Expand Up @@ -445,8 +469,10 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
}

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);
Expand Down Expand Up @@ -498,6 +524,14 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
return schema;
}

private Schema normalizeArraySchema(Schema schema) {
return processSetArraytoNullable(schema);
}

private Schema normalizeMapSchema(Schema schema) {
return processSetMapToNullable(schema);
}

private Schema normalizeSimpleSchema(Schema schema, Set<Schema> visitedSchemas) {
return processNormalize31Spec(schema, visitedSchemas);
}
Expand Down Expand Up @@ -864,6 +898,60 @@ private Schema processSimplifyOneOf(Schema schema) {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,4 +475,42 @@ public void testComposedSchemaDoesNotThrow() {
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<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Loading