mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-11-04 01:37:32 +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