fix additional properties handling in 3.1 spec (#22056)

This commit is contained in:
William Cheng 2025-10-03 11:06:39 +08:00 committed by GitHub
parent 4121803442
commit 1d7d399ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 182 additions and 3 deletions

View File

@ -724,15 +724,17 @@ public class OpenAPINormalizer {
if (skipNormalization(schema, visitedSchemas)) { if (skipNormalization(schema, visitedSchemas)) {
return schema; return schema;
} }
if (ModelUtils.isNullTypeSchema(openAPI, schema)) {
return schema;
}
markSchemaAsVisited(schema, visitedSchemas); markSchemaAsVisited(schema, visitedSchemas);
if (ModelUtils.isArraySchema(schema)) { // array if (ModelUtils.isArraySchema(schema)) { // array
Schema result = normalizeArraySchema(schema); Schema result = normalizeArraySchema(schema);
normalizeSchema(result.getItems(), visitedSchemas); normalizeSchema(result.getItems(), visitedSchemas);
return result; return result;
} else if (schema.getAdditionalProperties() instanceof Schema) { // map
normalizeMapSchema(schema);
normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas);
} else if (ModelUtils.isOneOf(schema)) { // oneOf } else if (ModelUtils.isOneOf(schema)) { // oneOf
return normalizeOneOf(schema, visitedSchemas); return normalizeOneOf(schema, visitedSchemas);
} else if (ModelUtils.isAnyOf(schema)) { // anyOf } else if (ModelUtils.isAnyOf(schema)) { // anyOf
@ -769,6 +771,9 @@ public class OpenAPINormalizer {
return schema; return schema;
} else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
normalizeProperties(schema.getProperties(), visitedSchemas); normalizeProperties(schema.getProperties(), visitedSchemas);
} else if (schema.getAdditionalProperties() instanceof Schema) { // map
normalizeMapSchema(schema);
normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas);
} else if (schema instanceof BooleanSchema) { } else if (schema instanceof BooleanSchema) {
normalizeBooleanSchema(schema, visitedSchemas); normalizeBooleanSchema(schema, visitedSchemas);
} else if (schema instanceof IntegerSchema) { } else if (schema instanceof IntegerSchema) {
@ -1012,6 +1017,7 @@ public class OpenAPINormalizer {
if (schema.getAnyOf() == null) { if (schema.getAnyOf() == null) {
return schema; return schema;
} }
for (int i = 0; i < schema.getAnyOf().size(); i++) { for (int i = 0; i < schema.getAnyOf().size(); i++) {
// normalize anyOf sub schemas one by one // normalize anyOf sub schemas one by one
Object item = schema.getAnyOf().get(i); Object item = schema.getAnyOf().get(i);

View File

@ -971,6 +971,11 @@ public class OpenAPINormalizerTest {
Schema schema21 = openAPI.getComponents().getSchemas().get("SingleAnyOfTest"); Schema schema21 = openAPI.getComponents().getSchemas().get("SingleAnyOfTest");
assertEquals(schema21.getAnyOf().size(), 1); assertEquals(schema21.getAnyOf().size(), 1);
Schema schema23 = openAPI.getComponents().getSchemas().get("PropertiesWithAnyOf");
assertEquals(((Schema) schema23.getProperties().get("anyof_nullable_string")).getAnyOf().size(), 2);
assertEquals(((Schema) schema23.getProperties().get("anyof_nullable_number")).getAnyOf().size(), 2);
// start the normalization
Map<String, String> options = new HashMap<>(); Map<String, String> options = new HashMap<>();
options.put("SIMPLIFY_ONEOF_ANYOF", "true"); options.put("SIMPLIFY_ONEOF_ANYOF", "true");
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
@ -1025,6 +1030,14 @@ public class OpenAPINormalizerTest {
assertEquals(schema22.getAnyOf(), null); assertEquals(schema22.getAnyOf(), null);
assertEquals(schema22.getTypes(), Set.of("string")); assertEquals(schema22.getTypes(), Set.of("string"));
assertEquals(schema22.getEnum().size(), 2); assertEquals(schema22.getEnum().size(), 2);
Schema schema24 = openAPI.getComponents().getSchemas().get("PropertiesWithAnyOf");
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getAnyOf(), null);
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getNullable(), true);
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getTypes().size(), 1);
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getAnyOf(), null);
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getNullable(), true);
assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getTypes().size(), 1);
} }
@Test @Test

View File

@ -623,6 +623,47 @@ public class ModelUtilsTest {
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
} }
@Test
public void isNullTypeSchemaTestWith31Spec() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/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));
// first element (getAnyOf().get(0)) is a string. no need to test
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)));
schema = openAPI.getComponents().getSchemas().get("OneOfRef");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(0)));
schema = openAPI.getComponents().getSchemas().get("OneOfMultiRef");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(0)));
assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(1)));
schema = openAPI.getComponents().getSchemas().get("JustDescription");
assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema));
}
@Test @Test
public void isUnsupportedSchemaTest() { public void isUnsupportedSchemaTest() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml"); OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml");

View File

@ -0,0 +1,107 @@
openapi: 3.1.0
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'
string_ref:
anyOf:
- $ref: '#/components/schemas/StringRef'
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
StringRef:
type: string
OneOfAnyType:
oneOf:
- type: object
- type: boolean
- type: number
- type: string
- type: integer
- type: array
items: {}
OneOfRef:
oneOf:
- $ref: '#/components/schemas/IntegerRef'
OneOfMultiRef:
oneOf:
- $ref: '#/components/schemas/IntegerRef'
- $ref: '#/components/schemas/StringRef'
JustDescription:
description: A schema with just description

View File

@ -77,6 +77,18 @@ components:
oneOf: oneOf:
- $ref: '#/components/schemas/Number' - $ref: '#/components/schemas/Number'
- $ref: '#/components/schemas/Number2' - $ref: '#/components/schemas/Number2'
PropertiesWithAnyOf:
additionalProperties: false
type: object
properties:
anyof_nullable_string:
anyOf:
- type: string
- type: null
anyof_nullable_number:
anyOf:
- type: number
- type: null
Number: Number:
enum: enum:
- one - one