refacot is null schema check (#19873)

This commit is contained in:
William Cheng 2024-10-15 17:17:28 +08:00 committed by GitHub
parent 2354d402a8
commit 3b3f9a770a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 193 additions and 72 deletions

View File

@ -951,68 +951,7 @@ public class OpenAPINormalizer {
return result;
}
/**
* Check if the schema is of type 'null' or schema itself is pointing to null
* <p>
* Return true if the schema's type is 'null' or not specified
*
* @param schema Schema
* @param openAPI OpenAPI
*
* @return true if schema is null type
*/
public boolean isNullTypeSchema(OpenAPI openAPI, Schema schema) {
if (schema == null) {
return true;
}
// dereference the schema
schema = ModelUtils.getReferencedSchema(openAPI, schema);
// allOf/anyOf/oneOf
if (ModelUtils.hasAllOf(schema) || ModelUtils.hasOneOf(schema) || ModelUtils.hasAnyOf(schema)) {
return false;
}
// schema with properties
if (schema.getProperties() != null) {
return false;
}
// convert referenced enum of null only to `nullable:true`
if (schema.getEnum() != null && schema.getEnum().size() == 1) {
if ("null".equals(String.valueOf(schema.getEnum().get(0)))) {
return true;
}
}
if (schema.getTypes() != null && !schema.getTypes().isEmpty()) {
// 3.1 spec
if (schema.getTypes().size() == 1) { // 1 type only
String type = (String) schema.getTypes().iterator().next();
return type == null || "null".equals(type);
} else { // more than 1 type so must not be just null
return false;
}
}
if (schema instanceof JsonSchema) { // 3.1 spec
if (Boolean.TRUE.equals(schema.getNullable())) {
return true;
}
// for `type: null`
if (schema.getTypes() == null && schema.get$ref() == null) {
return true;
}
} else { // 3.0.x or 2.x spec
if ((schema.getType() == null || schema.getType().equals("null")) && schema.get$ref() == null) {
return true;
}
}
return false;
}
/**
* If the schema is oneOf and the sub-schemas is null, set `nullable: true`
@ -1056,7 +995,7 @@ public class OpenAPINormalizer {
}
}
if (oneOfSchemas.removeIf(oneOf -> isNullTypeSchema(openAPI, oneOf))) {
if (oneOfSchemas.removeIf(oneOf -> ModelUtils.isNullTypeSchema(openAPI, oneOf))) {
schema.setNullable(true);
// if only one element left, simplify to just the element (schema)
@ -1192,7 +1131,7 @@ public class OpenAPINormalizer {
}
}
if (anyOfSchemas.removeIf(anyOf -> isNullTypeSchema(openAPI, anyOf))) {
if (anyOfSchemas.removeIf(anyOf -> ModelUtils.isNullTypeSchema(openAPI, anyOf))) {
schema.setNullable(true);
}

View File

@ -2199,6 +2199,69 @@ public class ModelUtils {
}
}
/**
* Check if the schema is of type 'null' or schema itself is pointing to null
* <p>
* Return true if the schema's type is 'null' or not specified
*
* @param schema Schema
* @param openAPI OpenAPI
*
* @return true if schema is null type
*/
public static boolean isNullTypeSchema(OpenAPI openAPI, Schema schema) {
if (schema == null) {
return true;
}
// dereference the schema
schema = ModelUtils.getReferencedSchema(openAPI, schema);
// allOf/anyOf/oneOf
if (ModelUtils.hasAllOf(schema) || ModelUtils.hasOneOf(schema) || ModelUtils.hasAnyOf(schema)) {
return false;
}
// schema with properties
if (schema.getProperties() != null) {
return false;
}
// convert referenced enum of null only to `nullable:true`
if (schema.getEnum() != null && schema.getEnum().size() == 1) {
if ("null".equals(String.valueOf(schema.getEnum().get(0)))) {
return true;
}
}
if (schema.getTypes() != null && !schema.getTypes().isEmpty()) {
// 3.1 spec
if (schema.getTypes().size() == 1) { // 1 type only
String type = (String) schema.getTypes().iterator().next();
return type == null || "null".equals(type);
} else { // more than 1 type so must not be just null
return false;
}
}
if (schema instanceof JsonSchema) { // 3.1 spec
if (Boolean.TRUE.equals(schema.getNullable())) {
return true;
}
// for `type: null`
if (schema.getTypes() == null && schema.get$ref() == null) {
return true;
}
} else { // 3.0.x or 2.x spec
if ((schema.getType() == null || schema.getType().equals("null")) && schema.get$ref() == null) {
return true;
}
}
return false;
}
@FunctionalInterface
private interface OpenAPISchemaVisitor {

View File

@ -107,15 +107,6 @@ public class OpenAPINormalizerTest {
assertTrue(schema3.getEnum().size() > 0);
}
@Test
public void isNullTypeSchemaTest() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml");
Map<String, String> options = new HashMap<>();
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
Schema schema = openAPI.getComponents().getSchemas().get("AnyOfStringArrayOfString");
assertFalse(openAPINormalizer.isNullTypeSchema(openAPI, schema));
}
@Test
public void testOpenAPINormalizerSimplifyOneOfAnyOf() {
// to test the rule SIMPLIFY_ONEOF_ANYOF

View File

@ -22,6 +22,7 @@ import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import org.openapitools.codegen.OpenAPINormalizer;
import org.openapitools.codegen.TestUtils;
import org.testng.Assert;
import org.testng.annotations.Test;
@ -29,6 +30,9 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
import java.util.*;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
public class ModelUtilsTest {
@Test
@ -469,4 +473,32 @@ public class ModelUtilsTest {
Assert.assertNotNull(ModelUtils.getSchemaItems((Schema) arrayWithPrefixItems.getProperties().get("with_prefixitems")));
Assert.assertNotNull(ModelUtils.getSchemaItems((Schema) arrayWithPrefixItems.getProperties().get("without_items")));
}
@Test
public void isNullTypeSchemaTest() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/null_schema_test.yaml");
Map<String, String> options = new HashMap<>();
Schema schema = openAPI.getComponents().getSchemas().get("AnyOfStringArrayOfString");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
schema = openAPI.getComponents().getSchemas().get("IntegerRef");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
schema = openAPI.getComponents().getSchemas().get("OneOfAnyType");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
schema = openAPI.getComponents().getSchemas().get("AnyOfAnyType");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
schema = openAPI.getComponents().getSchemas().get("Parent");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
// the dummy property is a ref to integer
assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getProperties().get("dummy")));
schema = openAPI.getComponents().getSchemas().get("AnyOfTest");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(1)));
assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(2)));
assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(3)));
}
}

View File

@ -0,0 +1,96 @@
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:
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/AnyOfTest"
components:
schemas:
AnyOfTest:
description: to test anyOf
anyOf:
- type: string
- type: 'null'
- type: null
- $ref: null
OneOfTest:
description: to test oneOf
oneOf:
- type: integer
- type: 'null'
- type: null
- $ref: null
OneOfTest2:
description: to test oneOf
oneOf:
- type: string
- type: 'null'
OneOfNullableTest:
description: to test oneOf nullable
oneOf:
- type: integer
- type: string
- $ref: null
Parent:
type: object
properties:
dummy:
$ref: '#/components/schemas/IntegerRef'
number:
anyOf:
- $ref: '#/components/schemas/Number'
AnyOfStringArrayOfString:
anyOf:
- type: string
- type: array
items:
type: string
AnyOfAnyType:
anyOf:
- type: boolean
- type: array
items: {}
- type: object
- type: string
- type: number
- type: integer
AnyOfAnyTypeWithRef:
anyOf:
- type: boolean
- type: array
items: { }
- type: object
- type: string
- type: number
- $ref: '#/components/schemas/IntegerRef'
IntegerRef:
type: integer
OneOfAnyType:
oneOf:
- type: object
- type: boolean
- type: number
- type: string
- type: integer
- type: array
items: {}