Skip to content

Commit 87e0693

Browse files
authored
Added validation for _info.yaml (#281)
DRY'ed JSON schema validation logic Signed-off-by: Theo Truong <[email protected]>
1 parent a8822aa commit 87e0693

19 files changed

+127
-31
lines changed

json_schemas/_info.schema.yaml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
$schema: http://json-schema.org/draft-07/schema#
2+
3+
type: object
4+
properties:
5+
title:
6+
type: string
7+
summary:
8+
type: string
9+
description:
10+
type: string
11+
termsOfService:
12+
type: string
13+
format: uri
14+
contact:
15+
$comment: https://spec.openapis.org/oas/v3.1.0#contact-object
16+
type: object
17+
properties:
18+
name:
19+
type: string
20+
url:
21+
type: string
22+
format: uri
23+
email:
24+
type: string
25+
format: email
26+
license:
27+
$comment: https://spec.openapis.org/oas/v3.1.0#license-object
28+
type: object
29+
properties:
30+
name:
31+
type: string
32+
identifier:
33+
type: string
34+
url:
35+
type: string
36+
format: uri
37+
required:
38+
- name
39+
version:
40+
type: string
41+
required:
42+
- title
43+
- version
44+
- $schema

json_schemas/_superseded_operations.yaml json_schemas/_superseded_operations.schema.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
$schema: http://json-schema.org/draft-07/schema#
2+
23
type: object
34
patternProperties:
45
^\$schema$:

spec/_info.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
title: OpenSearch API
2-
description: OpenSearch API
1+
$schema: ../json_schemas/_info.schema.yaml
2+
3+
title: OpenSearch API Specification
34
version: 1.0.0

spec/_superseded_operations.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
$schema: ../json_schemas/_superseded_operations.yaml
1+
$schema: ../json_schemas/_superseded_operations.schema.yaml
22

33
/_opendistro/_alerting/destinations:
44
superseded_by: /_plugins/_alerting/destinations

tools/helpers.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export function sort_by_keys (obj: Record<string, any>, priorities: string[] = [
4343
})
4444
}
4545

46-
export function read_yaml (file_path: string): Record<string, any> {
47-
return YAML.parse(fs.readFileSync(file_path, 'utf8'))
46+
export function read_yaml (file_path: string, exclude_schema: boolean = false): Record<string, any> {
47+
const doc = YAML.parse(fs.readFileSync(file_path, 'utf8'))
48+
if (exclude_schema) delete doc.$schema
49+
return doc
4850
}
4951

5052
export function write_yaml (file_path: string, content: Record<string, any>): void {

tools/linter/SpecValidator.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import NamespacesFolder from './components/NamespacesFolder'
33
import { type ValidationError } from '../types'
44
import SchemaRefsValidator from './SchemaRefsValidator'
55
import SupersededOperationsFile from './components/SupersededOperationsFile'
6+
import InfoFile from './components/InfoFile'
67

78
export default class SpecValidator {
8-
superseded_ops_files: SupersededOperationsFile
9+
superseded_ops_file: SupersededOperationsFile
10+
info_file: InfoFile
911
namespaces_folder: NamespacesFolder
1012
schemas_folder: SchemasFolder
1113
schema_refs_validator: SchemaRefsValidator
1214

1315
constructor (root_folder: string) {
14-
this.superseded_ops_files = new SupersededOperationsFile(`${root_folder}/_superseded_operations.yaml`)
16+
this.superseded_ops_file = new SupersededOperationsFile(`${root_folder}/_superseded_operations.yaml`)
17+
this.info_file = new InfoFile(`${root_folder}/_info.yaml`)
1518
this.namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`)
1619
this.schemas_folder = new SchemasFolder(`${root_folder}/schemas`)
1720
this.schema_refs_validator = new SchemaRefsValidator(this.namespaces_folder, this.schemas_folder)
@@ -26,7 +29,8 @@ export default class SpecValidator {
2629

2730
return [
2831
...this.schema_refs_validator.validate(),
29-
...this.superseded_ops_files.validate()
32+
...this.superseded_ops_file.validate(),
33+
...this.info_file.validate()
3034
]
3135
}
3236
}

tools/linter/components/InfoFile.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import FileValidator from './base/FileValidator'
2+
3+
export default class InfoFile extends FileValidator {
4+
has_json_schema = true
5+
}
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
import FileValidator from './base/FileValidator'
2-
import ajv from 'ajv'
3-
import { type ValidationError } from '../../types'
4-
import { read_yaml } from '../../helpers'
52

63
export default class SupersededOperationsFile extends FileValidator {
7-
readonly JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml'
8-
9-
validate (): ValidationError[] {
10-
return [
11-
this.validate_json_schema()
12-
].filter(e => e) as ValidationError[]
13-
}
14-
15-
validate_json_schema (): ValidationError | undefined {
16-
const schema = read_yaml(this.JSON_SCHEMA_PATH)
17-
const validator = (new ajv()).compile(schema)
18-
if (!validator(this.spec())) {
19-
return this.error(`File content does not match JSON schema found in '${this.JSON_SCHEMA_PATH}':\n ${JSON.stringify(validator.errors, null, 2)}`)
20-
}
21-
}
4+
has_json_schema = true
225
}

tools/linter/components/base/FileValidator.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import ValidatorBase from './ValidatorBase'
22
import { type ValidationError } from '../../../types'
33
import { type OpenAPIV3 } from 'openapi-types'
44
import { read_yaml } from '../../../helpers'
5+
import AJV from 'ajv'
6+
import addFormats from 'ajv-formats'
57

68
export default class FileValidator extends ValidatorBase {
79
file_path: string
10+
has_json_schema: boolean = false
811
protected _spec: OpenAPIV3.Document | undefined
912

1013
constructor (file_path: string) {
@@ -23,11 +26,13 @@ export default class FileValidator extends ValidatorBase {
2326
if (extension_error) return [extension_error]
2427
const yaml_error = this.validate_yaml()
2528
if (yaml_error) return [yaml_error]
29+
const json_schema_error = this.validate_json_schema()
30+
if (json_schema_error) return [json_schema_error]
2631
return this.validate_file()
2732
}
2833

2934
validate_file (): ValidationError[] {
30-
throw new Error('Method not implemented.')
35+
return []
3136
}
3237

3338
validate_extension (): ValidationError | undefined {
@@ -41,4 +46,17 @@ export default class FileValidator extends ValidatorBase {
4146
return this.error('Unable to read or parse YAML.', 'File Content')
4247
}
4348
}
49+
50+
validate_json_schema (): ValidationError | undefined {
51+
if (!this.has_json_schema) return
52+
const json_schema_path: string = (this.spec() as any).$schema ?? ''
53+
if (json_schema_path === '') return this.error('JSON Schema is required but not found in this file.', '$schema')
54+
const schema = read_yaml(json_schema_path)
55+
const ajv = new AJV({ schemaId: 'id' })
56+
addFormats(ajv)
57+
const validator = ajv.compile(schema)
58+
if (!validator(this.spec())) {
59+
return this.error(`File content does not match JSON schema found in '${json_schema_path}':\n ${JSON.stringify(validator.errors, null, 2)}`)
60+
}
61+
}
4462
}

tools/merger/OpenApiMerger.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default class OpenApiMerger {
1717
this.root_folder = fs.realpathSync(root_folder)
1818
this.spec = {
1919
openapi: '3.1.0',
20-
info: read_yaml(`${this.root_folder}/_info.yaml`),
20+
info: read_yaml(`${this.root_folder}/_info.yaml`, true),
2121
paths: {},
2222
components: {
2323
parameters: {},

tools/package-lock.json

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@types/lodash": "^4.14.202",
1616
"@types/node": "^20.10.3",
1717
"ajv": "^8.13.0",
18+
"ajv-formats": "^3.0.1",
1819
"lodash": "^4.17.21",
1920
"ts-node": "^10.9.1",
2021
"typescript": "^5.4.5",

tools/test/linter/InfoFile.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import InfoFile from '../../linter/components/InfoFile'
2+
3+
test('validate()', () => {
4+
const validator = new InfoFile('./test/linter/fixtures/_info.yaml')
5+
expect(validator.validate()).toEqual([
6+
{
7+
file: 'fixtures/_info.yaml',
8+
location: '$schema',
9+
message: 'JSON Schema is required but not found in this file.'
10+
}
11+
])
12+
})

tools/test/linter/SupersededOperationsFile.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ test('validate()', () => {
55
expect(validator.validate()).toEqual([
66
{
77
file: 'fixtures/_superseded_operations.yaml',
8-
message: "File content does not match JSON schema found in '../json_schemas/_superseded_operations.yaml':\n [\n {\n \"instancePath\": \"/~1hello~1world/operations/1\",\n \"schemaPath\": \"#/patternProperties/%5E~1/properties/operations/items/enum\",\n \"keyword\": \"enum\",\n \"params\": {\n \"allowedValues\": [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"HEAD\",\n \"OPTIONS\",\n \"PATCH\"\n ]\n },\n \"message\": \"must be equal to one of the allowed values\"\n }\n]"
8+
message: "File content does not match JSON schema found in '../json_schemas/_superseded_operations.schema.yaml':\n [\n {\n \"instancePath\": \"/~1hello~1world/operations/1\",\n \"schemaPath\": \"#/patternProperties/%5E~1/properties/operations/items/enum\",\n \"keyword\": \"enum\",\n \"params\": {\n \"allowedValues\": [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"HEAD\",\n \"OPTIONS\",\n \"PATCH\"\n ]\n },\n \"message\": \"must be equal to one of the allowed values\"\n }\n]"
99
}
1010
])
1111
})

tools/test/linter/fixtures/_info.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
title: OpenSearch API Specification
2+
version: 1.0.0

tools/test/linter/fixtures/_superseded_operations.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
$schema: ../../../../json_schemas/_superseded_operations.yaml
1+
$schema: ../json_schemas/_superseded_operations.schema.yaml
22

33
/hello/world:
44
superseded_by: /goodbye/world
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
$schema: ../json_schemas/_info.schema.yaml
2+
3+
title: ''
4+
version: ''
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
$schema: ../../../../../json_schemas/_superseded_operations.yaml
1+
$schema: ../json_schemas/_superseded_operations.schema.yaml
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
$schema: should-be-ignored
2+
13
title: OpenSearch API
24
description: OpenSearch API
35
version: 1.0.0

0 commit comments

Comments
 (0)