add rule to set primitive types to nullable (#18258)

This commit is contained in:
William Cheng
2024-04-01 11:54:21 +08:00
committed by GitHub
parent 2fac8e34db
commit 2d7f0496ec
4 changed files with 163 additions and 16 deletions

View File

@@ -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"
```

View File

@@ -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 =============
/**
@@ -143,6 +151,9 @@ public class OpenAPINormalizer {
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 class OpenAPINormalizer {
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 @@ public class OpenAPINormalizer {
}
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) {
@@ -917,25 +951,49 @@ public class OpenAPINormalizer {
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 @@ public class OpenAPINormalizer {
}
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;

View File

@@ -514,6 +514,48 @@ public class OpenAPINormalizerTest {
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

View File

@@ -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