-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Validate all schemas defined in the spec as valid JSON schemas
- Updated default arg values for merge.ts - Added silencing feature for merger tool - Added SchemasValidator Signed-off-by: Theo Truong <[email protected]>
- Loading branch information
Showing
21 changed files
with
357 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import AJV from 'ajv' | ||
import addFormats from 'ajv-formats' | ||
import OpenApiMerger from '../merger/OpenApiMerger' | ||
import { type ValidationError } from '../types' | ||
|
||
export default class SchemasValidator { | ||
root_folder: string | ||
spec: Record<string, any> = {} | ||
ajv: AJV | ||
|
||
constructor (root_folder: string) { | ||
this.root_folder = root_folder | ||
this.ajv = new AJV() | ||
addFormats(this.ajv) | ||
} | ||
|
||
validate (): ValidationError[] { | ||
this.spec = new OpenApiMerger(this.root_folder, true).merge().components as Record<string, any> | ||
const named_schemas_errors = this.validate_named_schemas() | ||
if (named_schemas_errors.length > 0) return named_schemas_errors | ||
return [ | ||
...this.validate_parameter_schemas(), | ||
...this.validate_request_body_schemas(), | ||
...this.validate_response_schemas() | ||
] | ||
} | ||
|
||
validate_named_schemas (): ValidationError[] { | ||
return Object.entries(this.spec.schemas as Record<string, any>).map(([key, _schema]) => { | ||
const schema = _schema as Record<string, any> | ||
const error = this.validate_schema(schema) | ||
if (error == null) { | ||
this.ajv.addSchema(schema, `#/components/schemas/${key}`) | ||
return null | ||
} | ||
|
||
const file = `schemas/${key.split(':')[0]}.yaml` | ||
const location = `#/components/schemas/${key.split(':')[1]}` | ||
return this.error(file, location, error) | ||
}).filter((error) => error != null) as ValidationError[] | ||
} | ||
|
||
validate_parameter_schemas (): ValidationError[] { | ||
return Object.entries(this.spec.parameters as Record<string, any>).map(([key, param]) => { | ||
const error = this.validate_schema(param.schema as Record<string, any>) | ||
if (error == null) return | ||
|
||
const namespace = this.group_to_namespace(key.split('::')[0]) | ||
const file = namespace === '_global' ? '_global_parameters.yaml' : `namespaces/${namespace}.yaml` | ||
const location = namespace === '_global' ? param.name as string : `#/components/parameters/${key}` | ||
return this.error(file, location, error) | ||
}).filter((error) => error != null) as ValidationError[] | ||
} | ||
|
||
validate_request_body_schemas (): ValidationError[] { | ||
return Object.entries(this.spec.requestBodies as Record<string, any>).flatMap(([namespace, body]) => { | ||
const file = `namespaces/${namespace}.yaml` | ||
const location = `#/components/requestBodies/${namespace}` | ||
return this.validate_content_schemas(file, location, body.content as Record<string, any>) | ||
}) | ||
} | ||
|
||
validate_response_schemas (): ValidationError[] { | ||
return Object.entries(this.spec.responses as Record<string, any>).flatMap(([key, response]) => { | ||
const namespace = this.group_to_namespace(key.split('@')[0]) | ||
const file = `namespaces/${namespace}.yaml` | ||
const location = `#/components/responses/${key}` | ||
const content = response.content as Record<string, any> | ||
return this.validate_content_schemas(file, location, content) | ||
}) | ||
} | ||
|
||
validate_content_schemas (file: string, location: string, content: Record<string, any> | undefined): ValidationError[] { | ||
return Object.entries(content ?? {}).map(([media_type, value]) => { | ||
const schema = value.schema as Record<string, any> | ||
const error = this.validate_schema(schema) | ||
if (error != null) return this.error(file, `${location}/content/${media_type}`, error) | ||
}).filter(e => e != null) as ValidationError[] | ||
} | ||
|
||
validate_schema (schema: Record<string, any>): Error | undefined { | ||
if (schema == null || schema.$ref != null) return | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
this.ajv.validateSchema(schema, true) | ||
} catch (error) { | ||
return error as Error | ||
} | ||
} | ||
|
||
group_to_namespace (group: string): string { | ||
if (group === '_global') return '_global' | ||
const [, namespace] = group.split('.').reverse() | ||
return namespace ?? '_core' | ||
} | ||
|
||
error (file: string, location: string, error: Error): ValidationError { | ||
return { file, location, message: error.message } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import SchemasValidator from '../../src/linter/SchemasValidator' | ||
|
||
test('validate() - named_schemas', () => { | ||
const validator = new SchemasValidator('./tools/tests/linter/fixtures/schemas_validator/named_schemas') | ||
expect(validator.validate()).toEqual([ | ||
{ | ||
file: 'schemas/actions.yaml', | ||
location: '#/components/schemas/Bark', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
}, | ||
{ | ||
file: 'schemas/animals.yaml', | ||
location: '#/components/schemas/Dog', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
} | ||
]) | ||
}) | ||
|
||
test('validate() - anonymous_schemas', () => { | ||
const validator = new SchemasValidator('./tools/tests/linter/fixtures/schemas_validator/anonymous_schemas') | ||
expect(validator.validate()).toEqual([ | ||
{ | ||
file: '_global_parameters.yaml', | ||
location: 'human', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
}, | ||
{ | ||
file: 'namespaces/_core.yaml', | ||
location: '#/components/parameters/adopt::path.docket', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
}, | ||
{ | ||
file: 'namespaces/adopt.yaml', | ||
location: '#/components/requestBodies/adopt/content/application/json', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
}, | ||
{ | ||
file: 'namespaces/_core.yaml', | ||
location: '#/components/responses/adopt@200/content/application/json', | ||
message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' | ||
} | ||
]) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/_global_parameters.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
openapi: 3.1.0 | ||
info: | ||
title: '' | ||
version: '' | ||
components: | ||
parameters: | ||
human: | ||
name: human | ||
in: query | ||
description: Whether to return human readable values for statistics. | ||
schema: | ||
type: bogus | ||
default: true |
1 change: 1 addition & 0 deletions
1
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/_info.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
1 change: 1 addition & 0 deletions
1
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/_superseded_operations.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
54 changes: 54 additions & 0 deletions
54
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/namespaces/shelter.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
openapi: 3.1.0 | ||
info: | ||
title: OpenSearch API | ||
description: OpenSearch API | ||
version: 1.0.0 | ||
paths: | ||
'/adopt/{animal}/dockets/{docket}': | ||
get: | ||
operationId: adopt.0 | ||
parameters: | ||
- $ref: '#/components/parameters/adopt::path.animal' | ||
- $ref: '#/components/parameters/adopt::path.docket' | ||
responses: | ||
'200': | ||
$ref: '#/components/responses/adopt@200' | ||
post: | ||
operationId: adopt.1 | ||
parameters: | ||
- $ref: '#/components/parameters/adopt::path.animal' | ||
- $ref: '#/components/parameters/adopt::path.docket' | ||
requestBody: | ||
$ref: '#/components/requestBodies/adopt' | ||
responses: | ||
'200': | ||
$ref: '#/components/responses/adopt@200' | ||
components: | ||
requestBodies: | ||
adopt: { | ||
content: { | ||
application/json: { | ||
schema: { | ||
type: object2 | ||
} | ||
} | ||
} | ||
} | ||
parameters: | ||
adopt::path.animal: | ||
name: animal | ||
in: path | ||
schema: | ||
$ref: '../schemas/animals.yaml#/components/schemas/Animal' | ||
adopt::path.docket: | ||
name: docket | ||
in: path | ||
schema: | ||
type: number2 | ||
responses: | ||
adopt@200: | ||
description: '' | ||
content: | ||
application/json: | ||
schema: | ||
type: object2 |
11 changes: 11 additions & 0 deletions
11
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/schemas/actions.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
openapi: 3.1.0 | ||
info: | ||
title: OpenSearch API | ||
description: OpenSearch API | ||
version: 1.0.0 | ||
components: | ||
schemas: | ||
Bark: | ||
type: string | ||
Meow: | ||
type: string |
21 changes: 21 additions & 0 deletions
21
tools/tests/linter/fixtures/schemas_validator/anonymous_schemas/schemas/animals.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
openapi: 3.1.0 | ||
info: | ||
title: OpenSearch API | ||
description: OpenSearch API | ||
version: 1.0.0 | ||
components: | ||
schemas: | ||
Animal: | ||
oneOf: | ||
- $ref: '#/components/schemas/Dog' | ||
- $ref: '#/components/schemas/Cat' | ||
Dog: | ||
type: object | ||
properties: | ||
bark: | ||
$ref: 'actions.yaml#/components/schemas/Bark' | ||
Cat: | ||
type: object | ||
properties: | ||
meow: | ||
$ref: 'actions.yaml#/components/schemas/Meow' |
Oops, something went wrong.