From 82eeeadf6bad89567efda3c9d80619bc9c594849 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 22 Apr 2024 18:47:11 -0400 Subject: [PATCH] Fix: '@typescript-eslint/indent': 'warn'. Signed-off-by: dblock --- tools/eslint.config.mjs | 1 - tools/helpers.ts | 58 ++--- tools/linter/PathRefsValidator.ts | 120 ++++----- tools/linter/SchemaRefsValidator.ts | 164 ++++++------ tools/linter/SpecValidator.ts | 48 ++-- tools/linter/components/NamespaceFile.ts | 156 ++++++------ tools/linter/components/NamespacesFolder.ts | 38 +-- tools/linter/components/Operation.ts | 194 +++++++------- tools/linter/components/OperationGroup.ts | 114 ++++----- tools/linter/components/RootFile.ts | 52 ++-- tools/linter/components/Schema.ts | 28 +-- tools/linter/components/SchemaFile.ts | 54 ++-- tools/linter/components/SchemasFolder.ts | 12 +- tools/linter/components/base/FileValidator.ts | 60 ++--- .../linter/components/base/FolderValidator.ts | 40 +-- tools/linter/components/base/ValidatorBase.ts | 24 +- tools/linter/lint.ts | 12 +- tools/merger/OpenApiMerger.ts | 236 +++++++++--------- tools/test/linter/NamespaceFile.test.ts | 142 +++++------ tools/test/linter/NamespacesFolder.test.ts | 86 +++---- tools/test/linter/Operation.test.ts | 232 ++++++++--------- tools/test/linter/OperationGroup.test.ts | 128 +++++----- tools/test/linter/PathRefsValidator.test.ts | 50 ++-- tools/test/linter/RootFile.test.ts | 46 ++-- tools/test/linter/Schema.test.ts | 18 +- tools/test/linter/SchemaFile.test.ts | 42 ++-- tools/test/linter/SchemaRefsValidator.test.ts | 48 ++-- tools/test/linter/SpecValidator.test.ts | 32 +-- tools/test/linter/factories/namespace_file.ts | 66 ++--- tools/test/linter/factories/operation.ts | 66 ++--- .../test/linter/factories/operation_group.ts | 56 ++--- tools/test/linter/factories/schema.ts | 24 +- tools/test/linter/factories/schema_file.ts | 30 +-- tools/test/merger/OpenApiMerger.test.ts | 10 +- tools/types.ts | 34 +-- 35 files changed, 1260 insertions(+), 1261 deletions(-) diff --git a/tools/eslint.config.mjs b/tools/eslint.config.mjs index b4262e051..7f14d56b9 100644 --- a/tools/eslint.config.mjs +++ b/tools/eslint.config.mjs @@ -26,7 +26,6 @@ export default [ '@typescript-eslint/consistent-type-imports': 'warn', '@typescript-eslint/dot-notation': 'warn', '@typescript-eslint/explicit-function-return-type': 'warn', - '@typescript-eslint/indent': 'warn', '@typescript-eslint/keyword-spacing': 'warn', '@typescript-eslint/lines-between-class-members': 'warn', '@typescript-eslint/member-delimiter-style': 'warn', diff --git a/tools/helpers.ts b/tools/helpers.ts index 336ffb68c..e88e79da6 100644 --- a/tools/helpers.ts +++ b/tools/helpers.ts @@ -3,45 +3,45 @@ import YAML from "yaml"; import _ from "lodash"; export function resolve(ref: string, root: Record) { - const paths = ref.replace('#/', '').split('/'); - for(const p of paths) { - root = root[p]; - if(root === undefined) break; - } - return root; + const paths = ref.replace('#/', '').split('/'); + for(const p of paths) { + root = root[p]; + if(root === undefined) break; + } + return root; } export function sortByKey(obj: Record, priorities: string[] = []) { - const orders = _.fromPairs(priorities.map((k, i) => [k, i+1])); - const sorted = _.entries(obj).sort((a,b) => { - const order_a = orders[a[0]]; - const order_b = orders[b[0]]; - if(order_a && order_b) return order_a - order_b; - if(order_a) return 1; - if(order_b) return -1; - return a[0].localeCompare(b[0]); - }); - sorted.forEach(([k, v]) => { - delete obj[k]; - obj[k] = v; - }); + const orders = _.fromPairs(priorities.map((k, i) => [k, i+1])); + const sorted = _.entries(obj).sort((a,b) => { + const order_a = orders[a[0]]; + const order_b = orders[b[0]]; + if(order_a && order_b) return order_a - order_b; + if(order_a) return 1; + if(order_b) return -1; + return a[0].localeCompare(b[0]); + }); + sorted.forEach(([k, v]) => { + delete obj[k]; + obj[k] = v; + }); } export function write2file(file_path: string, content: Record): void { - fs.writeFileSync(file_path, quoteRefs(YAML.stringify(removeAnchors(content), {lineWidth: 0, singleQuote: true}))); + fs.writeFileSync(file_path, quoteRefs(YAML.stringify(removeAnchors(content), {lineWidth: 0, singleQuote: true}))); } function quoteRefs(str: string): string { - return str.split('\n').map((line) => { - if(line.includes('$ref')) { - const [key, value] = line.split(': '); - if(!value.startsWith("'")) line = `${key}: '${value}'`; - } - return line - }).join('\n'); + return str.split('\n').map((line) => { + if(line.includes('$ref')) { + const [key, value] = line.split(': '); + if(!value.startsWith("'")) line = `${key}: '${value}'`; + } + return line + }).join('\n'); } function removeAnchors(content: Record): Record { - const replacer = (key: string, value: any) => key === '$anchor' ? undefined : value; - return JSON.parse(JSON.stringify(content, replacer)); + const replacer = (key: string, value: any) => key === '$anchor' ? undefined : value; + return JSON.parse(JSON.stringify(content, replacer)); } \ No newline at end of file diff --git a/tools/linter/PathRefsValidator.ts b/tools/linter/PathRefsValidator.ts index 7875bf208..bca6360a7 100644 --- a/tools/linter/PathRefsValidator.ts +++ b/tools/linter/PathRefsValidator.ts @@ -3,74 +3,74 @@ import RootFile from "./components/RootFile"; import NamespacesFolder from "./components/NamespacesFolder"; export default class PathRefsValidator { - root_file: RootFile; - namespaces_folder: NamespacesFolder; + root_file: RootFile; + namespaces_folder: NamespacesFolder; - referenced_paths: Record> = {}; // file -> paths - available_paths: Record> = {}; // file -> paths + referenced_paths: Record> = {}; // file -> paths + available_paths: Record> = {}; // file -> paths - constructor(root_file: RootFile, namespaces_folder: NamespacesFolder) { - this.root_file = root_file; - this.namespaces_folder = namespaces_folder; - this.#build_referenced_paths(); - this.#build_available_paths(); - } + constructor(root_file: RootFile, namespaces_folder: NamespacesFolder) { + this.root_file = root_file; + this.namespaces_folder = namespaces_folder; + this.#build_referenced_paths(); + this.#build_available_paths(); + } - #build_referenced_paths() { - for (const [path, spec] of Object.entries(this.root_file.spec().paths)) { - const ref = spec!.$ref!; - const file = ref.split('#')[0]; - if(!this.referenced_paths[file]) this.referenced_paths[file] = new Set(); - this.referenced_paths[file].add(path); - } + #build_referenced_paths() { + for (const [path, spec] of Object.entries(this.root_file.spec().paths)) { + const ref = spec!.$ref!; + const file = ref.split('#')[0]; + if(!this.referenced_paths[file]) this.referenced_paths[file] = new Set(); + this.referenced_paths[file].add(path); } + } - #build_available_paths() { - for (const file of this.namespaces_folder.files) { - this.available_paths[file.file] = new Set(Object.keys(file.spec().paths || {})); - } + #build_available_paths() { + for (const file of this.namespaces_folder.files) { + this.available_paths[file.file] = new Set(Object.keys(file.spec().paths || {})); } + } - validate(): ValidationError[] { - return [ - ...this.validate_unresolved_refs(), - ...this.validate_unreferenced_paths(), - ]; - } + validate(): ValidationError[] { + return [ + ...this.validate_unresolved_refs(), + ...this.validate_unreferenced_paths(), + ]; + } - validate_unresolved_refs(): ValidationError[] { - return Object.entries(this.referenced_paths).flatMap(([ref_file, ref_paths]) => { - const available = this.available_paths[ref_file]; - if(!available) return { - file: this.root_file.file, - location: `Paths: ${[...ref_paths].join(' , ')}`, - message: `Unresolved path reference: Namespace file ${ref_file} does not exist.`, - }; + validate_unresolved_refs(): ValidationError[] { + return Object.entries(this.referenced_paths).flatMap(([ref_file, ref_paths]) => { + const available = this.available_paths[ref_file]; + if(!available) return { + file: this.root_file.file, + location: `Paths: ${[...ref_paths].join(' , ')}`, + message: `Unresolved path reference: Namespace file ${ref_file} does not exist.`, + }; - return Array.from(ref_paths).map((path) => { - if(!available.has(path)) return { - file: this.root_file.file, - location: `Path: ${path}`, - message: `Unresolved path reference: Path ${path} does not exist in namespace file ${ref_file}.`, - }; - }).filter((e) => e) as ValidationError[]; - }); - } + return Array.from(ref_paths).map((path) => { + if(!available.has(path)) return { + file: this.root_file.file, + location: `Path: ${path}`, + message: `Unresolved path reference: Path ${path} does not exist in namespace file ${ref_file}.`, + }; + }).filter((e) => e) as ValidationError[]; + }); + } - validate_unreferenced_paths(): ValidationError[] { - return Object.entries(this.available_paths).flatMap(([ns_file, ns_paths]) => { - const referenced = this.referenced_paths[ns_file]; - if(!referenced) return { - file: ns_file, - message: `Unreferenced paths: No paths are referenced in the root file.`, - }; - return Array.from(ns_paths).map((path) => { - if(!referenced || !referenced.has(path)) return { - file: ns_file, - location: `Path: ${path}`, - message: `Unreferenced path: Path ${path} is not referenced in the root file.`, - }; - }).filter((e) => e) as ValidationError[]; - }); - } + validate_unreferenced_paths(): ValidationError[] { + return Object.entries(this.available_paths).flatMap(([ns_file, ns_paths]) => { + const referenced = this.referenced_paths[ns_file]; + if(!referenced) return { + file: ns_file, + message: `Unreferenced paths: No paths are referenced in the root file.`, + }; + return Array.from(ns_paths).map((path) => { + if(!referenced || !referenced.has(path)) return { + file: ns_file, + location: `Path: ${path}`, + message: `Unreferenced path: Path ${path} is not referenced in the root file.`, + }; + }).filter((e) => e) as ValidationError[]; + }); + } } \ No newline at end of file diff --git a/tools/linter/SchemaRefsValidator.ts b/tools/linter/SchemaRefsValidator.ts index 20da2d269..c4b76c2b6 100644 --- a/tools/linter/SchemaRefsValidator.ts +++ b/tools/linter/SchemaRefsValidator.ts @@ -3,98 +3,98 @@ import SchemasFolder from "./components/SchemasFolder"; import {ValidationError} from "../types"; export default class SchemaRefsValidator { - namespaces_folder: NamespacesFolder; - schemas_folder: SchemasFolder; + namespaces_folder: NamespacesFolder; + schemas_folder: SchemasFolder; - referenced_schemas: Record> = {}; // file -> schemas - available_schemas: Record> = {}; // file -> schemas + referenced_schemas: Record> = {}; // file -> schemas + available_schemas: Record> = {}; // file -> schemas - constructor(namespaces_folder: NamespacesFolder, schemas_folder: SchemasFolder) { - this.namespaces_folder = namespaces_folder; - this.schemas_folder = schemas_folder; - this.#find_refs_in_namespaces_folder(); - this.#find_refs_in_schemas_folder(); - this.#build_available_schemas(); - } - - #find_refs_in_namespaces_folder() { - const search = (obj: Record) => { - const ref = obj.$ref; - if(ref) { - const file = ref.split('#')[0].replace("../", ""); - const name = ref.split('/').pop(); - if(!this.referenced_schemas[file]) this.referenced_schemas[file] = new Set(); - this.referenced_schemas[file].add(name); - } - for (const key in obj) - if(typeof obj[key] === 'object') search(obj[key]); - }; + constructor(namespaces_folder: NamespacesFolder, schemas_folder: SchemasFolder) { + this.namespaces_folder = namespaces_folder; + this.schemas_folder = schemas_folder; + this.#find_refs_in_namespaces_folder(); + this.#find_refs_in_schemas_folder(); + this.#build_available_schemas(); + } - this.namespaces_folder.files.forEach((file) => { search(file.spec().components || {}) }); - } + #find_refs_in_namespaces_folder() { + const search = (obj: Record) => { + const ref = obj.$ref; + if(ref) { + const file = ref.split('#')[0].replace("../", ""); + const name = ref.split('/').pop(); + if(!this.referenced_schemas[file]) this.referenced_schemas[file] = new Set(); + this.referenced_schemas[file].add(name); + } + for (const key in obj) + if(typeof obj[key] === 'object') search(obj[key]); + }; - #find_refs_in_schemas_folder() { - const search = (obj: Record, ref_file: string) => { - const ref = obj.$ref; - if(ref) { - const file = ref.startsWith('#') ? ref_file : `schemas/${ref.split('#')[0]}`; - const name = ref.split('/').pop(); - if(!this.referenced_schemas[file]) this.referenced_schemas[file] = new Set(); - this.referenced_schemas[file].add(name); - } - for (const key in obj) - if(typeof obj[key] === 'object') search(obj[key], ref_file); - } + this.namespaces_folder.files.forEach((file) => { search(file.spec().components || {}) }); + } - this.schemas_folder.files.forEach((file) => { search(file.spec().components?.schemas || {}, file.file) }); + #find_refs_in_schemas_folder() { + const search = (obj: Record, ref_file: string) => { + const ref = obj.$ref; + if(ref) { + const file = ref.startsWith('#') ? ref_file : `schemas/${ref.split('#')[0]}`; + const name = ref.split('/').pop(); + if(!this.referenced_schemas[file]) this.referenced_schemas[file] = new Set(); + this.referenced_schemas[file].add(name); + } + for (const key in obj) + if(typeof obj[key] === 'object') search(obj[key], ref_file); } - #build_available_schemas() { - this.schemas_folder.files.forEach((file) => { - this.available_schemas[file.file] = new Set(Object.keys(file.spec().components?.schemas || {})); - }); - } + this.schemas_folder.files.forEach((file) => { search(file.spec().components?.schemas || {}, file.file) }); + } - validate(): ValidationError[] { - return [ - ...this.validate_unresolved_refs(), - ...this.validate_unreferenced_schemas(), - ]; - } + #build_available_schemas() { + this.schemas_folder.files.forEach((file) => { + this.available_schemas[file.file] = new Set(Object.keys(file.spec().components?.schemas || {})); + }); + } - validate_unresolved_refs(): ValidationError[] { - return Object.entries(this.referenced_schemas).flatMap(([ref_file, ref_schemas]) => { - const available = this.available_schemas[ref_file]; - if(!available) return { - file: this.namespaces_folder.file, - message: `Unresolved schema reference: Schema file ${ref_file} is referenced but does not exist.`, - }; + validate(): ValidationError[] { + return [ + ...this.validate_unresolved_refs(), + ...this.validate_unreferenced_schemas(), + ]; + } - return Array.from(ref_schemas).map((schema) => { - if(!available.has(schema)) return { - file: ref_file, - location: `#/components/schemas/${schema}`, - message: `Unresolved schema reference: Schema ${schema} is referenced but does not exist.`, - }; - }).filter((e) => e) as ValidationError[]; - }); - } + validate_unresolved_refs(): ValidationError[] { + return Object.entries(this.referenced_schemas).flatMap(([ref_file, ref_schemas]) => { + const available = this.available_schemas[ref_file]; + if(!available) return { + file: this.namespaces_folder.file, + message: `Unresolved schema reference: Schema file ${ref_file} is referenced but does not exist.`, + }; - validate_unreferenced_schemas(): ValidationError[] { - return Object.entries(this.available_schemas).flatMap(([file, schemas]) => { - const referenced = this.referenced_schemas[file]; - if(!referenced) return { - file: file, - message: `Unreferenced schema: Schema file ${file} is not referenced anywhere.`, - }; + return Array.from(ref_schemas).map((schema) => { + if(!available.has(schema)) return { + file: ref_file, + location: `#/components/schemas/${schema}`, + message: `Unresolved schema reference: Schema ${schema} is referenced but does not exist.`, + }; + }).filter((e) => e) as ValidationError[]; + }); + } - return Array.from(schemas).map((schema) => { - if(!referenced.has(schema)) return { - file: file, - location: `#/components/schemas/${schema}`, - message: `Unreferenced schema: Schema ${schema} is not referenced anywhere.`, - }; - }).filter((e) => e) as ValidationError[]; - }); - } + validate_unreferenced_schemas(): ValidationError[] { + return Object.entries(this.available_schemas).flatMap(([file, schemas]) => { + const referenced = this.referenced_schemas[file]; + if(!referenced) return { + file: file, + message: `Unreferenced schema: Schema file ${file} is not referenced anywhere.`, + }; + + return Array.from(schemas).map((schema) => { + if(!referenced.has(schema)) return { + file: file, + location: `#/components/schemas/${schema}`, + message: `Unreferenced schema: Schema ${schema} is not referenced anywhere.`, + }; + }).filter((e) => e) as ValidationError[]; + }); + } } \ No newline at end of file diff --git a/tools/linter/SpecValidator.ts b/tools/linter/SpecValidator.ts index c01353c31..7d2426790 100644 --- a/tools/linter/SpecValidator.ts +++ b/tools/linter/SpecValidator.ts @@ -6,31 +6,31 @@ import PathRefsValidator from "./PathRefsValidator"; import SchemaRefsValidator from "./SchemaRefsValidator"; export default class SpecValidator { - root_file: RootFile; - namespaces_folder: NamespacesFolder; - schemas_folder: SchemasFolder; - path_refs_validator: PathRefsValidator; - schema_refs_validator: SchemaRefsValidator; + root_file: RootFile; + namespaces_folder: NamespacesFolder; + schemas_folder: SchemasFolder; + path_refs_validator: PathRefsValidator; + schema_refs_validator: SchemaRefsValidator; - constructor(root_folder: string) { - this.root_file = new RootFile(`${root_folder}/opensearch-openapi.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); - this.schema_refs_validator = new SchemaRefsValidator(this.namespaces_folder, this.schemas_folder); - } + constructor(root_folder: string) { + this.root_file = new RootFile(`${root_folder}/opensearch-openapi.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); + this.schema_refs_validator = new SchemaRefsValidator(this.namespaces_folder, this.schemas_folder); + } - validate(): ValidationError[] { - const component_errors = [ - ...this.root_file.validate(), - ...this.namespaces_folder.validate(), - ...this.schemas_folder.validate(), - ]; - if(component_errors.length) return component_errors; + validate(): ValidationError[] { + const component_errors = [ + ...this.root_file.validate(), + ...this.namespaces_folder.validate(), + ...this.schemas_folder.validate(), + ]; + if(component_errors.length) return component_errors; - return [ - ...this.path_refs_validator.validate(), - ...this.schema_refs_validator.validate() - ] - } + return [ + ...this.path_refs_validator.validate(), + ...this.schema_refs_validator.validate() + ] + } } diff --git a/tools/linter/components/NamespaceFile.ts b/tools/linter/components/NamespaceFile.ts index 7a3b95476..8498b8578 100644 --- a/tools/linter/components/NamespaceFile.ts +++ b/tools/linter/components/NamespaceFile.ts @@ -10,94 +10,94 @@ const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch' const NAME_REGEX = /^[a-z]+[a-z_]*[a-z]+$/; export default class NamespaceFile extends FileValidator { - namespace: string; - _operation_groups: OperationGroup[] | undefined; - _refs: Set | undefined; + namespace: string; + _operation_groups: OperationGroup[] | undefined; + _refs: Set | undefined; - constructor(file_path: string) { - super(file_path); - this.namespace = file_path.split('/').slice(-1)[0].replace('.yaml', ''); - } + constructor(file_path: string) { + super(file_path); + this.namespace = file_path.split('/').slice(-1)[0].replace('.yaml', ''); + } - validate_file(): ValidationError[] { - const name_error = this.validate_name(); - if(name_error) return [name_error]; - const group_errors = this.operation_groups().flatMap((group) => group.validate()); - if(group_errors.length > 0) return group_errors; + validate_file(): ValidationError[] { + const name_error = this.validate_name(); + if(name_error) return [name_error]; + const group_errors = this.operation_groups().flatMap((group) => group.validate()); + if(group_errors.length > 0) return group_errors; - return [ - this.validate_schemas(), - ...this.validate_unresolved_refs(), - ...this.validate_unused_refs(), - ...this.validate_parameter_refs(), - ].filter((e) => e) as ValidationError[] - } + return [ + this.validate_schemas(), + ...this.validate_unresolved_refs(), + ...this.validate_unused_refs(), + ...this.validate_parameter_refs(), + ].filter((e) => e) as ValidationError[] + } - operation_groups(): OperationGroup[] { - if(this._operation_groups) return this._operation_groups; - const ops: Operation[] = _.entries(this.spec().paths).flatMap(([path, ops]) => { - return _.entries(_.pick(ops, HTTP_METHODS)).map(([verb, op]) => { - return new Operation(this.file, path, verb, op as OperationSpec); - }); - }); + operation_groups(): OperationGroup[] { + if(this._operation_groups) return this._operation_groups; + const ops: Operation[] = _.entries(this.spec().paths).flatMap(([path, ops]) => { + return _.entries(_.pick(ops, HTTP_METHODS)).map(([verb, op]) => { + return new Operation(this.file, path, verb, op as OperationSpec); + }); + }); - return this._operation_groups = _.entries(_.groupBy(ops, (op) => op.group)).map(([group, ops]) => { - return new OperationGroup(this.file, group, ops); - }); - } + return this._operation_groups = _.entries(_.groupBy(ops, (op) => op.group)).map(([group, ops]) => { + return new OperationGroup(this.file, group, ops); + }); + } - refs(): Set { - if(this._refs) return this._refs; - this._refs = new Set(); - const find_refs = (obj: Record) => { - if(obj.$ref) this._refs!.add(obj.$ref); - _.values(obj).forEach((value) => { if(typeof value === 'object') find_refs(value); }); - } - find_refs(this.spec().paths || {}); - return this._refs; + refs(): Set { + if(this._refs) return this._refs; + this._refs = new Set(); + const find_refs = (obj: Record) => { + if(obj.$ref) this._refs!.add(obj.$ref); + _.values(obj).forEach((value) => { if(typeof value === 'object') find_refs(value); }); } + find_refs(this.spec().paths || {}); + return this._refs; + } - validate_name(name = this.namespace): ValidationError | void { - if(name === '_core') return; - if(!name.match(NAME_REGEX)) - return this.error(`Invalid namespace name '${name}'. Must match regex: /${NAME_REGEX.source}/.`, 'File Name'); - return; - } + validate_name(name = this.namespace): ValidationError | void { + if(name === '_core') return; + if(!name.match(NAME_REGEX)) + return this.error(`Invalid namespace name '${name}'. Must match regex: /${NAME_REGEX.source}/.`, 'File Name'); + return; + } - validate_schemas(): ValidationError | void { - if(this.spec().components?.schemas) - return this.error(`components/schemas is not allowed in namespace files`, '#/components/schemas'); - } + validate_schemas(): ValidationError | void { + if(this.spec().components?.schemas) + return this.error(`components/schemas is not allowed in namespace files`, '#/components/schemas'); + } - validate_unresolved_refs(): ValidationError[] { - return Array.from(this.refs()).map((ref) => { - if(resolve(ref, this.spec()) === undefined) return this.error(`Unresolved reference: ${ref}`, ref); - }).filter((e) => e) as ValidationError[]; - } + validate_unresolved_refs(): ValidationError[] { + return Array.from(this.refs()).map((ref) => { + if(resolve(ref, this.spec()) === undefined) return this.error(`Unresolved reference: ${ref}`, ref); + }).filter((e) => e) as ValidationError[]; + } - validate_unused_refs(): ValidationError[] { - return _.entries(this.spec().components || {}).flatMap(([type, collection]) => { - return _.keys(collection).map((name) => { - if (!this.refs().has(`#/components/${type}/${name}`)) - return this.error(`Unused ${type} component: ${name}`, `#/components/${type}/${name}`); - }) - }).filter((e) => e) as ValidationError[]; - } + validate_unused_refs(): ValidationError[] { + return _.entries(this.spec().components || {}).flatMap(([type, collection]) => { + return _.keys(collection).map((name) => { + if (!this.refs().has(`#/components/${type}/${name}`)) + return this.error(`Unused ${type} component: ${name}`, `#/components/${type}/${name}`); + }) + }).filter((e) => e) as ValidationError[]; + } - validate_parameter_refs(): ValidationError[] { - const parameters = this.spec().components?.parameters as Record - if(!parameters) return []; - return _.entries(parameters).map(([name, p]) => { - const group = name.split('::')[0]; - const expected = `${group}::${p.in}.${p.name}`; - if(name !== expected) - return this.error( - `Parameter component '${name}' must be named '${expected}' since it is a ${p.in} parameter named '${p.name}'.`, - `#/components/parameters/#${name}`); - if(!p.name.match(/^[a-z0-9._]+$/)) - return this.error( - `Invalid parameter name '${p.name}'. A parameter's name can only contain lower-cased alphanumerics, underscores, and periods.`, - `#/components/parameters/#${name}`); - }).filter((e) => e) as ValidationError[]; - } + validate_parameter_refs(): ValidationError[] { + const parameters = this.spec().components?.parameters as Record + if(!parameters) return []; + return _.entries(parameters).map(([name, p]) => { + const group = name.split('::')[0]; + const expected = `${group}::${p.in}.${p.name}`; + if(name !== expected) + return this.error( + `Parameter component '${name}' must be named '${expected}' since it is a ${p.in} parameter named '${p.name}'.`, + `#/components/parameters/#${name}`); + if(!p.name.match(/^[a-z0-9._]+$/)) + return this.error( + `Invalid parameter name '${p.name}'. A parameter's name can only contain lower-cased alphanumerics, underscores, and periods.`, + `#/components/parameters/#${name}`); + }).filter((e) => e) as ValidationError[]; + } } \ No newline at end of file diff --git a/tools/linter/components/NamespacesFolder.ts b/tools/linter/components/NamespacesFolder.ts index a8b18c400..98bf2a63a 100644 --- a/tools/linter/components/NamespacesFolder.ts +++ b/tools/linter/components/NamespacesFolder.ts @@ -3,26 +3,26 @@ import {ValidationError} from "../../types"; import FolderValidator from "./base/FolderValidator"; export default class NamespacesFolder extends FolderValidator { - constructor(folder_path: string) { - super(folder_path, NamespaceFile); - } + constructor(folder_path: string) { + super(folder_path, NamespaceFile); + } - validate_folder(): ValidationError[] { - return this.validate_duplicate_paths(); - } + validate_folder(): ValidationError[] { + return this.validate_duplicate_paths(); + } - validate_duplicate_paths(): ValidationError[] { - const paths: { [path: string]: string[] } = {}; - for (const file of this.files) { - if(!file._spec?.paths) continue; - Object.keys(file.spec().paths).sort().forEach((path) => { - if(paths[path]) paths[path].push(file.namespace); - else paths[path] = [file.namespace]; - }); - } - return Object.entries(paths).map(([path, namespaces]) => { - if(namespaces.length > 1) - return this.error(`Duplicate path '${path}' found in namespaces: ${namespaces.sort().join(', ')}.`); - }).filter((e) => e) as ValidationError[]; + validate_duplicate_paths(): ValidationError[] { + const paths: { [path: string]: string[] } = {}; + for (const file of this.files) { + if(!file._spec?.paths) continue; + Object.keys(file.spec().paths).sort().forEach((path) => { + if(paths[path]) paths[path].push(file.namespace); + else paths[path] = [file.namespace]; + }); } + return Object.entries(paths).map(([path, namespaces]) => { + if(namespaces.length > 1) + return this.error(`Duplicate path '${path}' found in namespaces: ${namespaces.sort().join(', ')}.`); + }).filter((e) => e) as ValidationError[]; + } } \ No newline at end of file diff --git a/tools/linter/components/Operation.ts b/tools/linter/components/Operation.ts index 1be04bf12..13e30e580 100644 --- a/tools/linter/components/Operation.ts +++ b/tools/linter/components/Operation.ts @@ -5,116 +5,116 @@ import {OpenAPIV3} from "openapi-types"; const GROUP_REGEX = /^([a-z]+[a-z_]*[a-z]+\.)?([a-z]+[a-z_]*[a-z]+)$/; export default class Operation extends ValidatorBase { - path: string; - verb: string; - group: string; - group_regex: string; - namespace: string | undefined; - spec: OperationSpec; + path: string; + verb: string; + group: string; + group_regex: string; + namespace: string | undefined; + spec: OperationSpec; - constructor(file: string, path: string, verb: string, spec: OperationSpec) { - super(file, `Operation: ${verb.toUpperCase()} ${path}`); - this.path = path; - this.verb = verb; - this.spec = spec; - this.group = spec['x-operation-group']; - this.group_regex = this.group?.replace('.','\\.'); - if(this.group?.indexOf('.') > 0) this.namespace = this.group.split('.')[0]; - } + constructor(file: string, path: string, verb: string, spec: OperationSpec) { + super(file, `Operation: ${verb.toUpperCase()} ${path}`); + this.path = path; + this.verb = verb; + this.spec = spec; + this.group = spec['x-operation-group']; + this.group_regex = this.group?.replace('.','\\.'); + if(this.group?.indexOf('.') > 0) this.namespace = this.group.split('.')[0]; + } - validate(): ValidationError[] { - const group_error = this.validate_group(); - if(group_error) return [group_error]; - const namespace_error = this.validate_namespace(); - if(namespace_error) return [namespace_error]; - return [ - this.validate_operationId(), - this.validate_description(), - this.validate_requestBody(), - this.validate_parameters(), - this.validate_path_parameters(), - ...this.validate_responses(), - ].filter((e) => e) as ValidationError[]; - } + validate(): ValidationError[] { + const group_error = this.validate_group(); + if(group_error) return [group_error]; + const namespace_error = this.validate_namespace(); + if(namespace_error) return [namespace_error]; + return [ + this.validate_operationId(), + this.validate_description(), + this.validate_requestBody(), + this.validate_parameters(), + this.validate_path_parameters(), + ...this.validate_responses(), + ].filter((e) => e) as ValidationError[]; + } - validate_group(): ValidationError | void{ - if(!this.group || this.group === '') - return this.error(`Missing x-operation-group property`); - if(!this.group.match(GROUP_REGEX)) - return this.error(`Invalid x-operation-group '${this.group}'. Must match regex: /${GROUP_REGEX.source}/.`); - } + validate_group(): ValidationError | void{ + if(!this.group || this.group === '') + return this.error(`Missing x-operation-group property`); + if(!this.group.match(GROUP_REGEX)) + return this.error(`Invalid x-operation-group '${this.group}'. Must match regex: /${GROUP_REGEX.source}/.`); + } - validate_namespace(): ValidationError | void { - const expected_namespace = this.file.match(/namespaces\/(.*)\.yaml/)![1]; + validate_namespace(): ValidationError | void { + const expected_namespace = this.file.match(/namespaces\/(.*)\.yaml/)![1]; - if(expected_namespace === '_core' && this.namespace === undefined) return; - if(expected_namespace === '_core' && this.namespace === '_core') - return this.error(`Invalid x-operation-group '${this.group}'. '_core' namespace must be omitted in x-operation-group.`); + if(expected_namespace === '_core' && this.namespace === undefined) return; + if(expected_namespace === '_core' && this.namespace === '_core') + return this.error(`Invalid x-operation-group '${this.group}'. '_core' namespace must be omitted in x-operation-group.`); - if(this.namespace === expected_namespace ) return; - return this.error(`Invalid x-operation-group '${this.group}'. '${this.namespace}' namespace detected. ` + - `Only '${expected_namespace}' namespace is allowed in this file.`); - } + if(this.namespace === expected_namespace ) return; + return this.error(`Invalid x-operation-group '${this.group}'. '${this.namespace}' namespace detected. ` + + `Only '${expected_namespace}' namespace is allowed in this file.`); + } - validate_description(): ValidationError | void { - const description = this.spec.description; - if(!description || description === '') - return this.error(`Missing description property.`); - if(!description.endsWith('.')) - return this.error(`Description must end with a period.`); - } + validate_description(): ValidationError | void { + const description = this.spec.description; + if(!description || description === '') + return this.error(`Missing description property.`); + if(!description.endsWith('.')) + return this.error(`Description must end with a period.`); + } - validate_operationId(): ValidationError | void { - const id = this.spec.operationId; - if(!id || id === '') - return this.error(`Missing operationId property.`); - if(!id.match(new RegExp(`^${this.group_regex}\\.[0-9]+$`))) - return this.error(`Invalid operationId '${id}'. Must be in {x-operation-group}.{number} format.`); - } + validate_operationId(): ValidationError | void { + const id = this.spec.operationId; + if(!id || id === '') + return this.error(`Missing operationId property.`); + if(!id.match(new RegExp(`^${this.group_regex}\\.[0-9]+$`))) + return this.error(`Invalid operationId '${id}'. Must be in {x-operation-group}.{number} format.`); + } - validate_requestBody(): ValidationError | void { - const body = this.spec.requestBody; - if(!body) return; - const expected = `#/components/requestBodies/${this.group}`; - if(body.$ref !== expected) - return this.error(`The requestBody must be a reference object to '${expected}'.`); - } + validate_requestBody(): ValidationError | void { + const body = this.spec.requestBody; + if(!body) return; + const expected = `#/components/requestBodies/${this.group}`; + if(body.$ref !== expected) + return this.error(`The requestBody must be a reference object to '${expected}'.`); + } - validate_responses(): ValidationError[] { - const responses = this.spec.responses; - if(!responses || _.keys(responses).length == 0) return [this.error(`Missing responses property.`)]; - return _.entries(responses).map(([code, response]) => { - const expected = `#/components/responses/${this.group}@${code}`; - if(response.$ref && response.$ref !== expected) - return this.error(`The ${code} response must be a reference object to '${expected}'.`); - return; - }).filter((error) => error) as ValidationError[]; - } + validate_responses(): ValidationError[] { + const responses = this.spec.responses; + if(!responses || _.keys(responses).length == 0) return [this.error(`Missing responses property.`)]; + return _.entries(responses).map(([code, response]) => { + const expected = `#/components/responses/${this.group}@${code}`; + if(response.$ref && response.$ref !== expected) + return this.error(`The ${code} response must be a reference object to '${expected}'.`); + return; + }).filter((error) => error) as ValidationError[]; + } - validate_parameters(): ValidationError | void{ - const parameters = this.spec.parameters; - if(!parameters) return; - const regex = new RegExp(`^#/components/parameters/${this.group_regex}::((path)|(query))\\.[a-z0-9_.]+$`); - for(const parameter of parameters){ - if(!parameter.$ref.match(regex)) - return this.error(`Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'.`); - } + validate_parameters(): ValidationError | void{ + const parameters = this.spec.parameters; + if(!parameters) return; + const regex = new RegExp(`^#/components/parameters/${this.group_regex}::((path)|(query))\\.[a-z0-9_.]+$`); + for(const parameter of parameters){ + if(!parameter.$ref.match(regex)) + return this.error(`Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'.`); } + } - validate_path_parameters(): ValidationError | void { - const path_params = this.path_params(); - const expected = this.path.match(/{[a-z0-9_]+}/g)?.map(p => p.slice(1, -1)) || []; - if(path_params.sort().join(', ') !== expected.sort().join(', ')) - return this.error(`Path parameters must match the parameters in the path: {${expected.join('}, {')}}.`); - } + validate_path_parameters(): ValidationError | void { + const path_params = this.path_params(); + const expected = this.path.match(/{[a-z0-9_]+}/g)?.map(p => p.slice(1, -1)) || []; + if(path_params.sort().join(', ') !== expected.sort().join(', ')) + return this.error(`Path parameters must match the parameters in the path: {${expected.join('}, {')}}.`); + } - path_params(): string[] { - return this.spec.parameters?.map(p => p.$ref?.match(/::path\.(.+)/)?.[1]) - .filter((p): p is string => p !== undefined) || [] - } + path_params(): string[] { + return this.spec.parameters?.map(p => p.$ref?.match(/::path\.(.+)/)?.[1]) + .filter((p): p is string => p !== undefined) || [] + } - query_params(): string[] { - return this.spec.parameters?.map(p => p.$ref?.match(/::query\.(.+)/)?.[1]) - .filter((p): p is string => p !== undefined) || [] - } + query_params(): string[] { + return this.spec.parameters?.map(p => p.$ref?.match(/::query\.(.+)/)?.[1]) + .filter((p): p is string => p !== undefined) || [] + } } \ No newline at end of file diff --git a/tools/linter/components/OperationGroup.ts b/tools/linter/components/OperationGroup.ts index af1f5679f..eae675c33 100644 --- a/tools/linter/components/OperationGroup.ts +++ b/tools/linter/components/OperationGroup.ts @@ -3,61 +3,61 @@ import {ValidationError} from "../../types"; import ValidatorBase from "./base/ValidatorBase"; export default class OperationGroup extends ValidatorBase { - readonly OP_PRIORITY = ['operationId', 'x-operation-group', 'x-ignorable', 'deprecated', - 'x-deprecation-message', 'x-version-added', 'x-version-deprecated', 'x-version-removed', - 'description', 'externalDocs', 'parameters', 'requestBody', 'responses']; - name: string; - operations: Operation[]; - - constructor(file: string, name: string, operations: Operation[]) { - super(file, `Operation Group: ${name}`); - this.name = name; - this.operations = operations; - } - - validate(): ValidationError[] { - const location = `Operation Group: ${this.name}`; - const ops_errors = this.operations.flatMap((op) => op.validate()); - if(ops_errors.length > 0) return ops_errors; - if(this.operations.length == 1) return []; - return [ - this.validate_description(), - this.validate_externalDocs(), - this.validate_requestBody(), - this.validate_responses(), - this.validate_query_parameters(), - ].filter((e) => e) as ValidationError[]; - } - - validate_description(): ValidationError | void { - const uniq_descriptions = new Set(this.operations.map((op) => op.spec.description)); - if(uniq_descriptions.size > 1) - return this.error(`${this.operations.length} '${this.name}' operations must have identical description property.`); - } - - validate_externalDocs(): ValidationError | void { - const uniq_externalDocs = new Set(this.operations.map((op) => op.spec.externalDocs?.url)); - if(uniq_externalDocs.size > 1) - return this.error(`${this.operations.length} '${this.name}' operations must have identical externalDocs property.`); - } - - validate_requestBody(): ValidationError | void { - const uniq_requestBodies = new Set(this.operations.map((op) => op.spec.requestBody?.$ref)); - if(uniq_requestBodies.size > 1) - return this.error(`${this.operations.length} '${this.name}' operations must have identical requestBody property.`); - } - - validate_responses(): ValidationError | void { - const key_signatures = this.operations.map((op) => Object.keys(op.spec.responses).sort().join('#$@')); - const uniq_signatures = new Set(key_signatures); - if(uniq_signatures.size > 1) - return this.error(`${this.operations.length} '${this.name}' operations must have an identical set of responses.`); - } - - validate_query_parameters(): ValidationError | void { - const query_signatures = this.operations.map((op) => op.query_params().sort().join('#$@')); - const uniq_signatures = new Set(query_signatures); - if(uniq_signatures.size > 1) - return this.error(`${this.operations.length} '${this.name}' operations must have an identical set of query parameters.`); - } + readonly OP_PRIORITY = ['operationId', 'x-operation-group', 'x-ignorable', 'deprecated', + 'x-deprecation-message', 'x-version-added', 'x-version-deprecated', 'x-version-removed', + 'description', 'externalDocs', 'parameters', 'requestBody', 'responses']; + name: string; + operations: Operation[]; + + constructor(file: string, name: string, operations: Operation[]) { + super(file, `Operation Group: ${name}`); + this.name = name; + this.operations = operations; + } + + validate(): ValidationError[] { + const location = `Operation Group: ${this.name}`; + const ops_errors = this.operations.flatMap((op) => op.validate()); + if(ops_errors.length > 0) return ops_errors; + if(this.operations.length == 1) return []; + return [ + this.validate_description(), + this.validate_externalDocs(), + this.validate_requestBody(), + this.validate_responses(), + this.validate_query_parameters(), + ].filter((e) => e) as ValidationError[]; + } + + validate_description(): ValidationError | void { + const uniq_descriptions = new Set(this.operations.map((op) => op.spec.description)); + if(uniq_descriptions.size > 1) + return this.error(`${this.operations.length} '${this.name}' operations must have identical description property.`); + } + + validate_externalDocs(): ValidationError | void { + const uniq_externalDocs = new Set(this.operations.map((op) => op.spec.externalDocs?.url)); + if(uniq_externalDocs.size > 1) + return this.error(`${this.operations.length} '${this.name}' operations must have identical externalDocs property.`); + } + + validate_requestBody(): ValidationError | void { + const uniq_requestBodies = new Set(this.operations.map((op) => op.spec.requestBody?.$ref)); + if(uniq_requestBodies.size > 1) + return this.error(`${this.operations.length} '${this.name}' operations must have identical requestBody property.`); + } + + validate_responses(): ValidationError | void { + const key_signatures = this.operations.map((op) => Object.keys(op.spec.responses).sort().join('#$@')); + const uniq_signatures = new Set(key_signatures); + if(uniq_signatures.size > 1) + return this.error(`${this.operations.length} '${this.name}' operations must have an identical set of responses.`); + } + + validate_query_parameters(): ValidationError | void { + const query_signatures = this.operations.map((op) => op.query_params().sort().join('#$@')); + const uniq_signatures = new Set(query_signatures); + if(uniq_signatures.size > 1) + return this.error(`${this.operations.length} '${this.name}' operations must have an identical set of query parameters.`); + } } \ No newline at end of file diff --git a/tools/linter/components/RootFile.ts b/tools/linter/components/RootFile.ts index 5e84bac7a..1273f9aed 100644 --- a/tools/linter/components/RootFile.ts +++ b/tools/linter/components/RootFile.ts @@ -2,33 +2,33 @@ import {ParameterSpec, ValidationError} from "../../types"; import FileValidator from "./base/FileValidator"; export default class RootFile extends FileValidator { - constructor(file_path: string) { - super(file_path); - this.file = file_path.split('/').pop()!; - } + constructor(file_path: string) { + super(file_path); + this.file = file_path.split('/').pop()!; + } - validate_file(): ValidationError[] { - return [ - this.validate_paths(), - this.validate_params(), - ].flat(); - } + validate_file(): ValidationError[] { + return [ + this.validate_paths(), + this.validate_params(), + ].flat(); + } - validate_paths(): ValidationError[] { - return Object.entries(this.spec().paths).map(([path, spec]) => { - if(!spec?.$ref) - return this.error(`Every path must be a reference object to a path in a namespace file.`, `Path: ${path}`); - }).filter((e) => e) as ValidationError[]; - } + validate_paths(): ValidationError[] { + return Object.entries(this.spec().paths).map(([path, spec]) => { + if(!spec?.$ref) + return this.error(`Every path must be a reference object to a path in a namespace file.`, `Path: ${path}`); + }).filter((e) => e) as ValidationError[]; + } - validate_params(): ValidationError[] { - const params = (this.spec().components?.parameters || {}) as Record; - return Object.entries(params).map(([name, param]) => { - const expected = `_global::${param.in}.${param.name}`; - if(name !== expected) - return this.error(`Parameters in root file must be in the format '_global::{in}.{name}'. Expected '${expected}'.`, `#/components/parameters/${name}`); - if(!param['x-global']) - return this.error(`Parameters in root file must have 'x-global' extension set to true.`, `#/components/parameters/${name}`); - }).filter((e) => e) as ValidationError[]; - } + validate_params(): ValidationError[] { + const params = (this.spec().components?.parameters || {}) as Record; + return Object.entries(params).map(([name, param]) => { + const expected = `_global::${param.in}.${param.name}`; + if(name !== expected) + return this.error(`Parameters in root file must be in the format '_global::{in}.{name}'. Expected '${expected}'.`, `#/components/parameters/${name}`); + if(!param['x-global']) + return this.error(`Parameters in root file must have 'x-global' extension set to true.`, `#/components/parameters/${name}`); + }).filter((e) => e) as ValidationError[]; + } } \ No newline at end of file diff --git a/tools/linter/components/Schema.ts b/tools/linter/components/Schema.ts index d7ab3d1a0..ca6a6f325 100644 --- a/tools/linter/components/Schema.ts +++ b/tools/linter/components/Schema.ts @@ -5,21 +5,21 @@ import {ValidationError} from "../../types"; const NAME_REGEX = /^[A-Za-z0-9]+$/; export default class Schema extends ValidatorBase { - name: string; - spec: OpenAPIV3.SchemaObject; + name: string; + spec: OpenAPIV3.SchemaObject; - constructor(error_file: string, name: string, spec: OpenAPIV3.SchemaObject) { - super(error_file, `#/components/schemas/${name}`); - this.name = name - this.spec = spec; - } + constructor(error_file: string, name: string, spec: OpenAPIV3.SchemaObject) { + super(error_file, `#/components/schemas/${name}`); + this.name = name + this.spec = spec; + } - validate(): ValidationError[] { - return [this.validate_name()].filter(e => e) as ValidationError[]; - } + validate(): ValidationError[] { + return [this.validate_name()].filter(e => e) as ValidationError[]; + } - validate_name(): ValidationError | undefined { - if(!NAME_REGEX.test(this.name)) - return this.error(`Invalid schema name '${this.name}'. Only alphanumeric characters are allowed.`,); - } + validate_name(): ValidationError | undefined { + if(!NAME_REGEX.test(this.name)) + return this.error(`Invalid schema name '${this.name}'. Only alphanumeric characters are allowed.`,); + } } \ No newline at end of file diff --git a/tools/linter/components/SchemaFile.ts b/tools/linter/components/SchemaFile.ts index 2d989365a..e46c30fde 100644 --- a/tools/linter/components/SchemaFile.ts +++ b/tools/linter/components/SchemaFile.ts @@ -7,36 +7,36 @@ const CATEGORY_REGEX = /^[a-z_]+\.[a-z_]+$/; const NAME_REGEX = /^[a-z]+[a-z_]*[a-z]+$/; export default class SchemaFile extends FileValidator { - category: string; - _schemas: Schema[] | undefined; + category: string; + _schemas: Schema[] | undefined; - constructor(file_path: string) { - super(file_path); - this.category = file_path.split('/').slice(-1)[0].replace('.yaml', ''); - } + constructor(file_path: string) { + super(file_path); + this.category = file_path.split('/').slice(-1)[0].replace('.yaml', ''); + } - validate_file(): ValidationError[] { - const category_error = this.validate_category(); - if(category_error) return [category_error]; + validate_file(): ValidationError[] { + const category_error = this.validate_category(); + if(category_error) return [category_error]; - return [ - ...this.schemas().flatMap(s => s.validate()) - ]; - } + return [ + ...this.schemas().flatMap(s => s.validate()) + ]; + } - schemas(): Schema[] { - if(this._schemas) return this._schemas; - return Object.entries(this.spec().components?.schemas || {}).map(([name, spec]) => { - return new Schema(this.file, name, spec as OpenAPIV3.SchemaObject); - }); - } + schemas(): Schema[] { + if(this._schemas) return this._schemas; + return Object.entries(this.spec().components?.schemas || {}).map(([name, spec]) => { + return new Schema(this.file, name, spec as OpenAPIV3.SchemaObject); + }); + } - validate_category(category = this.category): ValidationError | void { - if(category === '_common') return; - if(!category.match(CATEGORY_REGEX)) - return this.error(`Invalid category name '${category}'. Must match regex: /${CATEGORY_REGEX.source}/.`, 'File Name'); - const name = category.split('.')[1]; - if(name !== '_common' && !name.match(NAME_REGEX)) - return this.error(`Invalid category name '${category}'. '${name}' does not match regex: /${NAME_REGEX.source}/.`, 'File Name'); - } + validate_category(category = this.category): ValidationError | void { + if(category === '_common') return; + if(!category.match(CATEGORY_REGEX)) + return this.error(`Invalid category name '${category}'. Must match regex: /${CATEGORY_REGEX.source}/.`, 'File Name'); + const name = category.split('.')[1]; + if(name !== '_common' && !name.match(NAME_REGEX)) + return this.error(`Invalid category name '${category}'. '${name}' does not match regex: /${NAME_REGEX.source}/.`, 'File Name'); + } } \ No newline at end of file diff --git a/tools/linter/components/SchemasFolder.ts b/tools/linter/components/SchemasFolder.ts index 3f65eae54..68eeb1f71 100644 --- a/tools/linter/components/SchemasFolder.ts +++ b/tools/linter/components/SchemasFolder.ts @@ -3,11 +3,11 @@ import FolderValidator from "./base/FolderValidator"; import {ValidationError} from "../../types"; export default class SchemasFolder extends FolderValidator { - constructor(folder_path: string) { - super(folder_path, SchemaFile); - } + constructor(folder_path: string) { + super(folder_path, SchemaFile); + } - validate_folder(): ValidationError[] { - return []; - } + validate_folder(): ValidationError[] { + return []; + } } \ No newline at end of file diff --git a/tools/linter/components/base/FileValidator.ts b/tools/linter/components/base/FileValidator.ts index 76d6632ac..6d4dec8c7 100644 --- a/tools/linter/components/base/FileValidator.ts +++ b/tools/linter/components/base/FileValidator.ts @@ -5,41 +5,41 @@ import YAML from "yaml"; import {OpenAPIV3} from "openapi-types"; export default class FileValidator extends ValidatorBase { - file_path: string; - _spec: OpenAPIV3.Document | undefined; + file_path: string; + _spec: OpenAPIV3.Document | undefined; - constructor(file_path: string) { - super(file_path.split('/').slice(-2).join('/')); - this.file_path = file_path; - } + constructor(file_path: string) { + super(file_path.split('/').slice(-2).join('/')); + this.file_path = file_path; + } - spec(): OpenAPIV3.Document { - if(this._spec) return this._spec; - return this._spec = YAML.parse(fs.readFileSync(this.file_path, 'utf8')) || {}; - } + spec(): OpenAPIV3.Document { + if(this._spec) return this._spec; + return this._spec = YAML.parse(fs.readFileSync(this.file_path, 'utf8')) || {}; + } - validate(...args: any[]): ValidationError[] { - const extension_error = this.validate_extension(); - if(extension_error) return [extension_error]; - const yaml_error = this.validate_yaml(); - if(yaml_error) return [yaml_error]; - return this.validate_file(); - } + validate(...args: any[]): ValidationError[] { + const extension_error = this.validate_extension(); + if(extension_error) return [extension_error]; + const yaml_error = this.validate_yaml(); + if(yaml_error) return [yaml_error]; + return this.validate_file(); + } - validate_file(...args: any[]): ValidationError[] { - throw new Error('Method not implemented.'); - } + validate_file(...args: any[]): ValidationError[] { + throw new Error('Method not implemented.'); + } - validate_extension(): ValidationError | undefined { - if(!this.file_path.endsWith('.yaml')) - return this.error(`Invalid file extension. Only '.yaml' files are allowed.`, 'File Extension'); - } + validate_extension(): ValidationError | undefined { + if(!this.file_path.endsWith('.yaml')) + return this.error(`Invalid file extension. Only '.yaml' files are allowed.`, 'File Extension'); + } - validate_yaml(): ValidationError | undefined { - try { - this.spec(); - } catch (e: any) { - return this.error(`Unable to read or parse YAML.`, 'File Content'); - } + validate_yaml(): ValidationError | undefined { + try { + this.spec(); + } catch (e: any) { + return this.error(`Unable to read or parse YAML.`, 'File Content'); } + } } \ No newline at end of file diff --git a/tools/linter/components/base/FolderValidator.ts b/tools/linter/components/base/FolderValidator.ts index 7f7e0be1d..8932a4c7f 100644 --- a/tools/linter/components/base/FolderValidator.ts +++ b/tools/linter/components/base/FolderValidator.ts @@ -4,27 +4,27 @@ import FileValidator from "./FileValidator"; import {ValidationError} from "../../../types"; export default class FolderValidator extends ValidatorBase { - folder_path: string; - files: F[]; + folder_path: string; + files: F[]; - constructor(folder_path: string, file_type: new (file_path: string) => F) { - const parts = folder_path.split('/').reverse(); - const folder_name = (parts[0] === undefined ? parts[1] : parts[0]) + '/'; - super(folder_name, 'Folder'); - this.folder_path = folder_path; - this.files = fs.readdirSync(this.folder_path).sort() - .filter((file) => file !== '.gitkeep') - .map((file) => { return new file_type(`${this.folder_path}/${file}`) as F; }); - } + constructor(folder_path: string, file_type: new (file_path: string) => F) { + const parts = folder_path.split('/').reverse(); + const folder_name = (parts[0] === undefined ? parts[1] : parts[0]) + '/'; + super(folder_name, 'Folder'); + this.folder_path = folder_path; + this.files = fs.readdirSync(this.folder_path).sort() + .filter((file) => file !== '.gitkeep') + .map((file) => { return new file_type(`${this.folder_path}/${file}`) as F; }); + } - validate(): ValidationError[] { - return [ - ...this.files.flatMap((file) => file.validate()), - ...this.validate_folder(), - ]; - } + validate(): ValidationError[] { + return [ + ...this.files.flatMap((file) => file.validate()), + ...this.validate_folder(), + ]; + } - validate_folder(): ValidationError[] { - throw new Error('Method not implemented.'); - } + validate_folder(): ValidationError[] { + throw new Error('Method not implemented.'); + } } \ No newline at end of file diff --git a/tools/linter/components/base/ValidatorBase.ts b/tools/linter/components/base/ValidatorBase.ts index d47483455..f9f1112bc 100644 --- a/tools/linter/components/base/ValidatorBase.ts +++ b/tools/linter/components/base/ValidatorBase.ts @@ -1,18 +1,18 @@ import { ValidationError } from "../../../types"; export default class ValidatorBase { - file: string; - location: string | undefined; + file: string; + location: string | undefined; - constructor(file: string, location?: string) { - this.file = file; - this.location = location; - } + constructor(file: string, location?: string) { + this.file = file; + this.location = location; + } - error(message: string, location = this.location, file = this.file): ValidationError { - return { file, location, message }; - } + error(message: string, location = this.location, file = this.file): ValidationError { + return { file, location, message }; + } - validate(): ValidationError[] { - throw new Error('Method not implemented.'); - } + validate(): ValidationError[] { + throw new Error('Method not implemented.'); + } } \ No newline at end of file diff --git a/tools/linter/lint.ts b/tools/linter/lint.ts index f137b23a0..e090a28da 100644 --- a/tools/linter/lint.ts +++ b/tools/linter/lint.ts @@ -6,11 +6,11 @@ const validator = new SpecValidator(root_folder); const errors = validator.validate(); if(errors.length === 0) { - console.log('No errors found.'); - process.exit(0); + console.log('No errors found.'); + process.exit(0); } else { - console.log('Errors found:\n'); - errors.forEach(e => console.error(e)); - console.log('\nTotal errors:', errors.length) - process.exit(1); + console.log('Errors found:\n'); + errors.forEach(e => console.error(e)); + console.log('\nTotal errors:', errors.length) + process.exit(1); } \ No newline at end of file diff --git a/tools/merger/OpenApiMerger.ts b/tools/merger/OpenApiMerger.ts index 2e108ce55..4d9520d93 100644 --- a/tools/merger/OpenApiMerger.ts +++ b/tools/merger/OpenApiMerger.ts @@ -6,122 +6,122 @@ import { write2file } from '../helpers'; // Create a single-file OpenAPI spec from multiple files for OpenAPI validation and programmatic consumption export default class OpenApiMerger { - root_path: string; - root_folder: string; - spec: Record; - global_param_refs: OpenAPIV3.ReferenceObject[]; - - paths: Record> = {}; // namespace -> path -> path_item_object - schemas: Record> = {}; // category -> schema -> schema_object - - constructor(root_path: string) { - this.root_path = fs.realpathSync(root_path); - this.root_folder = this.root_path.split('/').slice(0, -1).join('/'); - this.spec = yaml.parse(fs.readFileSync(this.root_path, 'utf8')); - const global_params: OpenAPIV3.ParameterObject = this.spec.components?.parameters || {}; - this.global_param_refs = Object.keys(global_params).map(param => ({$ref: `#/components/parameters/${param}`})); - this.spec.components = { - parameters: global_params, - requestBodies: {}, - responses: {}, - schemas: {}, - }; - } - - merge(output_path?: string): OpenAPIV3.Document { - this.#merge_schemas(); - this.#merge_namespaces(); - this.#apply_global_params(); - this.#sort_spec_keys(); - - if(output_path) write2file(output_path, this.spec); - return this.spec as OpenAPIV3.Document; - } - - // Apply global parameters to all operations in the spec. - #apply_global_params(): void { - Object.entries(this.spec.paths).forEach(([path, pathItem]) => { - Object.entries(pathItem!).forEach(([method, operation]) => { - const params = operation.parameters || []; - operation.parameters = [...params, ...Object.values(this.global_param_refs)]; - }); - }); - } - - // Merge files from /namespaces folder. - #merge_namespaces(): void { - const folder = `${this.root_folder}/namespaces`; - fs.readdirSync(folder).forEach(file => { - const spec = yaml.parse(fs.readFileSync(`${folder}/${file}`, 'utf8')); - const namespace = file.split('.yaml')[0]; - this.redirect_refs_in_namespace(spec); - this.paths[namespace] = spec['paths']; - this.spec.components.parameters = {...this.spec.components.parameters, ...spec['components']['parameters']}; - this.spec.components.responses = {...this.spec.components.responses, ...spec['components']['responses']}; - this.spec.components.requestBodies = {...this.spec.components.requestBodies, ...spec['components']['requestBodies']}; - }); - - Object.entries(this.spec.paths).forEach(([path, refObj]) => { - const ref = (refObj as Record).$ref!; - const namespace = ref.match(/namespaces\/(.*)\.yaml/)![1]; - this.spec.paths[path] = this.paths[namespace][path]; - }); - } - - // Redirect schema references in namespace files to local references in single-file spec. - redirect_refs_in_namespace(obj: Record): void { - const ref = obj.$ref; - if(ref?.startsWith('../schemas/')) - obj.$ref = ref.replace('../schemas/', '#/components/schemas/').replace('.yaml#/components/schemas/', ':'); - - for(const key in obj) - if(typeof obj[key] === 'object') - this.redirect_refs_in_namespace(obj[key]); - } - - // Merge files from /schemas folder. - #merge_schemas(): void { - const folder = `${this.root_folder}/schemas`; - fs.readdirSync(folder).forEach(file => { - const spec = yaml.parse(fs.readFileSync(`${folder}/${file}`, 'utf8')); - const category = file.split('.yaml')[0]; - this.redirect_refs_in_schema(category, spec); - this.schemas[category] = spec['components']['schemas'] as Record; - }); - - Object.entries(this.schemas).forEach(([category, schemas]) => { - Object.entries(schemas).forEach(([name, schemaObj]) => { - this.spec.components.schemas[`${category}:${name}`] = schemaObj; - }); - }); - } - - // Redirect schema references in schema files to local references in single-file spec. - redirect_refs_in_schema(category: string, obj: Record): void { - const ref = obj.$ref; - if(ref) - if(ref.startsWith('#/components/schemas')) - obj.$ref = `#/components/schemas/${category}:${ref.split('/').pop()}`; - else { - const other_category = ref.match(/(.*)\.yaml/)![1]; - obj.$ref = `#/components/schemas/${other_category}:${ref.split('/').pop()}`; - } - - for(const key in obj) - if(typeof obj[key] === 'object') - this.redirect_refs_in_schema(category, obj[key]); - } - - // Sort keys in the spec to make it easier to read and compare. - #sort_spec_keys(): void { - this.spec.components.schemas = _.fromPairs(Object.entries(this.spec.components.schemas).sort()); - this.spec.components.parameters = _.fromPairs(Object.entries(this.spec.components.parameters).sort()); - this.spec.components.responses = _.fromPairs(Object.entries(this.spec.components.responses).sort()); - this.spec.components.requestBodies = _.fromPairs(Object.entries(this.spec.components.requestBodies).sort()); - - this.spec.paths = _.fromPairs(Object.entries(this.spec.paths).sort()); - Object.entries(this.spec.paths).forEach(([path, pathItem]) => { - this.spec.paths[path] = _.fromPairs(Object.entries(pathItem!).sort()); - }); - } + root_path: string; + root_folder: string; + spec: Record; + global_param_refs: OpenAPIV3.ReferenceObject[]; + + paths: Record> = {}; // namespace -> path -> path_item_object + schemas: Record> = {}; // category -> schema -> schema_object + + constructor(root_path: string) { + this.root_path = fs.realpathSync(root_path); + this.root_folder = this.root_path.split('/').slice(0, -1).join('/'); + this.spec = yaml.parse(fs.readFileSync(this.root_path, 'utf8')); + const global_params: OpenAPIV3.ParameterObject = this.spec.components?.parameters || {}; + this.global_param_refs = Object.keys(global_params).map(param => ({$ref: `#/components/parameters/${param}`})); + this.spec.components = { + parameters: global_params, + requestBodies: {}, + responses: {}, + schemas: {}, + }; + } + + merge(output_path?: string): OpenAPIV3.Document { + this.#merge_schemas(); + this.#merge_namespaces(); + this.#apply_global_params(); + this.#sort_spec_keys(); + + if(output_path) write2file(output_path, this.spec); + return this.spec as OpenAPIV3.Document; + } + + // Apply global parameters to all operations in the spec. + #apply_global_params(): void { + Object.entries(this.spec.paths).forEach(([path, pathItem]) => { + Object.entries(pathItem!).forEach(([method, operation]) => { + const params = operation.parameters || []; + operation.parameters = [...params, ...Object.values(this.global_param_refs)]; + }); + }); + } + + // Merge files from /namespaces folder. + #merge_namespaces(): void { + const folder = `${this.root_folder}/namespaces`; + fs.readdirSync(folder).forEach(file => { + const spec = yaml.parse(fs.readFileSync(`${folder}/${file}`, 'utf8')); + const namespace = file.split('.yaml')[0]; + this.redirect_refs_in_namespace(spec); + this.paths[namespace] = spec['paths']; + this.spec.components.parameters = {...this.spec.components.parameters, ...spec['components']['parameters']}; + this.spec.components.responses = {...this.spec.components.responses, ...spec['components']['responses']}; + this.spec.components.requestBodies = {...this.spec.components.requestBodies, ...spec['components']['requestBodies']}; + }); + + Object.entries(this.spec.paths).forEach(([path, refObj]) => { + const ref = (refObj as Record).$ref!; + const namespace = ref.match(/namespaces\/(.*)\.yaml/)![1]; + this.spec.paths[path] = this.paths[namespace][path]; + }); + } + + // Redirect schema references in namespace files to local references in single-file spec. + redirect_refs_in_namespace(obj: Record): void { + const ref = obj.$ref; + if(ref?.startsWith('../schemas/')) + obj.$ref = ref.replace('../schemas/', '#/components/schemas/').replace('.yaml#/components/schemas/', ':'); + + for(const key in obj) + if(typeof obj[key] === 'object') + this.redirect_refs_in_namespace(obj[key]); + } + + // Merge files from /schemas folder. + #merge_schemas(): void { + const folder = `${this.root_folder}/schemas`; + fs.readdirSync(folder).forEach(file => { + const spec = yaml.parse(fs.readFileSync(`${folder}/${file}`, 'utf8')); + const category = file.split('.yaml')[0]; + this.redirect_refs_in_schema(category, spec); + this.schemas[category] = spec['components']['schemas'] as Record; + }); + + Object.entries(this.schemas).forEach(([category, schemas]) => { + Object.entries(schemas).forEach(([name, schemaObj]) => { + this.spec.components.schemas[`${category}:${name}`] = schemaObj; + }); + }); + } + + // Redirect schema references in schema files to local references in single-file spec. + redirect_refs_in_schema(category: string, obj: Record): void { + const ref = obj.$ref; + if(ref) + if(ref.startsWith('#/components/schemas')) + obj.$ref = `#/components/schemas/${category}:${ref.split('/').pop()}`; + else { + const other_category = ref.match(/(.*)\.yaml/)![1]; + obj.$ref = `#/components/schemas/${other_category}:${ref.split('/').pop()}`; + } + + for(const key in obj) + if(typeof obj[key] === 'object') + this.redirect_refs_in_schema(category, obj[key]); + } + + // Sort keys in the spec to make it easier to read and compare. + #sort_spec_keys(): void { + this.spec.components.schemas = _.fromPairs(Object.entries(this.spec.components.schemas).sort()); + this.spec.components.parameters = _.fromPairs(Object.entries(this.spec.components.parameters).sort()); + this.spec.components.responses = _.fromPairs(Object.entries(this.spec.components.responses).sort()); + this.spec.components.requestBodies = _.fromPairs(Object.entries(this.spec.components.requestBodies).sort()); + + this.spec.paths = _.fromPairs(Object.entries(this.spec.paths).sort()); + Object.entries(this.spec.paths).forEach(([path, pathItem]) => { + this.spec.paths[path] = _.fromPairs(Object.entries(pathItem!).sort()); + }); + } } \ No newline at end of file diff --git a/tools/test/linter/NamespaceFile.test.ts b/tools/test/linter/NamespaceFile.test.ts index 88e47f6a5..51b5e2cab 100644 --- a/tools/test/linter/NamespaceFile.test.ts +++ b/tools/test/linter/NamespaceFile.test.ts @@ -2,98 +2,98 @@ import {mocked_namespace_file, namespace_file} from "./factories/namespace_file" test('constructor()', () => { - const file = namespace_file('empty.yaml'); - expect(file.file).toBe('namespaces/empty.yaml'); - expect(file.namespace).toBe('empty'); - expect(file.operation_groups()).toEqual([]); + const file = namespace_file('empty.yaml'); + expect(file.file).toBe('namespaces/empty.yaml'); + expect(file.namespace).toBe('empty'); + expect(file.operation_groups()).toEqual([]); }); test('validate_name()', () => { - const ns_file = mocked_namespace_file({}); + const ns_file = mocked_namespace_file({}); - expect(ns_file.validate_name('_core')).toBeUndefined(); - expect(ns_file.validate_name('indices')).toBeUndefined(); - expect(ns_file.validate_name('_cat')).toEqual({ - file: 'namespaces/indices.yaml', - location: 'File Name', - message: `Invalid namespace name '_cat'. Must match regex: /^[a-z]+[a-z_]*[a-z]+$/.` - }); + expect(ns_file.validate_name('_core')).toBeUndefined(); + expect(ns_file.validate_name('indices')).toBeUndefined(); + expect(ns_file.validate_name('_cat')).toEqual({ + file: 'namespaces/indices.yaml', + location: 'File Name', + message: `Invalid namespace name '_cat'. Must match regex: /^[a-z]+[a-z_]*[a-z]+$/.` + }); }); test('validate_schemas()', () => { - const with_schemas = mocked_namespace_file({spec: {components: {schemas: {}}}}); - expect(with_schemas.validate_schemas()).toEqual({ - file: `namespaces/indices.yaml`, - location: `#/components/schemas`, - message: `components/schemas is not allowed in namespace files` - }); + const with_schemas = mocked_namespace_file({spec: {components: {schemas: {}}}}); + expect(with_schemas.validate_schemas()).toEqual({ + file: `namespaces/indices.yaml`, + location: `#/components/schemas`, + message: `components/schemas is not allowed in namespace files` + }); - const no_schemas = mocked_namespace_file({spec: {components: {}}}); - expect(no_schemas.validate_schemas()).toBeUndefined(); + const no_schemas = mocked_namespace_file({spec: {components: {}}}); + expect(no_schemas.validate_schemas()).toBeUndefined(); }); test('validate_unresolved_refs()', () => { - const validator = namespace_file('invalid_components.yaml'); - expect(validator.validate_unresolved_refs()).toEqual([ - { - file: `namespaces/invalid_components.yaml`, - location: `#/components/responses/indices.create@200`, - message: `Unresolved reference: #/components/responses/indices.create@200` - }, - { - file: `namespaces/invalid_components.yaml`, - location: `#/components/parameters/indices.create::query.pretty`, - message: `Unresolved reference: #/components/parameters/indices.create::query.pretty` - } - ]); + const validator = namespace_file('invalid_components.yaml'); + expect(validator.validate_unresolved_refs()).toEqual([ + { + file: `namespaces/invalid_components.yaml`, + location: `#/components/responses/indices.create@200`, + message: `Unresolved reference: #/components/responses/indices.create@200` + }, + { + file: `namespaces/invalid_components.yaml`, + location: `#/components/parameters/indices.create::query.pretty`, + message: `Unresolved reference: #/components/parameters/indices.create::query.pretty` + } + ]); }); test('validate_unused_refs()', () => { - const validator = namespace_file('invalid_components.yaml'); - expect(validator.validate_unused_refs()).toEqual([ - { - file: `namespaces/invalid_components.yaml`, - location: `#/components/requestBodies/indices.create`, - message: `Unused requestBodies component: indices.create` - }, - { - file: `namespaces/invalid_components.yaml`, - location: `#/components/parameters/indices.create::query.h`, - message: `Unused parameters component: indices.create::query.h` - } - ]); + const validator = namespace_file('invalid_components.yaml'); + expect(validator.validate_unused_refs()).toEqual([ + { + file: `namespaces/invalid_components.yaml`, + location: `#/components/requestBodies/indices.create`, + message: `Unused requestBodies component: indices.create` + }, + { + file: `namespaces/invalid_components.yaml`, + location: `#/components/parameters/indices.create::query.h`, + message: `Unused parameters component: indices.create::query.h` + } + ]); }); test('validate_parameter_refs()', () => { - const validator = namespace_file('invalid_components.yaml'); - expect(validator.validate_parameter_refs()).toEqual([ - { - file: "namespaces/invalid_components.yaml", - location: "#/components/parameters/#indices.create::query.ExpandWildcards", - message: "Invalid parameter name 'ExpandWildcards'. A parameter's name can only contain lower-cased alphanumerics, underscores, and periods." - }, - { - file: "namespaces/invalid_components.yaml", - location: "#/components/parameters/#indices.create::query.h", - message: "Parameter component 'indices.create::query.h' must be named 'indices.create::query.v' since it is a query parameter named 'v'." - } - ]); + const validator = namespace_file('invalid_components.yaml'); + expect(validator.validate_parameter_refs()).toEqual([ + { + file: "namespaces/invalid_components.yaml", + location: "#/components/parameters/#indices.create::query.ExpandWildcards", + message: "Invalid parameter name 'ExpandWildcards'. A parameter's name can only contain lower-cased alphanumerics, underscores, and periods." + }, + { + file: "namespaces/invalid_components.yaml", + location: "#/components/parameters/#indices.create::query.h", + message: "Parameter component 'indices.create::query.h' must be named 'indices.create::query.v' since it is a query parameter named 'v'." + } + ]); }); test('validate()', () => { - const invalid_name = mocked_namespace_file({returned_values: {validate_name: 'Invalid Name'}, groups_errors: [['group error']]}); - expect(invalid_name.validate()).toEqual(['Invalid Name']); + const invalid_name = mocked_namespace_file({returned_values: {validate_name: 'Invalid Name'}, groups_errors: [['group error']]}); + expect(invalid_name.validate()).toEqual(['Invalid Name']); - const invalid_groups = mocked_namespace_file({returned_values: { validate_schemas: 'Invalid schemas' }, groups_errors: [['error']]}); - expect(invalid_groups.validate()).toEqual(['error']); + const invalid_groups = mocked_namespace_file({returned_values: { validate_schemas: 'Invalid schemas' }, groups_errors: [['error']]}); + expect(invalid_groups.validate()).toEqual(['error']); - const typical = mocked_namespace_file({returned_values: { - validate_schemas: 'schemas error', - validate_unresolved_refs: ['unresolved'], - validate_unused_refs: ['unused'], - validate_parameter_refs: ['parameter']}}); - expect(typical.validate()).toEqual(['schemas error', 'unresolved', 'unused', 'parameter']); + const typical = mocked_namespace_file({returned_values: { + validate_schemas: 'schemas error', + validate_unresolved_refs: ['unresolved'], + validate_unused_refs: ['unused'], + validate_parameter_refs: ['parameter']}}); + expect(typical.validate()).toEqual(['schemas error', 'unresolved', 'unused', 'parameter']); - const valid = mocked_namespace_file({groups_errors: [[], []]}); - expect(valid.validate()).toEqual([]); + const valid = mocked_namespace_file({groups_errors: [[], []]}); + expect(valid.validate()).toEqual([]); }); \ No newline at end of file diff --git a/tools/test/linter/NamespacesFolder.test.ts b/tools/test/linter/NamespacesFolder.test.ts index 9563988aa..64dff9fa5 100644 --- a/tools/test/linter/NamespacesFolder.test.ts +++ b/tools/test/linter/NamespacesFolder.test.ts @@ -1,47 +1,47 @@ import NamespacesFolder from "../../linter/components/NamespacesFolder"; test('validate()', () => { - const validator = new NamespacesFolder('./test/linter/fixtures/folder_validators/namespaces'); - expect(validator.validate()).toEqual([ - { - file: "namespaces/indices.txt", - location: "File Extension", - message: "Invalid file extension. Only '.yaml' files are allowed." - }, - { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", - message: "Missing description property." - }, - { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", - message: "Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'." - }, - { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", - message: "Path parameters must match the parameters in the path: {id}, {index}." - }, - { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", - message: "The 200 response must be a reference object to '#/components/responses/invalid_spec.fetch@200'." - }, - { - file: "namespaces/invalid_yaml.yaml", - location: "File Content", - message: "Unable to read or parse YAML." - }, - { - file: "namespaces/", - location: "Folder", - message: "Duplicate path '/{index}' found in namespaces: dup_path_a, dup_path_c." - }, - { - file: "namespaces/", - location: "Folder", - message: "Duplicate path '/{index}/_rollover' found in namespaces: dup_path_a, dup_path_b, dup_path_c." - } - ]); + const validator = new NamespacesFolder('./test/linter/fixtures/folder_validators/namespaces'); + expect(validator.validate()).toEqual([ + { + file: "namespaces/indices.txt", + location: "File Extension", + message: "Invalid file extension. Only '.yaml' files are allowed." + }, + { + file: "namespaces/invalid_spec.yaml", + location: "Operation: GET /{index}/_doc/{id}", + message: "Missing description property." + }, + { + file: "namespaces/invalid_spec.yaml", + location: "Operation: GET /{index}/_doc/{id}", + message: "Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'." + }, + { + file: "namespaces/invalid_spec.yaml", + location: "Operation: GET /{index}/_doc/{id}", + message: "Path parameters must match the parameters in the path: {id}, {index}." + }, + { + file: "namespaces/invalid_spec.yaml", + location: "Operation: GET /{index}/_doc/{id}", + message: "The 200 response must be a reference object to '#/components/responses/invalid_spec.fetch@200'." + }, + { + file: "namespaces/invalid_yaml.yaml", + location: "File Content", + message: "Unable to read or parse YAML." + }, + { + file: "namespaces/", + location: "Folder", + message: "Duplicate path '/{index}' found in namespaces: dup_path_a, dup_path_c." + }, + { + file: "namespaces/", + location: "Folder", + message: "Duplicate path '/{index}/_rollover' found in namespaces: dup_path_a, dup_path_b, dup_path_c." + } + ]); }); diff --git a/tools/test/linter/Operation.test.ts b/tools/test/linter/Operation.test.ts index 43fce1229..cc385d74b 100644 --- a/tools/test/linter/Operation.test.ts +++ b/tools/test/linter/Operation.test.ts @@ -1,153 +1,153 @@ import {mocked_operation, operation} from "./factories/operation"; test('validate_group()', () => { - const no_group = operation({'x-operation-group': ''}); - expect(no_group.validate_group()).toEqual({ - file: 'namespaces/indices.yaml', - location: `Operation: POST /{index}/something/{abc_xyz}`, - message: `Missing x-operation-group property`, - }); - - const invalid_group = operation({'x-operation-group': 'indices_'}); - expect(invalid_group.validate_group()) - .toEqual(invalid_group.error(`Invalid x-operation-group 'indices_'. Must match regex: /^([a-z]+[a-z_]*[a-z]+\\.)?([a-z]+[a-z_]*[a-z]+)$/.`)); - - const invalid_action = operation({'x-operation-group': 'indices.create.index'}); - expect(invalid_action.validate_group()) - .toEqual(invalid_action.error(`Invalid x-operation-group 'indices.create.index'. Must match regex: /^([a-z]+[a-z_]*[a-z]+\\.)?([a-z]+[a-z_]*[a-z]+)$/.`)); - - const valid_group = operation({'x-operation-group': 'indices.create'}); - expect(valid_group.validate_group()) - .toBeUndefined(); + const no_group = operation({'x-operation-group': ''}); + expect(no_group.validate_group()).toEqual({ + file: 'namespaces/indices.yaml', + location: `Operation: POST /{index}/something/{abc_xyz}`, + message: `Missing x-operation-group property`, + }); + + const invalid_group = operation({'x-operation-group': 'indices_'}); + expect(invalid_group.validate_group()) + .toEqual(invalid_group.error(`Invalid x-operation-group 'indices_'. Must match regex: /^([a-z]+[a-z_]*[a-z]+\\.)?([a-z]+[a-z_]*[a-z]+)$/.`)); + + const invalid_action = operation({'x-operation-group': 'indices.create.index'}); + expect(invalid_action.validate_group()) + .toEqual(invalid_action.error(`Invalid x-operation-group 'indices.create.index'. Must match regex: /^([a-z]+[a-z_]*[a-z]+\\.)?([a-z]+[a-z_]*[a-z]+)$/.`)); + + const valid_group = operation({'x-operation-group': 'indices.create'}); + expect(valid_group.validate_group()) + .toBeUndefined(); }); test('validate_namespace()', () => { - const valid_core = operation({'x-operation-group': 'search'}, '_core.yaml'); - expect(valid_core.validate_namespace()) - .toBeUndefined(); + const valid_core = operation({'x-operation-group': 'search'}, '_core.yaml'); + expect(valid_core.validate_namespace()) + .toBeUndefined(); - const valid_non_core = operation({'x-operation-group': 'indices._create'}, 'indices.yaml'); - expect(valid_non_core.validate_namespace()) - .toBeUndefined(); + const valid_non_core = operation({'x-operation-group': 'indices._create'}, 'indices.yaml'); + expect(valid_non_core.validate_namespace()) + .toBeUndefined(); - const non_omitted_core = operation({'x-operation-group': '_core.search'}, '_core.yaml'); - expect(non_omitted_core.validate_namespace()) - .toEqual(non_omitted_core.error(`Invalid x-operation-group '_core.search'. '_core' namespace must be omitted in x-operation-group.`)); + const non_omitted_core = operation({'x-operation-group': '_core.search'}, '_core.yaml'); + expect(non_omitted_core.validate_namespace()) + .toEqual(non_omitted_core.error(`Invalid x-operation-group '_core.search'. '_core' namespace must be omitted in x-operation-group.`)); - const unmatched_namespace = operation({'x-operation-group': 'indices.create'}, 'cat.yaml'); - expect(unmatched_namespace.validate_namespace()) - .toEqual(unmatched_namespace.error(`Invalid x-operation-group 'indices.create'. 'indices' namespace detected. Only 'cat' namespace is allowed in this file.`)); + const unmatched_namespace = operation({'x-operation-group': 'indices.create'}, 'cat.yaml'); + expect(unmatched_namespace.validate_namespace()) + .toEqual(unmatched_namespace.error(`Invalid x-operation-group 'indices.create'. 'indices' namespace detected. Only 'cat' namespace is allowed in this file.`)); }); test('validate_operationId()', () => { - const no_id = operation({'x-operation-group': 'indices.create'}); - expect(no_id.validate_operationId()) - .toEqual(no_id.error(`Missing operationId property.`)); + const no_id = operation({'x-operation-group': 'indices.create'}); + expect(no_id.validate_operationId()) + .toEqual(no_id.error(`Missing operationId property.`)); - const invalid_id = operation({'x-operation-group': 'indices.create', operationId: 'create_index'}); - expect(invalid_id.validate_operationId()) - .toEqual(invalid_id.error(`Invalid operationId 'create_index'. Must be in {x-operation-group}.{number} format.`)); + const invalid_id = operation({'x-operation-group': 'indices.create', operationId: 'create_index'}); + expect(invalid_id.validate_operationId()) + .toEqual(invalid_id.error(`Invalid operationId 'create_index'. Must be in {x-operation-group}.{number} format.`)); - const valid_id = operation({'x-operation-group': 'indices.create', operationId: 'indices.create.1'}); - expect(valid_id.validate_operationId()) - .toBeUndefined(); + const valid_id = operation({'x-operation-group': 'indices.create', operationId: 'indices.create.1'}); + expect(valid_id.validate_operationId()) + .toBeUndefined(); }); test('validate_description()', () => { - const no_description = operation({'x-operation-group': 'indices.create'}); - expect(no_description.validate_description()) - .toEqual(no_description.error(`Missing description property.`)); + const no_description = operation({'x-operation-group': 'indices.create'}); + expect(no_description.validate_description()) + .toEqual(no_description.error(`Missing description property.`)); - const invalid_description = operation({'x-operation-group': 'indices.create', description: 'This is a description without a period'}); - expect(invalid_description.validate_description()) - .toEqual(invalid_description.error(`Description must end with a period.`)); + const invalid_description = operation({'x-operation-group': 'indices.create', description: 'This is a description without a period'}); + expect(invalid_description.validate_description()) + .toEqual(invalid_description.error(`Description must end with a period.`)); - const valid_description = operation({'x-operation-group': 'indices.create', description: 'This is a description with a period.'}); - expect(valid_description.validate_description()) - .toBeUndefined(); + const valid_description = operation({'x-operation-group': 'indices.create', description: 'This is a description with a period.'}); + expect(valid_description.validate_description()) + .toBeUndefined(); }); test('validate_requestBody()', () => { - const no_body = operation({'x-operation-group': 'indices.create'}); - expect(no_body.validate_requestBody()) - .toBeUndefined(); + const no_body = operation({'x-operation-group': 'indices.create'}); + expect(no_body.validate_requestBody()) + .toBeUndefined(); - const valid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create'}}); - expect(valid_body.validate_requestBody()) - .toBeUndefined(); + const valid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create'}}); + expect(valid_body.validate_requestBody()) + .toBeUndefined(); - const invalid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create.1'}}); - expect(invalid_body.validate_requestBody()) - .toEqual(invalid_body.error(`The requestBody must be a reference object to '#/components/requestBodies/indices.create'.`)); + const invalid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create.1'}}); + expect(invalid_body.validate_requestBody()) + .toEqual(invalid_body.error(`The requestBody must be a reference object to '#/components/requestBodies/indices.create'.`)); }); test('validate_response()', () => { - const no_responses = operation({responses: {}}); - expect(no_responses.validate_responses()).toEqual([no_responses.error(`Missing responses property.`)]); - - const invalid_responses = operation({'x-operation-group': 'cat.info', responses: { - '200': {$ref: '#/components/responses/cat.info'}, - '500': {$ref: '#/components/responses/cat.info@500'}, - '400': {$ref: '#/components/responses/cat.info:bad_request'}, - }}); - expect(invalid_responses.validate_responses()).toEqual([ - invalid_responses.error(`The 200 response must be a reference object to '#/components/responses/cat.info@200'.`), - invalid_responses.error(`The 400 response must be a reference object to '#/components/responses/cat.info@400'.`),]); - - const valid_responses = operation({'x-operation-group': 'cat.info', responses: {'200': {$ref: '#/components/responses/cat.info@200'}}}); - expect(valid_responses.validate_responses()) - .toEqual([]); + const no_responses = operation({responses: {}}); + expect(no_responses.validate_responses()).toEqual([no_responses.error(`Missing responses property.`)]); + + const invalid_responses = operation({'x-operation-group': 'cat.info', responses: { + '200': {$ref: '#/components/responses/cat.info'}, + '500': {$ref: '#/components/responses/cat.info@500'}, + '400': {$ref: '#/components/responses/cat.info:bad_request'}, + }}); + expect(invalid_responses.validate_responses()).toEqual([ + invalid_responses.error(`The 200 response must be a reference object to '#/components/responses/cat.info@200'.`), + invalid_responses.error(`The 400 response must be a reference object to '#/components/responses/cat.info@400'.`),]); + + const valid_responses = operation({'x-operation-group': 'cat.info', responses: {'200': {$ref: '#/components/responses/cat.info@200'}}}); + expect(valid_responses.validate_responses()) + .toEqual([]); }); test('validate_parameters()', () => { - const no_parameters = operation({'x-operation-group': 'indices.create'}); - expect(no_parameters.validate_parameters()) - .toBeUndefined(); - - const valid_parameters = operation({'x-operation-group': 'indices.create', parameters: [{$ref: '#/components/parameters/indices.create::path.request_timeout'}]}); - expect(valid_parameters.validate_parameters()) - .toBeUndefined(); - - const invalid_parameters = operation({'x-operation-group': 'indices.create', parameters: [ - {$ref: '#/components/parameters/indices.create::path.index'}, - {$ref: '#/components/parameters/indices.create::timeout'}, - {$ref: '#/components/parameters/indices.create::query:pretty'}, - ]}); - expect(invalid_parameters.validate_parameters()) - .toEqual(invalid_parameters.error(`Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'.`)); + const no_parameters = operation({'x-operation-group': 'indices.create'}); + expect(no_parameters.validate_parameters()) + .toBeUndefined(); + + const valid_parameters = operation({'x-operation-group': 'indices.create', parameters: [{$ref: '#/components/parameters/indices.create::path.request_timeout'}]}); + expect(valid_parameters.validate_parameters()) + .toBeUndefined(); + + const invalid_parameters = operation({'x-operation-group': 'indices.create', parameters: [ + {$ref: '#/components/parameters/indices.create::path.index'}, + {$ref: '#/components/parameters/indices.create::timeout'}, + {$ref: '#/components/parameters/indices.create::query:pretty'}, + ]}); + expect(invalid_parameters.validate_parameters()) + .toEqual(invalid_parameters.error(`Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'.`)); }); test('validate_path_parameters()', () => { - const invalid_path_params = operation({parameters: [{$ref: '#/components/parameters/indices.create::path.index'}]}); - expect(invalid_path_params.validate_path_parameters()) - .toEqual(invalid_path_params.error(`Path parameters must match the parameters in the path: {abc_xyz}, {index}.`)); - - const valid_path_params = operation({parameters: [ - {$ref: '#/components/parameters/indices.create::path.index'}, - {$ref: '#/components/parameters/indices.create::path.abc_xyz'} ]}); - expect(valid_path_params.validate_path_parameters()) - .toBeUndefined(); + const invalid_path_params = operation({parameters: [{$ref: '#/components/parameters/indices.create::path.index'}]}); + expect(invalid_path_params.validate_path_parameters()) + .toEqual(invalid_path_params.error(`Path parameters must match the parameters in the path: {abc_xyz}, {index}.`)); + + const valid_path_params = operation({parameters: [ + {$ref: '#/components/parameters/indices.create::path.index'}, + {$ref: '#/components/parameters/indices.create::path.abc_xyz'} ]}); + expect(valid_path_params.validate_path_parameters()) + .toBeUndefined(); }); test('validate()', () => { - const invalid_group = mocked_operation({validate_group: 'Invalid group', validate_namespace: 'Invalid namespace'}); - expect(invalid_group.validate()) - .toEqual(['Invalid group']); - - const invalid_namespace = mocked_operation({validate_group: undefined, validate_namespace: 'Invalid namespace', validate_operationId: 'Invalid operationId'}); - expect(invalid_namespace.validate()) - .toEqual(['Invalid namespace']); - - const typical_invalid = mocked_operation({ - validate_group: undefined, validate_namespace: undefined, - validate_operationId: 'Invalid operationId', validate_description: 'Invalid description', validate_requestBody: 'Invalid requestBody', - validate_responses: ['Invalid response 1', 'Invalid response 2'], - validate_parameters: 'Invalid parameters', validate_path_parameters: 'Invalid path parameters' }); - expect(typical_invalid.validate()) - .toEqual(['Invalid operationId', 'Invalid description', 'Invalid requestBody', - 'Invalid parameters', 'Invalid path parameters', 'Invalid response 1', 'Invalid response 2']); - - const valid = mocked_operation({ validate_responses: [] }); - expect(valid.validate()) - .toEqual([]); + const invalid_group = mocked_operation({validate_group: 'Invalid group', validate_namespace: 'Invalid namespace'}); + expect(invalid_group.validate()) + .toEqual(['Invalid group']); + + const invalid_namespace = mocked_operation({validate_group: undefined, validate_namespace: 'Invalid namespace', validate_operationId: 'Invalid operationId'}); + expect(invalid_namespace.validate()) + .toEqual(['Invalid namespace']); + + const typical_invalid = mocked_operation({ + validate_group: undefined, validate_namespace: undefined, + validate_operationId: 'Invalid operationId', validate_description: 'Invalid description', validate_requestBody: 'Invalid requestBody', + validate_responses: ['Invalid response 1', 'Invalid response 2'], + validate_parameters: 'Invalid parameters', validate_path_parameters: 'Invalid path parameters' }); + expect(typical_invalid.validate()) + .toEqual(['Invalid operationId', 'Invalid description', 'Invalid requestBody', + 'Invalid parameters', 'Invalid path parameters', 'Invalid response 1', 'Invalid response 2']); + + const valid = mocked_operation({ validate_responses: [] }); + expect(valid.validate()) + .toEqual([]); }); \ No newline at end of file diff --git a/tools/test/linter/OperationGroup.test.ts b/tools/test/linter/OperationGroup.test.ts index b57cb80dd..4215a06a5 100644 --- a/tools/test/linter/OperationGroup.test.ts +++ b/tools/test/linter/OperationGroup.test.ts @@ -1,91 +1,91 @@ import {mocked_operation_group, operation_group} from "./factories/operation_group"; test('validate_description()', () => { - const invalid_descriptions = operation_group([ - {description: 'This is a description'}, - {description: 'This is a different description'}]); - expect(invalid_descriptions.validate_description()).toEqual({ - file: `namespaces/indices.yaml`, - location: `Operation Group: indices.create`, - message: `2 'indices.create' operations must have identical description property.` - }); + const invalid_descriptions = operation_group([ + {description: 'This is a description'}, + {description: 'This is a different description'}]); + expect(invalid_descriptions.validate_description()).toEqual({ + file: `namespaces/indices.yaml`, + location: `Operation Group: indices.create`, + message: `2 'indices.create' operations must have identical description property.` + }); - const valid_descriptions = operation_group([ - {description: 'This is a description'}, - {description: 'This is a description'}]); - expect(valid_descriptions.validate_description()) - .toBeUndefined(); + const valid_descriptions = operation_group([ + {description: 'This is a description'}, + {description: 'This is a description'}]); + expect(valid_descriptions.validate_description()) + .toBeUndefined(); }); test('validate_externalDocs()', () => { - const valid_externalDocs = operation_group([ - {externalDocs: {url: 'https://example.com'}}, - {externalDocs: {url: 'https://example.com'}}]); - expect(valid_externalDocs.validate_externalDocs()) - .toBeUndefined(); + const valid_externalDocs = operation_group([ + {externalDocs: {url: 'https://example.com'}}, + {externalDocs: {url: 'https://example.com'}}]); + expect(valid_externalDocs.validate_externalDocs()) + .toBeUndefined(); - const invalid_externalDocs = operation_group([ - {externalDocs: {url: 'https://example.com'}}, - {externalDocs: {url: 'https://example.com'}}, - {}]); - expect(invalid_externalDocs.validate_externalDocs()) - .toEqual(invalid_externalDocs.error(`3 'indices.create' operations must have identical externalDocs property.`)); + const invalid_externalDocs = operation_group([ + {externalDocs: {url: 'https://example.com'}}, + {externalDocs: {url: 'https://example.com'}}, + {}]); + expect(invalid_externalDocs.validate_externalDocs()) + .toEqual(invalid_externalDocs.error(`3 'indices.create' operations must have identical externalDocs property.`)); }); test('validate_requestBody()', () => { - const valid_requestBodies = operation_group([ - {requestBody: {$ref: '#/components/requestBodies/indices.create'}}, - {requestBody: {$ref: '#/components/requestBodies/indices.create'}}]); - expect(valid_requestBodies.validate_requestBody()) - .toBeUndefined(); + const valid_requestBodies = operation_group([ + {requestBody: {$ref: '#/components/requestBodies/indices.create'}}, + {requestBody: {$ref: '#/components/requestBodies/indices.create'}}]); + expect(valid_requestBodies.validate_requestBody()) + .toBeUndefined(); - const invalid_requestBodies = operation_group([ - {requestBody: {$ref: '#/components/requestBodies/indices.create'}}, - {}]); - expect(invalid_requestBodies.validate_requestBody()) - .toEqual(invalid_requestBodies.error(`2 'indices.create' operations must have identical requestBody property.`)); + const invalid_requestBodies = operation_group([ + {requestBody: {$ref: '#/components/requestBodies/indices.create'}}, + {}]); + expect(invalid_requestBodies.validate_requestBody()) + .toEqual(invalid_requestBodies.error(`2 'indices.create' operations must have identical requestBody property.`)); }); test('validate_responses()', () => { - const valid_responses = operation_group([ - {responses: {'200': {$ref: '#/components/responses/indices.create@200'}, '400': {$ref: '#/components/responses/indices.create@400'}}}, - {responses: {'400': {$ref: '#/components/responses/indices.create@400'}, '200': {$ref: '#/components/responses/indices.create@200'}}}]); - expect(valid_responses.validate_responses()) - .toBeUndefined(); + const valid_responses = operation_group([ + {responses: {'200': {$ref: '#/components/responses/indices.create@200'}, '400': {$ref: '#/components/responses/indices.create@400'}}}, + {responses: {'400': {$ref: '#/components/responses/indices.create@400'}, '200': {$ref: '#/components/responses/indices.create@200'}}}]); + expect(valid_responses.validate_responses()) + .toBeUndefined(); - const invalid_responses = operation_group([ - {responses: {'200': {$ref: '#/components/responses/indices.create@200'}}}, - {responses: {'200': {$ref: '#/components/responses/indices.create@200'}, - '400': {$ref: '#/components/responses/indices.create@400'}}}]); - expect(invalid_responses.validate_responses()) - .toEqual(invalid_responses.error(`2 'indices.create' operations must have an identical set of responses.`)); + const invalid_responses = operation_group([ + {responses: {'200': {$ref: '#/components/responses/indices.create@200'}}}, + {responses: {'200': {$ref: '#/components/responses/indices.create@200'}, + '400': {$ref: '#/components/responses/indices.create@400'}}}]); + expect(invalid_responses.validate_responses()) + .toEqual(invalid_responses.error(`2 'indices.create' operations must have an identical set of responses.`)); }); test('validate_query_parameters()', () => { - const valid_query_parameters = operation_group([ - {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}, {$ref: '#/components/parameters/indices.create::path.param2'}]}, - {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}]}]); - expect(valid_query_parameters.validate_query_parameters()) - .toBeUndefined(); + const valid_query_parameters = operation_group([ + {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}, {$ref: '#/components/parameters/indices.create::path.param2'}]}, + {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}]}]); + expect(valid_query_parameters.validate_query_parameters()) + .toBeUndefined(); - const invalid_query_parameters = operation_group([ - {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}]}, - {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}, {$ref: '#/components/parameters/indices.create::query.param2'}]}]); - expect(invalid_query_parameters.validate_query_parameters()) - .toEqual(invalid_query_parameters.error(`2 'indices.create' operations must have an identical set of query parameters.`)); + const invalid_query_parameters = operation_group([ + {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}]}, + {parameters: [{$ref: '#/components/parameters/indices.create::query.param1'}, {$ref: '#/components/parameters/indices.create::query.param2'}]}]); + expect(invalid_query_parameters.validate_query_parameters()) + .toEqual(invalid_query_parameters.error(`2 'indices.create' operations must have an identical set of query parameters.`)); }); test('validate()', () => { - const ops_errors = mocked_operation_group({validate_description: 'Invalid description'}, [['Invalid operation 1A'], ['Invalid operation 2A', 'Invalid operation 2B']]); - expect(ops_errors.validate()) - .toEqual(['Invalid operation 1A', 'Invalid operation 2A', 'Invalid operation 2B']); + const ops_errors = mocked_operation_group({validate_description: 'Invalid description'}, [['Invalid operation 1A'], ['Invalid operation 2A', 'Invalid operation 2B']]); + expect(ops_errors.validate()) + .toEqual(['Invalid operation 1A', 'Invalid operation 2A', 'Invalid operation 2B']); - const other_errors = mocked_operation_group({validate_description: 'Invalid description', validate_externalDocs: 'Invalid externalDocs', validate_requestBody: 'Invalid requestBody', validate_responses: 'Invalid responses', validate_query_parameters: 'Invalid query parameters'}); - expect(other_errors.validate()) - .toEqual(['Invalid description', 'Invalid externalDocs', 'Invalid requestBody', 'Invalid responses', 'Invalid query parameters']); + const other_errors = mocked_operation_group({validate_description: 'Invalid description', validate_externalDocs: 'Invalid externalDocs', validate_requestBody: 'Invalid requestBody', validate_responses: 'Invalid responses', validate_query_parameters: 'Invalid query parameters'}); + expect(other_errors.validate()) + .toEqual(['Invalid description', 'Invalid externalDocs', 'Invalid requestBody', 'Invalid responses', 'Invalid query parameters']); - const no_errors = mocked_operation_group({}); - expect(no_errors.validate()) - .toEqual([]); + const no_errors = mocked_operation_group({}); + expect(no_errors.validate()) + .toEqual([]); }); \ No newline at end of file diff --git a/tools/test/linter/PathRefsValidator.test.ts b/tools/test/linter/PathRefsValidator.test.ts index 527782e3e..28c332075 100644 --- a/tools/test/linter/PathRefsValidator.test.ts +++ b/tools/test/linter/PathRefsValidator.test.ts @@ -3,29 +3,29 @@ import RootFile from "../../linter/components/RootFile"; import NamespacesFolder from "../../linter/components/NamespacesFolder"; test('validate()', () => { - const root_folder = './test/linter/fixtures/path_refs_validator'; - const root_file = new RootFile(`${root_folder}/opensearch-openapi.yaml`); - const namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`); - const validator = new PathRefsValidator(root_file, namespaces_folder); - expect(validator.validate()).toEqual([ - { - file: "opensearch-openapi.yaml", - location: "Path: /{index}", - message: "Unresolved path reference: Path /{index} does not exist in namespace file namespaces/indices.yaml." - }, - { - file: "opensearch-openapi.yaml", - location: "Paths: /_cluster/health , /_cluster/{id}", - message: "Unresolved path reference: Namespace file namespaces/cluster.yaml does not exist." - }, - { - file: "namespaces/indices.yaml", - location: "Path: /{index}/_aliases", - message: "Unreferenced path: Path /{index}/_aliases is not referenced in the root file." - }, - { - file: "namespaces/missing.yaml", - message: "Unreferenced paths: No paths are referenced in the root file." - } - ]); + const root_folder = './test/linter/fixtures/path_refs_validator'; + const root_file = new RootFile(`${root_folder}/opensearch-openapi.yaml`); + const namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`); + const validator = new PathRefsValidator(root_file, namespaces_folder); + expect(validator.validate()).toEqual([ + { + file: "opensearch-openapi.yaml", + location: "Path: /{index}", + message: "Unresolved path reference: Path /{index} does not exist in namespace file namespaces/indices.yaml." + }, + { + file: "opensearch-openapi.yaml", + location: "Paths: /_cluster/health , /_cluster/{id}", + message: "Unresolved path reference: Namespace file namespaces/cluster.yaml does not exist." + }, + { + file: "namespaces/indices.yaml", + location: "Path: /{index}/_aliases", + message: "Unreferenced path: Path /{index}/_aliases is not referenced in the root file." + }, + { + file: "namespaces/missing.yaml", + message: "Unreferenced paths: No paths are referenced in the root file." + } + ]); }); \ No newline at end of file diff --git a/tools/test/linter/RootFile.test.ts b/tools/test/linter/RootFile.test.ts index e27923f69..1d98e94f0 100644 --- a/tools/test/linter/RootFile.test.ts +++ b/tools/test/linter/RootFile.test.ts @@ -1,27 +1,27 @@ import RootFile from "../../linter/components/RootFile"; test('validate()', () => { - const validator = new RootFile('./test/linter/fixtures/root.yaml'); - expect(validator.validate()).toEqual([ - { - file: "root.yaml", - location: "Path: /", - message: "Every path must be a reference object to a path in a namespace file." - }, - { - file: "root.yaml", - location: "Path: /{index}", - message: "Every path must be a reference object to a path in a namespace file." - }, - { - file: "root.yaml", - location: "#/components/parameters/_global::query.pretty", - message: "Parameters in root file must be in the format '_global::{in}.{name}'. Expected '_global::query.beautify'." - }, - { - file: "root.yaml", - location: "#/components/parameters/_global::query.human", - message: "Parameters in root file must have 'x-global' extension set to true." - } - ]); + const validator = new RootFile('./test/linter/fixtures/root.yaml'); + expect(validator.validate()).toEqual([ + { + file: "root.yaml", + location: "Path: /", + message: "Every path must be a reference object to a path in a namespace file." + }, + { + file: "root.yaml", + location: "Path: /{index}", + message: "Every path must be a reference object to a path in a namespace file." + }, + { + file: "root.yaml", + location: "#/components/parameters/_global::query.pretty", + message: "Parameters in root file must be in the format '_global::{in}.{name}'. Expected '_global::query.beautify'." + }, + { + file: "root.yaml", + location: "#/components/parameters/_global::query.human", + message: "Parameters in root file must have 'x-global' extension set to true." + } + ]); }); \ No newline at end of file diff --git a/tools/test/linter/Schema.test.ts b/tools/test/linter/Schema.test.ts index 3eec71a81..2ca262f52 100644 --- a/tools/test/linter/Schema.test.ts +++ b/tools/test/linter/Schema.test.ts @@ -2,14 +2,14 @@ import {schema} from "./factories/schema"; test('validate_name()', () => { - expect(schema('invalid_name').validate_name()).toEqual({ - file: "_common.yaml", - location: "#/components/schemas/invalid_name", - message: "Invalid schema name 'invalid_name'. Only alphanumeric characters are allowed." - }); - expect(schema('Invalid.Name').validate_name()).toBeDefined(); + expect(schema('invalid_name').validate_name()).toEqual({ + file: "_common.yaml", + location: "#/components/schemas/invalid_name", + message: "Invalid schema name 'invalid_name'. Only alphanumeric characters are allowed." + }); + expect(schema('Invalid.Name').validate_name()).toBeDefined(); - expect(schema('ValidName1').validate_name()).toBeUndefined(); - expect(schema('Valid1Name').validate_name()).toBeUndefined(); - expect(schema('uint').validate_name()).toBeUndefined(); + expect(schema('ValidName1').validate_name()).toBeUndefined(); + expect(schema('Valid1Name').validate_name()).toBeUndefined(); + expect(schema('uint').validate_name()).toBeUndefined(); }); \ No newline at end of file diff --git a/tools/test/linter/SchemaFile.test.ts b/tools/test/linter/SchemaFile.test.ts index 06eb0ffbe..4e2081f42 100644 --- a/tools/test/linter/SchemaFile.test.ts +++ b/tools/test/linter/SchemaFile.test.ts @@ -1,31 +1,31 @@ import {mocked_schema_file, schema_file} from "./factories/schema_file"; test('validate_category()', () => { - const validator = schema_file('_common.empty.yaml'); + const validator = schema_file('_common.empty.yaml'); - expect(validator.validate_category()).toBeUndefined(); - expect(validator.validate_category('_common')).toBeUndefined(); - expect(validator.validate_category('cat._common')).toBeUndefined(); - expect(validator.validate_category('cat.valid_name')).toBeUndefined(); - expect(validator.validate_category('cat._invalid_name')).toEqual({ - file: "schemas/_common.empty.yaml", - location: "File Name", - message: "Invalid category name 'cat._invalid_name'. '_invalid_name' does not match regex: /^[a-z]+[a-z_]*[a-z]+$/." - }); - expect(validator.validate_category('invalid_regex')).toEqual({ - file: "schemas/_common.empty.yaml", - location: "File Name", - message: "Invalid category name 'invalid_regex'. Must match regex: /^[a-z_]+\\.[a-z_]+$/." - }); + expect(validator.validate_category()).toBeUndefined(); + expect(validator.validate_category('_common')).toBeUndefined(); + expect(validator.validate_category('cat._common')).toBeUndefined(); + expect(validator.validate_category('cat.valid_name')).toBeUndefined(); + expect(validator.validate_category('cat._invalid_name')).toEqual({ + file: "schemas/_common.empty.yaml", + location: "File Name", + message: "Invalid category name 'cat._invalid_name'. '_invalid_name' does not match regex: /^[a-z]+[a-z_]*[a-z]+$/." + }); + expect(validator.validate_category('invalid_regex')).toEqual({ + file: "schemas/_common.empty.yaml", + location: "File Name", + message: "Invalid category name 'invalid_regex'. Must match regex: /^[a-z_]+\\.[a-z_]+$/." + }); }); test('validate()', () => { - const invalid_category = mocked_schema_file({returned_values: { validate_category: 'Invalid Category'}, schema_errors: [['Invalid Schema']]}); - expect(invalid_category.validate()).toEqual(['Invalid Category']); + const invalid_category = mocked_schema_file({returned_values: { validate_category: 'Invalid Category'}, schema_errors: [['Invalid Schema']]}); + expect(invalid_category.validate()).toEqual(['Invalid Category']); - const invalid_schema = mocked_schema_file({schema_errors: [['Error 1', 'Error 2']]}); - expect(invalid_schema.validate()).toEqual(['Error 1', 'Error 2']); + const invalid_schema = mocked_schema_file({schema_errors: [['Error 1', 'Error 2']]}); + expect(invalid_schema.validate()).toEqual(['Error 1', 'Error 2']); - const valid = mocked_schema_file({schema_errors: [[]]}); - expect(valid.validate()).toEqual([]); + const valid = mocked_schema_file({schema_errors: [[]]}); + expect(valid.validate()).toEqual([]); }); \ No newline at end of file diff --git a/tools/test/linter/SchemaRefsValidator.test.ts b/tools/test/linter/SchemaRefsValidator.test.ts index 048a28fab..d1b7df909 100644 --- a/tools/test/linter/SchemaRefsValidator.test.ts +++ b/tools/test/linter/SchemaRefsValidator.test.ts @@ -3,28 +3,28 @@ import NamespacesFolder from "../../linter/components/NamespacesFolder"; import SchemaRefsValidator from "../../linter/SchemaRefsValidator"; test('validate()', () => { - const root_folder = './test/linter/fixtures/schema_refs_validator'; - const namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`); - const schemas_folder = new SchemasFolder(`${root_folder}/schemas`); - const validator = new SchemaRefsValidator(namespaces_folder, schemas_folder); - expect(validator.validate()).toEqual([ - { - file: "schemas/animals.yaml", - location: "#/components/schemas/Crab", - message: "Unresolved schema reference: Schema Crab is referenced but does not exist." - }, - { - file: "namespaces/", - message: "Unresolved schema reference: Schema file schemas/vehicles.yaml is referenced but does not exist." - }, - { - file: "schemas/animals.yaml", - location: "#/components/schemas/Goat", - message: "Unreferenced schema: Schema Goat is not referenced anywhere." - }, - { - file: "schemas/others.yaml", - message: "Unreferenced schema: Schema file schemas/others.yaml is not referenced anywhere." - } - ]); + const root_folder = './test/linter/fixtures/schema_refs_validator'; + const namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`); + const schemas_folder = new SchemasFolder(`${root_folder}/schemas`); + const validator = new SchemaRefsValidator(namespaces_folder, schemas_folder); + expect(validator.validate()).toEqual([ + { + file: "schemas/animals.yaml", + location: "#/components/schemas/Crab", + message: "Unresolved schema reference: Schema Crab is referenced but does not exist." + }, + { + file: "namespaces/", + message: "Unresolved schema reference: Schema file schemas/vehicles.yaml is referenced but does not exist." + }, + { + file: "schemas/animals.yaml", + location: "#/components/schemas/Goat", + message: "Unreferenced schema: Schema Goat is not referenced anywhere." + }, + { + file: "schemas/others.yaml", + message: "Unreferenced schema: Schema file schemas/others.yaml is not referenced anywhere." + } + ]); }); \ No newline at end of file diff --git a/tools/test/linter/SpecValidator.test.ts b/tools/test/linter/SpecValidator.test.ts index 0c30c867b..d995fab0d 100644 --- a/tools/test/linter/SpecValidator.test.ts +++ b/tools/test/linter/SpecValidator.test.ts @@ -1,24 +1,24 @@ import SpecValidator from "../../linter/SpecValidator"; test('validate()', () => { - const validator = new SpecValidator('./test/linter/fixtures/empty'); - expect(validator.validate()).toEqual([]); + const validator = new SpecValidator('./test/linter/fixtures/empty'); + expect(validator.validate()).toEqual([]); - validator.namespaces_folder.validate = jest.fn().mockReturnValue([{file: 'namespaces/', message: 'namespace error'},]); - validator.schemas_folder.validate = jest.fn().mockReturnValue([{file: 'schemas/', message: 'schema error'},]); - validator.path_refs_validator.validate = jest.fn().mockReturnValue([{file: 'path_refs', message: 'path refs error'},]); - validator.schema_refs_validator.validate = jest.fn().mockReturnValue([{file: 'schema_refs', message: 'schema refs error'},]); + validator.namespaces_folder.validate = jest.fn().mockReturnValue([{file: 'namespaces/', message: 'namespace error'},]); + validator.schemas_folder.validate = jest.fn().mockReturnValue([{file: 'schemas/', message: 'schema error'},]); + validator.path_refs_validator.validate = jest.fn().mockReturnValue([{file: 'path_refs', message: 'path refs error'},]); + validator.schema_refs_validator.validate = jest.fn().mockReturnValue([{file: 'schema_refs', message: 'schema refs error'},]); - expect(validator.validate()).toEqual([ - {file: 'namespaces/', message: 'namespace error'}, - {file: 'schemas/', message: 'schema error'}, - ]); + expect(validator.validate()).toEqual([ + {file: 'namespaces/', message: 'namespace error'}, + {file: 'schemas/', message: 'schema error'}, + ]); - validator.namespaces_folder.validate = jest.fn().mockReturnValue([]); - validator.schemas_folder.validate = jest.fn().mockReturnValue([]); + validator.namespaces_folder.validate = jest.fn().mockReturnValue([]); + validator.schemas_folder.validate = jest.fn().mockReturnValue([]); - expect(validator.validate()).toEqual([ - {file: 'path_refs', message: 'path refs error'}, - {file: 'schema_refs', message: 'schema refs error'}, - ]); + expect(validator.validate()).toEqual([ + {file: 'path_refs', message: 'path refs error'}, + {file: 'schema_refs', message: 'schema refs error'}, + ]); }); \ No newline at end of file diff --git a/tools/test/linter/factories/namespace_file.ts b/tools/test/linter/factories/namespace_file.ts index 42d3f6287..87737fd21 100644 --- a/tools/test/linter/factories/namespace_file.ts +++ b/tools/test/linter/factories/namespace_file.ts @@ -3,45 +3,45 @@ import {OpenAPIV3} from "openapi-types"; import {mocked_operation_group} from "./operation_group"; export function namespace_file(fixture_file: string): NamespaceFile { - return new NamespaceFile(`./test/linter/fixtures/file_validators/namespaces/${fixture_file}`); + return new NamespaceFile(`./test/linter/fixtures/file_validators/namespaces/${fixture_file}`); } interface MockedReturnedValues { - validate?: string[]; - validate_name?: string | void; - validate_schemas?: string | void; - validate_unresolved_refs?: string[]; - validate_unused_refs?: string[]; - validate_parameter_refs?: string[]; + validate?: string[]; + validate_name?: string | void; + validate_schemas?: string | void; + validate_unresolved_refs?: string[]; + validate_unused_refs?: string[]; + validate_parameter_refs?: string[]; } export function mocked_namespace_file(ops: {returned_values?: MockedReturnedValues, spec?: Record, groups_errors?: string[][]}): NamespaceFile { - const ns_file = namespace_file('empty.yaml'); - ns_file.file = 'namespaces/indices.yaml'; - ns_file.namespace = 'indices'; - - if(ops.groups_errors) ns_file._operation_groups = ops.groups_errors.map((errors) => mocked_operation_group({validate: errors})); - if(ops.spec) ns_file._spec = { paths: {}, components: {}, ...ops.spec } as OpenAPIV3.Document; - - if(ops.returned_values) { - if(ops.returned_values.validate) { - ns_file.validate = jest.fn(); - (ns_file.validate as jest.Mock).mockReturnValue(ops.returned_values.validate); - return ns_file; - } - - ns_file.validate_name = jest.fn(); - ns_file.validate_schemas = jest.fn(); - ns_file.validate_unresolved_refs = jest.fn(); - ns_file.validate_unused_refs = jest.fn(); - ns_file.validate_parameter_refs = jest.fn(); - - if(ops.returned_values.validate_name) (ns_file.validate_name as jest.Mock).mockReturnValue(ops.returned_values.validate_name); - if(ops.returned_values.validate_schemas) (ns_file.validate_schemas as jest.Mock).mockReturnValue(ops.returned_values.validate_schemas); - if(ops.returned_values.validate_unresolved_refs) (ns_file.validate_unresolved_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_unresolved_refs); - if(ops.returned_values.validate_unused_refs) (ns_file.validate_unused_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_unused_refs); - if(ops.returned_values.validate_parameter_refs) (ns_file.validate_parameter_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_parameter_refs); + const ns_file = namespace_file('empty.yaml'); + ns_file.file = 'namespaces/indices.yaml'; + ns_file.namespace = 'indices'; + + if(ops.groups_errors) ns_file._operation_groups = ops.groups_errors.map((errors) => mocked_operation_group({validate: errors})); + if(ops.spec) ns_file._spec = { paths: {}, components: {}, ...ops.spec } as OpenAPIV3.Document; + + if(ops.returned_values) { + if(ops.returned_values.validate) { + ns_file.validate = jest.fn(); + (ns_file.validate as jest.Mock).mockReturnValue(ops.returned_values.validate); + return ns_file; } - return ns_file; + ns_file.validate_name = jest.fn(); + ns_file.validate_schemas = jest.fn(); + ns_file.validate_unresolved_refs = jest.fn(); + ns_file.validate_unused_refs = jest.fn(); + ns_file.validate_parameter_refs = jest.fn(); + + if(ops.returned_values.validate_name) (ns_file.validate_name as jest.Mock).mockReturnValue(ops.returned_values.validate_name); + if(ops.returned_values.validate_schemas) (ns_file.validate_schemas as jest.Mock).mockReturnValue(ops.returned_values.validate_schemas); + if(ops.returned_values.validate_unresolved_refs) (ns_file.validate_unresolved_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_unresolved_refs); + if(ops.returned_values.validate_unused_refs) (ns_file.validate_unused_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_unused_refs); + if(ops.returned_values.validate_parameter_refs) (ns_file.validate_parameter_refs as jest.Mock).mockReturnValue(ops.returned_values.validate_parameter_refs); + } + + return ns_file; } \ No newline at end of file diff --git a/tools/test/linter/factories/operation.ts b/tools/test/linter/factories/operation.ts index 3721e0ec4..d74ccddff 100644 --- a/tools/test/linter/factories/operation.ts +++ b/tools/test/linter/factories/operation.ts @@ -2,47 +2,47 @@ import Operation from "../../../linter/components/Operation"; import {OperationSpec} from "../../../types"; export function operation(spec: Record, file_name = 'indices.yaml') { - return new Operation(`namespaces/${file_name}`, '/{index}/something/{abc_xyz}', 'post', spec as OperationSpec); + return new Operation(`namespaces/${file_name}`, '/{index}/something/{abc_xyz}', 'post', spec as OperationSpec); } interface MockedReturnedValues { - validate?: string[]; - validate_group?: string | void; - validate_namespace?: string | void; - validate_operationId?: string | void; - validate_description?: string | void; - validate_requestBody?: string | void; - validate_responses?: string[]; - validate_parameters?: string | void; - validate_path_parameters?: string | void; + validate?: string[]; + validate_group?: string | void; + validate_namespace?: string | void; + validate_operationId?: string | void; + validate_description?: string | void; + validate_requestBody?: string | void; + validate_responses?: string[]; + validate_parameters?: string | void; + validate_path_parameters?: string | void; } export function mocked_operation(returned_values: MockedReturnedValues) { - const op = new Operation('', '', '', {} as OperationSpec); + const op = new Operation('', '', '', {} as OperationSpec); - if(returned_values.validate) { - op.validate = jest.fn(); - (op.validate as jest.Mock).mockReturnValue(returned_values.validate) - return op; - } + if(returned_values.validate) { + op.validate = jest.fn(); + (op.validate as jest.Mock).mockReturnValue(returned_values.validate) + return op; + } - op.validate_group = jest.fn(); - op.validate_namespace = jest.fn(); - op.validate_operationId = jest.fn(); - op.validate_description = jest.fn(); - op.validate_requestBody = jest.fn(); - op.validate_responses = jest.fn(); - op.validate_parameters = jest.fn(); - op.validate_path_parameters = jest.fn(); + op.validate_group = jest.fn(); + op.validate_namespace = jest.fn(); + op.validate_operationId = jest.fn(); + op.validate_description = jest.fn(); + op.validate_requestBody = jest.fn(); + op.validate_responses = jest.fn(); + op.validate_parameters = jest.fn(); + op.validate_path_parameters = jest.fn(); - if(returned_values.validate_group) (op.validate_group as jest.Mock).mockReturnValue(returned_values.validate_group); - if(returned_values.validate_namespace) (op.validate_namespace as jest.Mock).mockReturnValue(returned_values.validate_namespace); - if(returned_values.validate_operationId) (op.validate_operationId as jest.Mock).mockReturnValue(returned_values.validate_operationId); - if(returned_values.validate_description) (op.validate_description as jest.Mock).mockReturnValue(returned_values.validate_description); - if(returned_values.validate_requestBody) (op.validate_requestBody as jest.Mock).mockReturnValue(returned_values.validate_requestBody); - if(returned_values.validate_responses) (op.validate_responses as jest.Mock).mockReturnValue(returned_values.validate_responses); - if(returned_values.validate_parameters) (op.validate_parameters as jest.Mock).mockReturnValue(returned_values.validate_parameters); - if(returned_values.validate_path_parameters) (op.validate_path_parameters as jest.Mock).mockReturnValue(returned_values.validate_path_parameters); + if(returned_values.validate_group) (op.validate_group as jest.Mock).mockReturnValue(returned_values.validate_group); + if(returned_values.validate_namespace) (op.validate_namespace as jest.Mock).mockReturnValue(returned_values.validate_namespace); + if(returned_values.validate_operationId) (op.validate_operationId as jest.Mock).mockReturnValue(returned_values.validate_operationId); + if(returned_values.validate_description) (op.validate_description as jest.Mock).mockReturnValue(returned_values.validate_description); + if(returned_values.validate_requestBody) (op.validate_requestBody as jest.Mock).mockReturnValue(returned_values.validate_requestBody); + if(returned_values.validate_responses) (op.validate_responses as jest.Mock).mockReturnValue(returned_values.validate_responses); + if(returned_values.validate_parameters) (op.validate_parameters as jest.Mock).mockReturnValue(returned_values.validate_parameters); + if(returned_values.validate_path_parameters) (op.validate_path_parameters as jest.Mock).mockReturnValue(returned_values.validate_path_parameters); - return op; + return op; } diff --git a/tools/test/linter/factories/operation_group.ts b/tools/test/linter/factories/operation_group.ts index e19cc3080..d8aa55597 100644 --- a/tools/test/linter/factories/operation_group.ts +++ b/tools/test/linter/factories/operation_group.ts @@ -2,42 +2,42 @@ import OperationGroup from "../../../linter/components/OperationGroup"; import {operation, mocked_operation} from "./operation"; export function operation_group (operation_specs: Record[]): OperationGroup { - const operations = operation_specs.map((spec) => { - return operation({'x-operation-group': 'indices.create', ...spec}); - }); - return new OperationGroup(`namespaces/indices.yaml`, 'indices.create', operations); + const operations = operation_specs.map((spec) => { + return operation({'x-operation-group': 'indices.create', ...spec}); + }); + return new OperationGroup(`namespaces/indices.yaml`, 'indices.create', operations); } interface MockedReturnedValues { - validate?: string[]; - validate_description?: string | void; - validate_externalDocs?: string | void; - validate_requestBody?: string | void; - validate_responses?: string | void; - validate_query_parameters?: string | void; + validate?: string[]; + validate_description?: string | void; + validate_externalDocs?: string | void; + validate_requestBody?: string | void; + validate_responses?: string | void; + validate_query_parameters?: string | void; } export function mocked_operation_group(returned_values: MockedReturnedValues, ops_errors: string[][] = []) { - const ops = ops_errors.map((errors) => mocked_operation({validate: errors})); - const op_group = new OperationGroup('', '', ops); + const ops = ops_errors.map((errors) => mocked_operation({validate: errors})); + const op_group = new OperationGroup('', '', ops); - if(returned_values.validate) { - op_group.validate = jest.fn(); - (op_group.validate as jest.Mock).mockReturnValue(returned_values.validate) - return op_group; - } + if(returned_values.validate) { + op_group.validate = jest.fn(); + (op_group.validate as jest.Mock).mockReturnValue(returned_values.validate) + return op_group; + } - op_group.validate_description = jest.fn(); - op_group.validate_externalDocs = jest.fn(); - op_group.validate_requestBody = jest.fn(); - op_group.validate_responses = jest.fn(); - op_group.validate_query_parameters = jest.fn(); + op_group.validate_description = jest.fn(); + op_group.validate_externalDocs = jest.fn(); + op_group.validate_requestBody = jest.fn(); + op_group.validate_responses = jest.fn(); + op_group.validate_query_parameters = jest.fn(); - if(returned_values.validate_description) (op_group.validate_description as jest.Mock).mockReturnValue(returned_values.validate_description); - if(returned_values.validate_externalDocs) (op_group.validate_externalDocs as jest.Mock).mockReturnValue(returned_values.validate_externalDocs); - if(returned_values.validate_requestBody) (op_group.validate_requestBody as jest.Mock).mockReturnValue(returned_values.validate_requestBody); - if(returned_values.validate_responses) (op_group.validate_responses as jest.Mock).mockReturnValue(returned_values.validate_responses); - if(returned_values.validate_query_parameters) (op_group.validate_query_parameters as jest.Mock).mockReturnValue(returned_values.validate_query_parameters); + if(returned_values.validate_description) (op_group.validate_description as jest.Mock).mockReturnValue(returned_values.validate_description); + if(returned_values.validate_externalDocs) (op_group.validate_externalDocs as jest.Mock).mockReturnValue(returned_values.validate_externalDocs); + if(returned_values.validate_requestBody) (op_group.validate_requestBody as jest.Mock).mockReturnValue(returned_values.validate_requestBody); + if(returned_values.validate_responses) (op_group.validate_responses as jest.Mock).mockReturnValue(returned_values.validate_responses); + if(returned_values.validate_query_parameters) (op_group.validate_query_parameters as jest.Mock).mockReturnValue(returned_values.validate_query_parameters); - return op_group; + return op_group; } \ No newline at end of file diff --git a/tools/test/linter/factories/schema.ts b/tools/test/linter/factories/schema.ts index c6775da5f..2bdfa1f59 100644 --- a/tools/test/linter/factories/schema.ts +++ b/tools/test/linter/factories/schema.ts @@ -2,26 +2,26 @@ import Schema from "../../../linter/components/Schema"; import {OpenAPIV3} from "openapi-types"; export function schema(name: string, spec: Record = {}): Schema { - return new Schema(`_common.yaml`, name, spec as OpenAPIV3.SchemaObject); + return new Schema(`_common.yaml`, name, spec as OpenAPIV3.SchemaObject); } interface MockedReturnedValues { - validate?: string[]; - validate_name?: string | void; + validate?: string[]; + validate_name?: string | void; } export function mocked_schema(returned_values: MockedReturnedValues) { - const schema = new Schema(`_common.yaml`, 'Schema', {} as OpenAPIV3.SchemaObject); + const schema = new Schema(`_common.yaml`, 'Schema', {} as OpenAPIV3.SchemaObject); - if(returned_values.validate) { - schema.validate = jest.fn(); - (schema.validate as jest.Mock).mockReturnValue(returned_values.validate); - return schema; - } + if(returned_values.validate) { + schema.validate = jest.fn(); + (schema.validate as jest.Mock).mockReturnValue(returned_values.validate); + return schema; + } - schema.validate_name = jest.fn(); + schema.validate_name = jest.fn(); - if(returned_values.validate_name) (schema.validate_name as jest.Mock).mockReturnValue(returned_values.validate_name); + if(returned_values.validate_name) (schema.validate_name as jest.Mock).mockReturnValue(returned_values.validate_name); - return schema; + return schema; } \ No newline at end of file diff --git a/tools/test/linter/factories/schema_file.ts b/tools/test/linter/factories/schema_file.ts index 3dfeebd16..489f76e0e 100644 --- a/tools/test/linter/factories/schema_file.ts +++ b/tools/test/linter/factories/schema_file.ts @@ -2,29 +2,29 @@ import {mocked_schema, schema} from "./schema"; import SchemaFile from "../../../linter/components/SchemaFile"; export function schema_file(fixture:string): SchemaFile { - return new SchemaFile(`./test/linter/fixtures/file_validators/schemas/${fixture}`); + return new SchemaFile(`./test/linter/fixtures/file_validators/schemas/${fixture}`); } interface MockedReturnedValues { - validate?: string[]; - validate_category?: string | void; + validate?: string[]; + validate_category?: string | void; } export function mocked_schema_file(ops: {returned_values?: MockedReturnedValues, schema_errors?: string[][]}): SchemaFile { - const validator = schema_file('_common.empty.yaml'); - if(ops.schema_errors) validator._schemas = ops.schema_errors.map((errors) => mocked_schema({validate: errors})); + const validator = schema_file('_common.empty.yaml'); + if(ops.schema_errors) validator._schemas = ops.schema_errors.map((errors) => mocked_schema({validate: errors})); - if(ops.returned_values) { - if(ops.returned_values.validate) { - validator.validate = jest.fn(); - (validator.validate as jest.Mock).mockReturnValue(ops.returned_values.validate); - return validator; - } + if(ops.returned_values) { + if(ops.returned_values.validate) { + validator.validate = jest.fn(); + (validator.validate as jest.Mock).mockReturnValue(ops.returned_values.validate); + return validator; + } - validator.validate_category = jest.fn(); + validator.validate_category = jest.fn(); - if(ops.returned_values.validate_category) (validator.validate_category as jest.Mock).mockReturnValue(ops.returned_values.validate_category); - } + if(ops.returned_values.validate_category) (validator.validate_category as jest.Mock).mockReturnValue(ops.returned_values.validate_category); + } - return validator; + return validator; } \ No newline at end of file diff --git a/tools/test/merger/OpenApiMerger.test.ts b/tools/test/merger/OpenApiMerger.test.ts index 32aec55e8..5c1c01597 100644 --- a/tools/test/merger/OpenApiMerger.test.ts +++ b/tools/test/merger/OpenApiMerger.test.ts @@ -4,9 +4,9 @@ import yaml from 'yaml'; test('merge()', async () => { - const merger = new OpenApiMerger('./test/merger/fixtures/spec/opensearch-openapi.yaml'); - merger.merge('./test/merger/opensearch-openapi.yaml'); - expect(fs.readFileSync('./test/merger/fixtures/expected.yaml', 'utf8')) - .toEqual(fs.readFileSync('./test/merger/opensearch-openapi.yaml', 'utf8')); - fs.unlinkSync('./test/merger/opensearch-openapi.yaml'); + const merger = new OpenApiMerger('./test/merger/fixtures/spec/opensearch-openapi.yaml'); + merger.merge('./test/merger/opensearch-openapi.yaml'); + expect(fs.readFileSync('./test/merger/fixtures/expected.yaml', 'utf8')) + .toEqual(fs.readFileSync('./test/merger/opensearch-openapi.yaml', 'utf8')); + fs.unlinkSync('./test/merger/opensearch-openapi.yaml'); }); \ No newline at end of file diff --git a/tools/types.ts b/tools/types.ts index dfd485de8..fa4010d7f 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -1,28 +1,28 @@ import {OpenAPIV3} from "openapi-types"; export interface OperationSpec extends OpenAPIV3.OperationObject { - 'x-operation-group': string; - 'x-version-added': string; - 'x-version-removed'?: string; - 'x-version-deprecated'?: string; - 'x-deprecation-message'?: string; - 'x-ignorable'?: boolean; + 'x-operation-group': string; + 'x-version-added': string; + 'x-version-removed'?: string; + 'x-version-deprecated'?: string; + 'x-deprecation-message'?: string; + 'x-ignorable'?: boolean; - parameters?: OpenAPIV3.ReferenceObject[]; - requestBody?: OpenAPIV3.ReferenceObject; - responses: { [code: string]: OpenAPIV3.ReferenceObject }; + parameters?: OpenAPIV3.ReferenceObject[]; + requestBody?: OpenAPIV3.ReferenceObject; + responses: { [code: string]: OpenAPIV3.ReferenceObject }; } export interface ParameterSpec extends OpenAPIV3.ParameterObject { - schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; - 'x-data-type'?: string; - 'x-version-deprecated'?: string; - 'x-deprecation-message'?: string; - 'x-global'?: boolean; + schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; + 'x-data-type'?: string; + 'x-version-deprecated'?: string; + 'x-deprecation-message'?: string; + 'x-global'?: boolean; } export interface ValidationError { - file: string; - location?: string; - message: string; + file: string; + location?: string; + message: string; } \ No newline at end of file