From 0edf004215707087434e9fb5db179d7693aa6733 Mon Sep 17 00:00:00 2001 From: Theo Truong Date: Tue, 30 Apr 2024 16:30:12 -0600 Subject: [PATCH 1/3] Validate _superseded_operations.yaml against its JSON schema Signed-off-by: Theo Truong --- json_schemas/_superseded_operations.yaml | 20 ++++++++++++++ spec/_superseded_operations.yaml | 2 ++ tools/linter/SpecValidator.ts | 6 ++++- .../components/SupersededOperationsFile.ts | 26 +++++++++++++++++++ tools/merger/SupersededOpsGenerator.ts | 1 + tools/package-lock.json | 15 ++++++----- tools/package.json | 5 ++-- .../linter/SupersededOperationsFile.test.ts | 11 ++++++++ .../fixtures/_superseded_operations.yaml | 7 +++++ .../empty/_superseded_operations.yaml | 1 + 10 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 json_schemas/_superseded_operations.yaml create mode 100644 tools/linter/components/SupersededOperationsFile.ts create mode 100644 tools/test/linter/SupersededOperationsFile.test.ts create mode 100644 tools/test/linter/fixtures/_superseded_operations.yaml create mode 100644 tools/test/linter/fixtures/empty/_superseded_operations.yaml diff --git a/json_schemas/_superseded_operations.yaml b/json_schemas/_superseded_operations.yaml new file mode 100644 index 000000000..ed298d7f3 --- /dev/null +++ b/json_schemas/_superseded_operations.yaml @@ -0,0 +1,20 @@ +$schema: http://json-schema.org/draft-07/schema# +type: object +patternProperties: + ^\$schema$: + type: string + ^/: + type: object + properties: + superseded_by: + type: string + pattern: ^/ + operations: + type: array + items: + type: string + enum: [GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH] + required: [superseded_by, operations] + additionalProperties: false +required: [$schema] +additionalProperties: false \ No newline at end of file diff --git a/spec/_superseded_operations.yaml b/spec/_superseded_operations.yaml index 5191a5b6f..3996da336 100644 --- a/spec/_superseded_operations.yaml +++ b/spec/_superseded_operations.yaml @@ -1,3 +1,5 @@ +$schema: ../json_schemas/_superseded_operations.yaml + /_opendistro/_alerting/destinations: superseded_by: /_plugins/_alerting/destinations operations: diff --git a/tools/linter/SpecValidator.ts b/tools/linter/SpecValidator.ts index d7cd3ada5..f5c5834db 100644 --- a/tools/linter/SpecValidator.ts +++ b/tools/linter/SpecValidator.ts @@ -4,9 +4,11 @@ import RootFile from './components/RootFile' import { type ValidationError } from '../types' import PathRefsValidator from './PathRefsValidator' import SchemaRefsValidator from './SchemaRefsValidator' +import SupersededOperationsFile from "./components/SupersededOperationsFile"; export default class SpecValidator { root_file: RootFile + superseded_ops_files: SupersededOperationsFile namespaces_folder: NamespacesFolder schemas_folder: SchemasFolder path_refs_validator: PathRefsValidator @@ -14,6 +16,7 @@ export default class SpecValidator { constructor (root_folder: string) { this.root_file = new RootFile(`${root_folder}/opensearch-openapi.yaml`) + this.superseded_ops_files = new SupersededOperationsFile(`${root_folder}/_superseded_operations.yaml`) this.namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`) this.schemas_folder = new SchemasFolder(`${root_folder}/schemas`) this.path_refs_validator = new PathRefsValidator(this.root_file, this.namespaces_folder) @@ -30,7 +33,8 @@ export default class SpecValidator { return [ ...this.path_refs_validator.validate(), - ...this.schema_refs_validator.validate() + ...this.schema_refs_validator.validate(), + ...this.superseded_ops_files.validate() ] } } diff --git a/tools/linter/components/SupersededOperationsFile.ts b/tools/linter/components/SupersededOperationsFile.ts new file mode 100644 index 000000000..93c15aaaa --- /dev/null +++ b/tools/linter/components/SupersededOperationsFile.ts @@ -0,0 +1,26 @@ +import FileValidator from "./base/FileValidator"; +import ajv from "ajv"; +import fs from "fs"; +import YAML from "yaml"; +import {ValidationError} from "../../types"; + +export default class SupersededOperationsFile extends FileValidator { + JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml'; + constructor(file_path: string) { + super(file_path); + } + + validate(): ValidationError[] { + return [ + this.validate_json_schema() + ].filter(e => e) as ValidationError[] + } + + validate_json_schema(): ValidationError | undefined { + const schema = YAML.parse(fs.readFileSync(this.JSON_SCHEMA_PATH, 'utf8')); + 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)}`); + } + } +} \ No newline at end of file diff --git a/tools/merger/SupersededOpsGenerator.ts b/tools/merger/SupersededOpsGenerator.ts index b278786a8..1d37ff99f 100644 --- a/tools/merger/SupersededOpsGenerator.ts +++ b/tools/merger/SupersededOpsGenerator.ts @@ -9,6 +9,7 @@ export default class SupersededOpsGenerator { constructor (root_path: string) { const file_path = root_path + '/_superseded_operations.yaml' this.superseded_ops = YAML.parse(fs.readFileSync(file_path, 'utf8')) + delete this.superseded_ops.$schema } generate (spec: Record): void { diff --git a/tools/package-lock.json b/tools/package-lock.json index e1faac991..baf75ba26 100644 --- a/tools/package-lock.json +++ b/tools/package-lock.json @@ -12,8 +12,10 @@ "@apidevtools/swagger-parser": "^10.1.0", "@types/lodash": "^4.14.202", "@types/node": "^20.10.3", + "ajv": "^8.13.0", "lodash": "^4.17.21", "ts-node": "^10.9.1", + "typescript": "^5.4.5", "yaml": "^2.3.4" }, "devDependencies": { @@ -28,8 +30,7 @@ "eslint-plugin-promise": "^6.1.1", "globals": "^15.0.0", "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "ts-jest": "^29.1.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1803,14 +1804,14 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", diff --git a/tools/package.json b/tools/package.json index b1e67dc6c..a29beee9e 100644 --- a/tools/package.json +++ b/tools/package.json @@ -11,10 +11,12 @@ "test": "jest" }, "dependencies": { + "ajv": "^8.13.0", "@apidevtools/swagger-parser": "^10.1.0", "@types/lodash": "^4.14.202", "@types/node": "^20.10.3", "lodash": "^4.17.21", + "typescript": "^5.4.5", "ts-node": "^10.9.1", "yaml": "^2.3.4" }, @@ -30,7 +32,6 @@ "eslint-plugin-promise": "^6.1.1", "globals": "^15.0.0", "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "ts-jest": "^29.1.2" } } diff --git a/tools/test/linter/SupersededOperationsFile.test.ts b/tools/test/linter/SupersededOperationsFile.test.ts new file mode 100644 index 000000000..1ee3648be --- /dev/null +++ b/tools/test/linter/SupersededOperationsFile.test.ts @@ -0,0 +1,11 @@ +import SupersededOperationsFile from "../../linter/components/SupersededOperationsFile"; + +test('validate()', () => { + const validator = new SupersededOperationsFile('./test/linter/fixtures/_superseded_operations.yaml') + 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]" + } + ]) +}); \ No newline at end of file diff --git a/tools/test/linter/fixtures/_superseded_operations.yaml b/tools/test/linter/fixtures/_superseded_operations.yaml new file mode 100644 index 000000000..d264c0681 --- /dev/null +++ b/tools/test/linter/fixtures/_superseded_operations.yaml @@ -0,0 +1,7 @@ +$schema: ../../../../json_schemas/_superseded_operations.yaml + +/hello/world: + superseded_by: /goodbye/world + operations: + - GET + - CLEAN \ No newline at end of file diff --git a/tools/test/linter/fixtures/empty/_superseded_operations.yaml b/tools/test/linter/fixtures/empty/_superseded_operations.yaml new file mode 100644 index 000000000..ac2175ea5 --- /dev/null +++ b/tools/test/linter/fixtures/empty/_superseded_operations.yaml @@ -0,0 +1 @@ +$schema: ../../../../../json_schemas/_superseded_operations.yaml \ No newline at end of file From d8f958417ff2c50c7ab3f323ae364010c549e963 Mon Sep 17 00:00:00 2001 From: Theo Truong Date: Tue, 30 Apr 2024 17:01:00 -0600 Subject: [PATCH 2/3] # lint Signed-off-by: Theo Truong --- tools/linter/SpecValidator.ts | 4 +-- .../components/SupersededOperationsFile.ts | 28 +++++++++---------- .../linter/SupersededOperationsFile.test.ts | 6 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tools/linter/SpecValidator.ts b/tools/linter/SpecValidator.ts index f5c5834db..b227c4fec 100644 --- a/tools/linter/SpecValidator.ts +++ b/tools/linter/SpecValidator.ts @@ -4,7 +4,7 @@ import RootFile from './components/RootFile' import { type ValidationError } from '../types' import PathRefsValidator from './PathRefsValidator' import SchemaRefsValidator from './SchemaRefsValidator' -import SupersededOperationsFile from "./components/SupersededOperationsFile"; +import SupersededOperationsFile from './components/SupersededOperationsFile' export default class SpecValidator { root_file: RootFile @@ -29,7 +29,7 @@ export default class SpecValidator { ...this.namespaces_folder.validate(), ...this.schemas_folder.validate() ] - if (component_errors.length) return component_errors + if (component_errors.length > 0) return component_errors return [ ...this.path_refs_validator.validate(), diff --git a/tools/linter/components/SupersededOperationsFile.ts b/tools/linter/components/SupersededOperationsFile.ts index 93c15aaaa..69048d33d 100644 --- a/tools/linter/components/SupersededOperationsFile.ts +++ b/tools/linter/components/SupersededOperationsFile.ts @@ -1,26 +1,26 @@ -import FileValidator from "./base/FileValidator"; -import ajv from "ajv"; -import fs from "fs"; -import YAML from "yaml"; -import {ValidationError} from "../../types"; +import FileValidator from './base/FileValidator' +import ajv from 'ajv' +import fs from 'fs' +import YAML from 'yaml' +import { type ValidationError } from '../../types' export default class SupersededOperationsFile extends FileValidator { - JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml'; - constructor(file_path: string) { - super(file_path); + JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml' + constructor (file_path: string) { + super(file_path) } - validate(): ValidationError[] { + validate (): ValidationError[] { return [ this.validate_json_schema() ].filter(e => e) as ValidationError[] } - validate_json_schema(): ValidationError | undefined { - const schema = YAML.parse(fs.readFileSync(this.JSON_SCHEMA_PATH, 'utf8')); - const validator = (new ajv()).compile(schema); + validate_json_schema (): ValidationError | undefined { + const schema = YAML.parse(fs.readFileSync(this.JSON_SCHEMA_PATH, 'utf8')) + 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)}`); + return this.error(`File content does not match JSON schema found in '${this.JSON_SCHEMA_PATH}':\n ${JSON.stringify(validator.errors, null, 2)}`) } } -} \ No newline at end of file +} diff --git a/tools/test/linter/SupersededOperationsFile.test.ts b/tools/test/linter/SupersededOperationsFile.test.ts index 1ee3648be..be93ea7a0 100644 --- a/tools/test/linter/SupersededOperationsFile.test.ts +++ b/tools/test/linter/SupersededOperationsFile.test.ts @@ -1,11 +1,11 @@ -import SupersededOperationsFile from "../../linter/components/SupersededOperationsFile"; +import SupersededOperationsFile from '../../linter/components/SupersededOperationsFile' test('validate()', () => { const validator = new SupersededOperationsFile('./test/linter/fixtures/_superseded_operations.yaml') expect(validator.validate()).toEqual([ { - file: "fixtures/_superseded_operations.yaml", + 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]" } ]) -}); \ No newline at end of file +}) From 0d1e1baec265dd72e3560f3aabcf4881cfc06fba Mon Sep 17 00:00:00 2001 From: Theo Truong Date: Tue, 30 Apr 2024 17:55:03 -0600 Subject: [PATCH 3/3] # lint Signed-off-by: Theo Truong --- tools/linter/components/SupersededOperationsFile.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/linter/components/SupersededOperationsFile.ts b/tools/linter/components/SupersededOperationsFile.ts index 69048d33d..208279c14 100644 --- a/tools/linter/components/SupersededOperationsFile.ts +++ b/tools/linter/components/SupersededOperationsFile.ts @@ -6,9 +6,6 @@ import { type ValidationError } from '../../types' export default class SupersededOperationsFile extends FileValidator { JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml' - constructor (file_path: string) { - super(file_path) - } validate (): ValidationError[] { return [