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 primitive types to nullable #18258

Merged
merged 1 commit into from
Apr 1, 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 @@ -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"
```
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> setPrimitiveTypesToNullable = new HashSet<>();
boolean updateStringToNullable;
boolean updateIntegerToNullable;
boolean updateNumberToNullable;
boolean updateBooleanToNullable;

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

/**
Expand Down Expand Up @@ -143,6 +151,9 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> 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);
Expand Down Expand Up @@ -223,6 +234,26 @@ public void processRules(Map<String, String> 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));
}
}
}

/**
Expand Down Expand Up @@ -533,15 +564,18 @@ private Schema normalizeMapSchema(Schema schema) {
}

private Schema normalizeSimpleSchema(Schema schema, Set<Schema> visitedSchemas) {
return processNormalize31Spec(schema, visitedSchemas);
Schema result = processNormalize31Spec(schema, visitedSchemas);
return processSetPrimitiveTypesToNullable(result);
}

private void normalizeBooleanSchema(Schema schema, Set<Schema> visitedSchemas) {
processSimplifyBooleanEnum(schema);
processSetPrimitiveTypesToNullable(schema);
}

private void normalizeIntegerSchema(Schema schema, Set<Schema> visitedSchemas) {
processAddUnsignedToIntegerWithInvalidMaxValue(schema);
processSetPrimitiveTypesToNullable(schema);
}

private void normalizeProperties(Map<String, Schema> properties, Set<Schema> visitedSchemas) {
Expand Down Expand Up @@ -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.
*
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading