mirror of
https://github.com/Redocly/redoc.git
synced 2025-06-06 14:03:13 +03:00
feat: Support OAS 3.1 unevaluatedProperties (#1978)
This commit is contained in:
parent
60bc603e9b
commit
0755ac6f04
|
@ -305,6 +305,9 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
unevaluatedProperties:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
$ref: '#/components/schemas/ApiResponse'
|
||||||
security:
|
security:
|
||||||
- petstore_auth:
|
- petstore_auth:
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Schema definition with unevaluatedProperties",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Test": {
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"$ref": "#/components/schemas/Cat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Test2": {
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": true,
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Cat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Dog"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Test3": {
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"$ref": "#/components/schemas/Cat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cat": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Dog": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,5 +48,33 @@ describe('Models', () => {
|
||||||
expect(schema.fields).toHaveLength(1);
|
expect(schema.fields).toHaveLength(1);
|
||||||
expect(schema.pointer).toBe('#/components/schemas/Child');
|
expect(schema.pointer).toBe('#/components/schemas/Child');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('schemaDefinition should resolve unevaluatedProperties in properties', () => {
|
||||||
|
const spec = require('../fixtures/3.1/unevaluatedProperties.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts);
|
||||||
|
expect(schema.fields).toHaveLength(2);
|
||||||
|
expect(schema.fields![1].kind).toEqual('additionalProperties');
|
||||||
|
expect(schema.fields![1].schema.type).toEqual('any');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('schemaDefinition should resolve unevaluatedProperties in anyOf', () => {
|
||||||
|
const spec = require('../fixtures/3.1/unevaluatedProperties.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Test2, '', opts);
|
||||||
|
expect(schema.oneOf![0].fields).toHaveLength(2);
|
||||||
|
expect(schema.oneOf![0].fields![1].kind).toEqual('additionalProperties');
|
||||||
|
expect(schema.oneOf![1].fields).toHaveLength(2);
|
||||||
|
expect(schema.oneOf![1].fields![1].kind).toEqual('additionalProperties');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('schemaDefinition should resolve unevaluatedProperties type boolean', () => {
|
||||||
|
const spec = require('../fixtures/3.1/unevaluatedProperties.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Test3, '', opts);
|
||||||
|
expect(schema.fields).toHaveLength(2);
|
||||||
|
expect(schema.fields![1].kind).toEqual('additionalProperties');
|
||||||
|
expect(schema.fields![1].schema.type).toEqual('boolean');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -364,7 +364,7 @@ function buildFields(
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
): FieldModel[] {
|
): FieldModel[] {
|
||||||
const props = schema.properties || {};
|
const props = schema.properties || {};
|
||||||
const additionalProps = schema.additionalProperties;
|
const additionalProps = schema.additionalProperties || schema.unevaluatedProperties;
|
||||||
const defaults = schema.default;
|
const defaults = schema.default;
|
||||||
let fields = Object.keys(props || []).map(fieldName => {
|
let fields = Object.keys(props || []).map(fieldName => {
|
||||||
let field = props[fieldName];
|
let field = props[fieldName];
|
||||||
|
|
|
@ -114,6 +114,7 @@ export interface OpenAPISchema {
|
||||||
type?: string | string[];
|
type?: string | string[];
|
||||||
properties?: { [name: string]: OpenAPISchema };
|
properties?: { [name: string]: OpenAPISchema };
|
||||||
additionalProperties?: boolean | OpenAPISchema;
|
additionalProperties?: boolean | OpenAPISchema;
|
||||||
|
unevaluatedProperties?: boolean | OpenAPISchema;
|
||||||
description?: string;
|
description?: string;
|
||||||
default?: any;
|
default?: any;
|
||||||
items?: OpenAPISchema;
|
items?: OpenAPISchema;
|
||||||
|
|
|
@ -2776,6 +2776,10 @@ try {
|
||||||
"application/json": Object {
|
"application/json": Object {
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"$ref": "#/components/schemas/ApiResponse",
|
"$ref": "#/components/schemas/ApiResponse",
|
||||||
|
"unevaluatedProperties": Object {
|
||||||
|
"format": "int32",
|
||||||
|
"type": "integer",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -146,7 +146,14 @@ describe('Utils', () => {
|
||||||
string: ['pattern', 'minLength', 'maxLength'],
|
string: ['pattern', 'minLength', 'maxLength'],
|
||||||
|
|
||||||
array: ['items', 'maxItems', 'minItems', 'uniqueItems'],
|
array: ['items', 'maxItems', 'minItems', 'uniqueItems'],
|
||||||
object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties'],
|
object: [
|
||||||
|
'maxProperties',
|
||||||
|
'minProperties',
|
||||||
|
'required',
|
||||||
|
'additionalProperties',
|
||||||
|
'unevaluatedProperties',
|
||||||
|
'properties',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(tests).forEach(name => {
|
Object.keys(tests).forEach(name => {
|
||||||
|
@ -212,6 +219,17 @@ describe('Utils', () => {
|
||||||
expect(isPrimitiveType(schema)).toEqual(false);
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return false for array contains array type and schema has items (unevaluatedProperties)', () => {
|
||||||
|
const schema = {
|
||||||
|
type: ['array'],
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
unevaluatedProperties: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return false for array contains object and array types and schema has items', () => {
|
it('should return false for array contains object and array types and schema has items', () => {
|
||||||
const schema = {
|
const schema = {
|
||||||
type: ['array', 'object'],
|
type: ['array', 'object'],
|
||||||
|
@ -223,6 +241,17 @@ describe('Utils', () => {
|
||||||
expect(isPrimitiveType(schema)).toEqual(false);
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return false for array contains object and array types and schema has items (unevaluatedProperties)', () => {
|
||||||
|
const schema = {
|
||||||
|
type: ['array', 'object'],
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
unevaluatedProperties: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return false for array contains object and array types and schema has properties', () => {
|
it('should return false for array contains object and array types and schema has properties', () => {
|
||||||
const schema = {
|
const schema = {
|
||||||
type: ['array', 'object'],
|
type: ['array', 'object'],
|
||||||
|
@ -281,6 +310,17 @@ describe('Utils', () => {
|
||||||
expect(isPrimitiveType(schema)).toEqual(false);
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return false for object with unevaluatedProperties', () => {
|
||||||
|
const schema = {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
unevaluatedProperties: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(isPrimitiveType(schema)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with externally provided type', () => {
|
it('should work with externally provided type', () => {
|
||||||
const schema = {
|
const schema = {
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -97,6 +97,7 @@ const schemaKeywordTypes = {
|
||||||
minProperties: 'object',
|
minProperties: 'object',
|
||||||
required: 'object',
|
required: 'object',
|
||||||
additionalProperties: 'object',
|
additionalProperties: 'object',
|
||||||
|
unevaluatedProperties: 'object',
|
||||||
properties: 'object',
|
properties: 'object',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ export function isPrimitiveType(
|
||||||
isPrimitive =
|
isPrimitive =
|
||||||
schema.properties !== undefined
|
schema.properties !== undefined
|
||||||
? Object.keys(schema.properties).length === 0
|
? Object.keys(schema.properties).length === 0
|
||||||
: schema.additionalProperties === undefined;
|
: schema.additionalProperties === undefined && schema.unevaluatedProperties === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.items !== undefined && (type === 'array' || (isArray && type?.includes('array')))) {
|
if (schema.items !== undefined && (type === 'array' || (isArray && type?.includes('array')))) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user