Skip to content

Commit

Permalink
Added validation for _info.yaml (#281)
Browse files Browse the repository at this point in the history
DRY'ed JSON schema validation logic

Signed-off-by: Theo Truong <[email protected]>
  • Loading branch information
nhtruong authored May 4, 2024
1 parent a8822aa commit 87e0693
Show file tree
Hide file tree
Showing 19 changed files with 127 additions and 31 deletions.
44 changes: 44 additions & 0 deletions json_schemas/_info.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
$schema: http://json-schema.org/draft-07/schema#

type: object
properties:
title:
type: string
summary:
type: string
description:
type: string
termsOfService:
type: string
format: uri
contact:
$comment: https://spec.openapis.org/oas/v3.1.0#contact-object
type: object
properties:
name:
type: string
url:
type: string
format: uri
email:
type: string
format: email
license:
$comment: https://spec.openapis.org/oas/v3.1.0#license-object
type: object
properties:
name:
type: string
identifier:
type: string
url:
type: string
format: uri
required:
- name
version:
type: string
required:
- title
- version
- $schema
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
$schema: http://json-schema.org/draft-07/schema#

type: object
patternProperties:
^\$schema$:
Expand Down
5 changes: 3 additions & 2 deletions spec/_info.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
title: OpenSearch API
description: OpenSearch API
$schema: ../json_schemas/_info.schema.yaml

title: OpenSearch API Specification
version: 1.0.0
2 changes: 1 addition & 1 deletion spec/_superseded_operations.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$schema: ../json_schemas/_superseded_operations.yaml
$schema: ../json_schemas/_superseded_operations.schema.yaml

/_opendistro/_alerting/destinations:
superseded_by: /_plugins/_alerting/destinations
Expand Down
6 changes: 4 additions & 2 deletions tools/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ export function sort_by_keys (obj: Record<string, any>, priorities: string[] = [
})
}

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

export function write_yaml (file_path: string, content: Record<string, any>): void {
Expand Down
10 changes: 7 additions & 3 deletions tools/linter/SpecValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import NamespacesFolder from './components/NamespacesFolder'
import { type ValidationError } from '../types'
import SchemaRefsValidator from './SchemaRefsValidator'
import SupersededOperationsFile from './components/SupersededOperationsFile'
import InfoFile from './components/InfoFile'

export default class SpecValidator {
superseded_ops_files: SupersededOperationsFile
superseded_ops_file: SupersededOperationsFile
info_file: InfoFile
namespaces_folder: NamespacesFolder
schemas_folder: SchemasFolder
schema_refs_validator: SchemaRefsValidator

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

return [
...this.schema_refs_validator.validate(),
...this.superseded_ops_files.validate()
...this.superseded_ops_file.validate(),
...this.info_file.validate()
]
}
}
5 changes: 5 additions & 0 deletions tools/linter/components/InfoFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import FileValidator from './base/FileValidator'

export default class InfoFile extends FileValidator {
has_json_schema = true
}
19 changes: 1 addition & 18 deletions tools/linter/components/SupersededOperationsFile.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
import FileValidator from './base/FileValidator'
import ajv from 'ajv'
import { type ValidationError } from '../../types'
import { read_yaml } from '../../helpers'

export default class SupersededOperationsFile extends FileValidator {
readonly JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml'

validate (): ValidationError[] {
return [
this.validate_json_schema()
].filter(e => e) as ValidationError[]
}

validate_json_schema (): ValidationError | undefined {
const schema = read_yaml(this.JSON_SCHEMA_PATH)
const validator = (new ajv()).compile(schema)
if (!validator(this.spec())) {
return this.error(`File content does not match JSON schema found in '${this.JSON_SCHEMA_PATH}':\n ${JSON.stringify(validator.errors, null, 2)}`)
}
}
has_json_schema = true
}
20 changes: 19 additions & 1 deletion tools/linter/components/base/FileValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import ValidatorBase from './ValidatorBase'
import { type ValidationError } from '../../../types'
import { type OpenAPIV3 } from 'openapi-types'
import { read_yaml } from '../../../helpers'
import AJV from 'ajv'
import addFormats from 'ajv-formats'

export default class FileValidator extends ValidatorBase {
file_path: string
has_json_schema: boolean = false
protected _spec: OpenAPIV3.Document | undefined

constructor (file_path: string) {
Expand All @@ -23,11 +26,13 @@ export default class FileValidator extends ValidatorBase {
if (extension_error) return [extension_error]
const yaml_error = this.validate_yaml()
if (yaml_error) return [yaml_error]
const json_schema_error = this.validate_json_schema()
if (json_schema_error) return [json_schema_error]
return this.validate_file()
}

validate_file (): ValidationError[] {
throw new Error('Method not implemented.')
return []
}

validate_extension (): ValidationError | undefined {
Expand All @@ -41,4 +46,17 @@ export default class FileValidator extends ValidatorBase {
return this.error('Unable to read or parse YAML.', 'File Content')
}
}

validate_json_schema (): ValidationError | undefined {
if (!this.has_json_schema) return
const json_schema_path: string = (this.spec() as any).$schema ?? ''
if (json_schema_path === '') return this.error('JSON Schema is required but not found in this file.', '$schema')
const schema = read_yaml(json_schema_path)
const ajv = new AJV({ schemaId: 'id' })
addFormats(ajv)
const validator = ajv.compile(schema)
if (!validator(this.spec())) {
return this.error(`File content does not match JSON schema found in '${json_schema_path}':\n ${JSON.stringify(validator.errors, null, 2)}`)
}
}
}
2 changes: 1 addition & 1 deletion tools/merger/OpenApiMerger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default class OpenApiMerger {
this.root_folder = fs.realpathSync(root_folder)
this.spec = {
openapi: '3.1.0',
info: read_yaml(`${this.root_folder}/_info.yaml`),
info: read_yaml(`${this.root_folder}/_info.yaml`, true),
paths: {},
components: {
parameters: {},
Expand Down
17 changes: 17 additions & 0 deletions tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@types/lodash": "^4.14.202",
"@types/node": "^20.10.3",
"ajv": "^8.13.0",
"ajv-formats": "^3.0.1",
"lodash": "^4.17.21",
"ts-node": "^10.9.1",
"typescript": "^5.4.5",
Expand Down
12 changes: 12 additions & 0 deletions tools/test/linter/InfoFile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import InfoFile from '../../linter/components/InfoFile'

test('validate()', () => {
const validator = new InfoFile('./test/linter/fixtures/_info.yaml')
expect(validator.validate()).toEqual([
{
file: 'fixtures/_info.yaml',
location: '$schema',
message: 'JSON Schema is required but not found in this file.'
}
])
})
2 changes: 1 addition & 1 deletion tools/test/linter/SupersededOperationsFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ test('validate()', () => {
expect(validator.validate()).toEqual([
{
file: 'fixtures/_superseded_operations.yaml',
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]"
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]"
}
])
})
2 changes: 2 additions & 0 deletions tools/test/linter/fixtures/_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
title: OpenSearch API Specification
version: 1.0.0
2 changes: 1 addition & 1 deletion tools/test/linter/fixtures/_superseded_operations.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$schema: ../../../../json_schemas/_superseded_operations.yaml
$schema: ../json_schemas/_superseded_operations.schema.yaml

/hello/world:
superseded_by: /goodbye/world
Expand Down
4 changes: 4 additions & 0 deletions tools/test/linter/fixtures/empty/_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$schema: ../json_schemas/_info.schema.yaml

title: ''
version: ''
Original file line number Diff line number Diff line change
@@ -1 +1 @@
$schema: ../../../../../json_schemas/_superseded_operations.yaml
$schema: ../json_schemas/_superseded_operations.schema.yaml
2 changes: 2 additions & 0 deletions tools/test/merger/fixtures/spec/_info.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$schema: should-be-ignored

title: OpenSearch API
description: OpenSearch API
version: 1.0.0

0 comments on commit 87e0693

Please sign in to comment.