From 3ed6aaff0ce51af3aad00fe57c34d1a7056bd6d1 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Tue, 23 Apr 2024 12:46:41 -0400 Subject: [PATCH] Fix obvious lints. (#265) * Fix cosmetic autoformat lints. * Fixed @typescript-eslint/no-unused-vars. * Fixed eqeqeq. * Fixed @typescript-eslint/consistent-type-imports. * Fixed no-useless-return. * Fixed @typescript-eslint/array-type. * Rebased with changes on main. Signed-off-by: dblock --- tools/eslint.config.mjs | 33 +--- tools/helpers.ts | 66 +++---- tools/linter/PathRefsValidator.ts | 108 ++++++----- tools/linter/SchemaRefsValidator.ts | 140 +++++++------- tools/linter/SpecValidator.ts | 42 ++-- tools/linter/components/NamespaceFile.ts | 118 ++++++----- tools/linter/components/NamespacesFolder.ts | 33 ++-- tools/linter/components/Operation.ts | 145 +++++++------- tools/linter/components/OperationGroup.ts | 77 ++++---- tools/linter/components/RootFile.ts | 39 ++-- tools/linter/components/Schema.ts | 29 ++- tools/linter/components/SchemaFile.ts | 52 +++-- tools/linter/components/SchemasFolder.ts | 16 +- tools/linter/components/base/FileValidator.ts | 55 +++--- .../linter/components/base/FolderValidator.ts | 36 ++-- tools/linter/components/base/ValidatorBase.ts | 22 +-- tools/linter/lint.ts | 25 ++- tools/merger/OpenApiMerger.ts | 178 +++++++++-------- tools/merger/OpenDistro.ts | 28 +-- tools/merger/SupersededOpsGenerator.ts | 67 ++++--- tools/merger/merge.ts | 7 +- tools/test/linter/NamespaceFile.test.ts | 120 ++++++------ tools/test/linter/NamespacesFolder.test.ts | 46 ++--- tools/test/linter/Operation.test.ts | 183 ++++++++++-------- tools/test/linter/OperationGroup.test.ts | 101 +++++----- tools/test/linter/PathRefsValidator.test.ts | 40 ++-- tools/test/linter/RootFile.test.ts | 28 +-- tools/test/linter/Schema.test.ts | 19 +- tools/test/linter/SchemaFile.test.ts | 40 ++-- tools/test/linter/SchemaRefsValidator.test.ts | 38 ++-- tools/test/linter/SpecValidator.test.ts | 32 +-- tools/test/linter/factories/namespace_file.ts | 68 +++---- tools/test/linter/factories/operation.ts | 68 +++---- .../test/linter/factories/operation_group.ts | 58 +++--- tools/test/linter/factories/schema.ts | 30 +-- tools/test/linter/factories/schema_file.ts | 34 ++-- tools/test/merger/OpenApiMerger.test.ts | 16 +- tools/types.ts | 40 ++-- 38 files changed, 1122 insertions(+), 1155 deletions(-) diff --git a/tools/eslint.config.mjs b/tools/eslint.config.mjs index 7f14d56b9..a5c9d3fd8 100644 --- a/tools/eslint.config.mjs +++ b/tools/eslint.config.mjs @@ -13,22 +13,12 @@ export default [ ...compat.extends('standard-with-typescript'), { files: ['**/*.{js,ts}'], - ignores: [ - '**/eslint.config.mjs' - ], + // to auto-fix disable all rules except the one you want to fix with '@rule': 'warn', then run `npm run lint -- --fix` rules: { - '@typescript-eslint/array-type': 'warn', - '@typescript-eslint/block-spacing': 'warn', - '@typescript-eslint/comma-dangle': 'warn', - '@typescript-eslint/comma-spacing': 'warn', '@typescript-eslint/consistent-indexed-object-style': 'warn', '@typescript-eslint/consistent-type-assertions': 'warn', - '@typescript-eslint/consistent-type-imports': 'warn', '@typescript-eslint/dot-notation': 'warn', '@typescript-eslint/explicit-function-return-type': 'warn', - '@typescript-eslint/keyword-spacing': 'warn', - '@typescript-eslint/lines-between-class-members': 'warn', - '@typescript-eslint/member-delimiter-style': 'warn', '@typescript-eslint/naming-convention': 'warn', '@typescript-eslint/no-confusing-void-expression': 'warn', '@typescript-eslint/no-dynamic-delete': 'warn', @@ -36,32 +26,13 @@ export default [ '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/no-unnecessary-type-assertion': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn', - '@typescript-eslint/no-unused-vars': 'warn', - '@typescript-eslint/object-curly-spacing': 'warn', '@typescript-eslint/prefer-nullish-coalescing': 'warn', - '@typescript-eslint/quotes': 'warn', '@typescript-eslint/require-array-sort-compare': 'warn', - '@typescript-eslint/semi': 'warn', - '@typescript-eslint/space-before-blocks': 'warn', - '@typescript-eslint/space-before-function-paren': 'warn', - '@typescript-eslint/space-infix-ops': 'warn', '@typescript-eslint/strict-boolean-expressions': 'warn', - '@typescript-eslint/type-annotation-spacing': 'warn', - 'array-bracket-spacing': 'warn', 'array-callback-return': 'warn', - curly: 'warn', - 'eol-last': 'warn', - eqeqeq: 'warn', 'new-cap': 'warn', - 'no-multi-spaces': 'warn', - 'no-multiple-empty-lines': 'warn', 'no-return-assign': 'warn', - 'no-useless-return': 'warn', - 'object-curly-newline': 'warn', - 'object-property-newline': 'warn', - 'object-shorthand': 'warn', - 'quote-props': 'warn', - 'space-in-parens': 'warn' + 'object-shorthand': 'warn' } } ] diff --git a/tools/helpers.ts b/tools/helpers.ts index e88e79da6..737c9fd84 100644 --- a/tools/helpers.ts +++ b/tools/helpers.ts @@ -1,47 +1,47 @@ -import fs from "fs"; -import YAML from "yaml"; -import _ from "lodash"; +import fs from 'fs' +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; +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; + 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]); - }); +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; - }); + 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}))); +export function write2file (file_path: string, content: Record): void { + fs.writeFileSync(file_path, quoteRefs(YAML.stringify(removeAnchors(content), { lineWidth: 0, singleQuote: true }))) } -function quoteRefs(str: string): string { +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}'`; + if (line.includes('$ref')) { + const [key, value] = line.split(': ') + if (!value.startsWith("'")) line = `${key}: '${value}'` } return line - }).join('\n'); + }).join('\n') } -function removeAnchors(content: Record): Record { - const replacer = (key: string, value: any) => key === '$anchor' ? undefined : value; - return JSON.parse(JSON.stringify(content, replacer)); -} \ No newline at end of file +function removeAnchors (content: Record): Record { + const replacer = (key: string, value: any) => key === '$anchor' ? undefined : value + return JSON.parse(JSON.stringify(content, replacer)) +} diff --git a/tools/linter/PathRefsValidator.ts b/tools/linter/PathRefsValidator.ts index bca6360a7..e8d464b40 100644 --- a/tools/linter/PathRefsValidator.ts +++ b/tools/linter/PathRefsValidator.ts @@ -1,76 +1,84 @@ -import {ValidationError} from "../types"; -import RootFile from "./components/RootFile"; -import NamespacesFolder from "./components/NamespacesFolder"; +import { type ValidationError } from '../types' +import type RootFile from './components/RootFile' +import type 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() { + #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); + 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() { + #build_available_paths () { for (const file of this.namespaces_folder.files) { - this.available_paths[file.file] = new Set(Object.keys(file.spec().paths || {})); + this.available_paths[file.file] = new Set(Object.keys(file.spec().paths || {})) } } - validate(): ValidationError[] { + validate (): ValidationError[] { return [ ...this.validate_unresolved_refs(), - ...this.validate_unreferenced_paths(), - ]; + ...this.validate_unreferenced_paths() + ] } - validate_unresolved_refs(): ValidationError[] { + 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.`, - }; + 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[]; - }); + 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[] { + 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 { + const referenced = this.referenced_paths[ns_file] + if (!referenced) { + return { file: ns_file, - location: `Path: ${path}`, - message: `Unreferenced path: Path ${path} is not referenced in the root file.`, - }; - }).filter((e) => e) as ValidationError[]; - }); + 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 c4b76c2b6..9dc184a7b 100644 --- a/tools/linter/SchemaRefsValidator.ts +++ b/tools/linter/SchemaRefsValidator.ts @@ -1,100 +1,106 @@ -import NamespacesFolder from "./components/NamespacesFolder"; -import SchemasFolder from "./components/SchemasFolder"; -import {ValidationError} from "../types"; +import type NamespacesFolder from './components/NamespacesFolder' +import type SchemasFolder from './components/SchemasFolder' +import { type 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(); + 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() { + #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); + 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]); - }; + for (const key in obj) { if (typeof obj[key] === 'object') search(obj[key]) } + } - this.namespaces_folder.files.forEach((file) => { search(file.spec().components || {}) }); + this.namespaces_folder.files.forEach((file) => { search(file.spec().components || {}) }) } - #find_refs_in_schemas_folder() { + #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); + 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); + for (const key in obj) { if (typeof obj[key] === 'object') search(obj[key], ref_file) } } - this.schemas_folder.files.forEach((file) => { search(file.spec().components?.schemas || {}, file.file) }); + this.schemas_folder.files.forEach((file) => { search(file.spec().components?.schemas || {}, file.file) }) } - #build_available_schemas() { + #build_available_schemas () { this.schemas_folder.files.forEach((file) => { - this.available_schemas[file.file] = new Set(Object.keys(file.spec().components?.schemas || {})); - }); + this.available_schemas[file.file] = new Set(Object.keys(file.spec().components?.schemas || {})) + }) } - validate(): ValidationError[] { + validate (): ValidationError[] { return [ ...this.validate_unresolved_refs(), - ...this.validate_unreferenced_schemas(), - ]; + ...this.validate_unreferenced_schemas() + ] } - validate_unresolved_refs(): 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.`, - }; + 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.` + } + } 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[]; - }); + 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_unreferenced_schemas(): 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.`, - }; + 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[]; - }); + 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 7d2426790..d7cd3ada5 100644 --- a/tools/linter/SpecValidator.ts +++ b/tools/linter/SpecValidator.ts @@ -1,32 +1,32 @@ -import SchemasFolder from "./components/SchemasFolder"; -import NamespacesFolder from "./components/NamespacesFolder"; -import RootFile from "./components/RootFile"; -import {ValidationError} from "../types"; -import PathRefsValidator from "./PathRefsValidator"; -import SchemaRefsValidator from "./SchemaRefsValidator"; +import SchemasFolder from './components/SchemasFolder' +import NamespacesFolder from './components/NamespacesFolder' +import RootFile from './components/RootFile' +import { type ValidationError } from '../types' +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[] { + validate (): ValidationError[] { const component_errors = [ ...this.root_file.validate(), ...this.namespaces_folder.validate(), - ...this.schemas_folder.validate(), - ]; - if(component_errors.length) return component_errors; + ...this.schemas_folder.validate() + ] + if (component_errors.length) return component_errors return [ ...this.path_refs_validator.validate(), diff --git a/tools/linter/components/NamespaceFile.ts b/tools/linter/components/NamespaceFile.ts index 8498b8578..1a461be9b 100644 --- a/tools/linter/components/NamespaceFile.ts +++ b/tools/linter/components/NamespaceFile.ts @@ -1,103 +1,101 @@ -import {OpenAPIV3} from "openapi-types"; -import {OperationSpec, ValidationError} from "../../types"; -import OperationGroup from "./OperationGroup"; -import _ from "lodash"; -import Operation from "./Operation"; -import {resolve} from "../../helpers" -import FileValidator from "./base/FileValidator"; +import { type OpenAPIV3 } from 'openapi-types' +import { type OperationSpec, type ValidationError } from '../../types' +import OperationGroup from './OperationGroup' +import _ from 'lodash' +import Operation from './Operation' +import { resolve } from '../../helpers' +import FileValidator from './base/FileValidator' -const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']; -const NAME_REGEX = /^[a-z]+[a-z_]*[a-z]+$/; +const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'] +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(), + ...this.validate_parameter_refs() ].filter((e) => e) as ValidationError[] } - operation_groups(): OperationGroup[] { - if(this._operation_groups) return this._operation_groups; + 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 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 new OperationGroup(this.file, group, ops) + }) } - refs(): Set { - if(this._refs) return this._refs; - this._refs = new Set(); + 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); }); + 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; + 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') } } - 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[] { + 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[]; + if (resolve(ref, this.spec()) === undefined) return this.error(`Unresolved reference: ${ref}`, ref) + }).filter((e) => e) as ValidationError[] } - validate_unused_refs(): 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}`); + if (!this.refs().has(`#/components/${type}/${name}`)) { return this.error(`Unused ${type} component: ${name}`, `#/components/${type}/${name}`) } }) - }).filter((e) => e) as ValidationError[]; + }).filter((e) => e) as ValidationError[] } - validate_parameter_refs(): ValidationError[] { + validate_parameter_refs (): ValidationError[] { const parameters = this.spec().components?.parameters as Record - if(!parameters) return []; + if (!parameters) return [] return _.entries(parameters).map(([name, p]) => { - const group = name.split('::')[0]; - const expected = `${group}::${p.in}.${p.name}`; - if(name !== expected) + 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._]+$/)) + `#/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[]; + `#/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 98bf2a63a..b1be1faa2 100644 --- a/tools/linter/components/NamespacesFolder.ts +++ b/tools/linter/components/NamespacesFolder.ts @@ -1,28 +1,27 @@ -import NamespaceFile from "./NamespaceFile"; -import {ValidationError} from "../../types"; -import FolderValidator from "./base/FolderValidator"; +import NamespaceFile from './NamespaceFile' +import { type 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[] } = {}; + validate_duplicate_paths (): ValidationError[] { + const paths: { [path: string]: string[] } = {} for (const file of this.files) { - if(!file._spec?.paths) continue; + 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]; - }); + 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[]; + 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 13e30e580..e7f5adc60 100644 --- a/tools/linter/components/Operation.ts +++ b/tools/linter/components/Operation.ts @@ -1,120 +1,107 @@ -import {OperationSpec, ValidationError} from "../../types"; -import _ from "lodash"; -import ValidatorBase from "./base/ValidatorBase"; -import {OpenAPIV3} from "openapi-types"; +import { type OperationSpec, type ValidationError } from '../../types' +import _ from 'lodash' +import ValidatorBase from './base/ValidatorBase' -const GROUP_REGEX = /^([a-z]+[a-z_]*[a-z]+\.)?([a-z]+[a-z_]*[a-z]+)$/; +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]; + 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[]; + ...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; + 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.`); + `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.`)]; + 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[]; + 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}'.`) } + }).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[] { + path_params (): string[] { return this.spec.parameters?.map(p => p.$ref?.match(/::path\.(.+)/)?.[1]) .filter((p): p is string => p !== undefined) || [] } - query_params(): string[] { + 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 eae675c33..9b88b9bb8 100644 --- a/tools/linter/components/OperationGroup.ts +++ b/tools/linter/components/OperationGroup.ts @@ -1,63 +1,58 @@ -import Operation from "./Operation"; -import {ValidationError} from "../../types"; -import ValidatorBase from "./base/ValidatorBase"; +import type Operation from './Operation' +import { type 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; + '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 []; + validate (): ValidationError[] { + 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[]; + 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_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_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_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_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.`); + 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 1273f9aed..3cfd9b7ae 100644 --- a/tools/linter/components/RootFile.ts +++ b/tools/linter/components/RootFile.ts @@ -1,34 +1,31 @@ -import {ParameterSpec, ValidationError} from "../../types"; -import FileValidator from "./base/FileValidator"; +import { type ParameterSpec, type 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[] { + validate_file (): ValidationError[] { return [ this.validate_paths(), - this.validate_params(), - ].flat(); + this.validate_params() + ].flat() } - validate_paths(): 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[]; + 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; + 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[]; + 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 ca6a6f325..38ac29017 100644 --- a/tools/linter/components/Schema.ts +++ b/tools/linter/components/Schema.ts @@ -1,25 +1,24 @@ -import ValidatorBase from "./base/ValidatorBase"; -import {OpenAPIV3} from "openapi-types"; -import {ValidationError} from "../../types"; +import ValidatorBase from './base/ValidatorBase' +import { type OpenAPIV3 } from 'openapi-types' +import { type ValidationError } from '../../types' -const NAME_REGEX = /^[A-Za-z0-9]+$/; +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}`); + constructor (error_file: string, name: string, spec: OpenAPIV3.SchemaObject) { + super(error_file, `#/components/schemas/${name}`) this.name = name - this.spec = spec; + 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 e46c30fde..65284b68f 100644 --- a/tools/linter/components/SchemaFile.ts +++ b/tools/linter/components/SchemaFile.ts @@ -1,42 +1,40 @@ -import FileValidator from "./base/FileValidator"; -import {ValidationError} from "../../types"; -import Schema from "./Schema"; -import {OpenAPIV3} from "openapi-types"; +import FileValidator from './base/FileValidator' +import { type ValidationError } from '../../types' +import Schema from './Schema' +import { type OpenAPIV3 } from 'openapi-types' -const CATEGORY_REGEX = /^[a-z_]+\.[a-z_]+$/; -const NAME_REGEX = /^[a-z]+[a-z_]*[a-z]+$/; +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()) - ]; + ] } - schemas(): Schema[] { - if(this._schemas) return this._schemas; + 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); - }); + 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 68eeb1f71..e40ff4f87 100644 --- a/tools/linter/components/SchemasFolder.ts +++ b/tools/linter/components/SchemasFolder.ts @@ -1,13 +1,13 @@ -import SchemaFile from "./SchemaFile"; -import FolderValidator from "./base/FolderValidator"; -import {ValidationError} from "../../types"; +import SchemaFile from './SchemaFile' +import FolderValidator from './base/FolderValidator' +import { type 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 6d4dec8c7..6f5d234aa 100644 --- a/tools/linter/components/base/FileValidator.ts +++ b/tools/linter/components/base/FileValidator.ts @@ -1,45 +1,44 @@ -import ValidatorBase from "./ValidatorBase"; -import {ValidationError} from "../../../types"; -import fs from "fs"; -import YAML from "yaml"; -import {OpenAPIV3} from "openapi-types"; +import ValidatorBase from './ValidatorBase' +import { type ValidationError } from '../../../types' +import fs from 'fs' +import YAML from 'yaml' +import { type 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 { + validate_yaml (): ValidationError | undefined { try { - this.spec(); + this.spec() } catch (e: any) { - return this.error(`Unable to read or parse YAML.`, 'File Content'); + 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 8932a4c7f..2543b6f82 100644 --- a/tools/linter/components/base/FolderValidator.ts +++ b/tools/linter/components/base/FolderValidator.ts @@ -1,30 +1,30 @@ -import fs from "fs"; -import ValidatorBase from "./ValidatorBase"; -import FileValidator from "./FileValidator"; -import {ValidationError} from "../../../types"; +import fs from 'fs' +import ValidatorBase from './ValidatorBase' +import type FileValidator from './FileValidator' +import { type 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; + 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; }); + .map((file) => { return new file_type(`${this.folder_path}/${file}`) as F }) } - validate(): ValidationError[] { + validate (): ValidationError[] { return [ ...this.files.flatMap((file) => file.validate()), - ...this.validate_folder(), - ]; + ...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 f9f1112bc..7b0deaafe 100644 --- a/tools/linter/components/base/ValidatorBase.ts +++ b/tools/linter/components/base/ValidatorBase.ts @@ -1,18 +1,18 @@ -import { ValidationError } from "../../../types"; +import { type 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 e090a28da..af9a25394 100644 --- a/tools/linter/lint.ts +++ b/tools/linter/lint.ts @@ -1,16 +1,15 @@ -import SpecValidator from "./SpecValidator"; +import SpecValidator from './SpecValidator' +const root_folder = process.argv[2] || '../spec' +const validator = new SpecValidator(root_folder) +const errors = validator.validate() -const root_folder = process.argv[2] || '../spec'; -const validator = new SpecValidator(root_folder); -const errors = validator.validate(); - -if(errors.length === 0) { - console.log('No errors found.'); - process.exit(0); -} else { - console.log('Errors found:\n'); - errors.forEach(e => console.error(e)); +if (errors.length === 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); -} \ No newline at end of file + process.exit(1) +} diff --git a/tools/merger/OpenApiMerger.ts b/tools/merger/OpenApiMerger.ts index 4bec290b0..f76731aae 100644 --- a/tools/merger/OpenApiMerger.ts +++ b/tools/merger/OpenApiMerger.ts @@ -1,134 +1,132 @@ -import {OpenAPIV3} from "openapi-types"; -import fs from 'fs'; -import _ from 'lodash'; -import yaml from 'yaml'; -import {write2file} from '../helpers'; -import SupersededOpsGenerator from "./SupersededOpsGenerator"; +import { type OpenAPIV3 } from 'openapi-types' +import fs from 'fs' +import _ from 'lodash' +import yaml from 'yaml' +import { write2file } from '../helpers' +import SupersededOpsGenerator from './SupersededOpsGenerator' // 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}`})); + 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: {}, - }; + schemas: {} + } } - merge(output_path?: string): OpenAPIV3.Document { - this.#merge_schemas(); - this.#merge_namespaces(); - this.#apply_global_params(); - this.#sort_spec_keys(); - this.#generate_replaced_ops(); + merge (output_path?: string): OpenAPIV3.Document { + this.#merge_schemas() + this.#merge_namespaces() + this.#apply_global_params() + this.#sort_spec_keys() + this.#generate_replaced_ops() - if (output_path) write2file(output_path, this.spec); - return this.spec as OpenAPIV3.Document; + 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 { + #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)]; - }); - }); + 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`; + #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']}; - }); + 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]; - }); + 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]); + 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`; + #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; - }); + 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; - }); - }); + 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()}`; + 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]); + 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()); + #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()); + 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()); - }); + this.spec.paths[path] = _.fromPairs(Object.entries(pathItem!).sort()) + }) } - #generate_replaced_ops(): void { - const gen = new SupersededOpsGenerator(this.root_folder); - gen.generate(this.spec); + #generate_replaced_ops (): void { + const gen = new SupersededOpsGenerator(this.root_folder) + gen.generate(this.spec) } -} \ No newline at end of file +} diff --git a/tools/merger/OpenDistro.ts b/tools/merger/OpenDistro.ts index 23e6128b7..4014c3ccc 100644 --- a/tools/merger/OpenDistro.ts +++ b/tools/merger/OpenDistro.ts @@ -1,25 +1,25 @@ -import fs from "fs"; -import YAML from "yaml"; -import {HttpVerb, OperationPath, SupersededOperationMap} from "../types"; -import {write2file} from "../helpers"; +import fs from 'fs' +import YAML from 'yaml' +import { type HttpVerb, type OperationPath, type SupersededOperationMap } from '../types' +import { write2file } from '../helpers' // One-time script to generate _superseded_operations.yaml file for OpenDistro // Keeping this for now in case we need to update the file in the near future. Can be removed after a few months. // TODO: Remove this file in 2025. export default class OpenDistro { - input: Record; - output: SupersededOperationMap = {}; + input: Record + output: SupersededOperationMap = {} - constructor(file_path: string) { - this.input = YAML.parse(fs.readFileSync(file_path, 'utf8')); - this.build_output(); - write2file(file_path, this.output); + constructor (file_path: string) { + this.input = YAML.parse(fs.readFileSync(file_path, 'utf8')) + this.build_output() + write2file(file_path, this.output) } - build_output() { + build_output () { for (const [path, operations] of Object.entries(this.input)) { - const replaced_by = path.replace('_opendistro', '_plugins'); - this.output[path] = {superseded_by: replaced_by, operations}; + const replaced_by = path.replace('_opendistro', '_plugins') + this.output[path] = { superseded_by: replaced_by, operations } } } -} \ No newline at end of file +} diff --git a/tools/merger/SupersededOpsGenerator.ts b/tools/merger/SupersededOpsGenerator.ts index 1820888de..b278786a8 100644 --- a/tools/merger/SupersededOpsGenerator.ts +++ b/tools/merger/SupersededOpsGenerator.ts @@ -1,49 +1,48 @@ -import {OperationSpec, SupersededOperationMap} from "../types"; -import YAML from "yaml"; -import fs from "fs"; -import _ from "lodash"; +import { type OperationSpec, type SupersededOperationMap } from '../types' +import YAML from 'yaml' +import fs from 'fs' +import _ from 'lodash' export default class SupersededOpsGenerator { - superseded_ops: SupersededOperationMap; + superseded_ops: SupersededOperationMap - constructor(root_path: string) { - const file_path = root_path + '/_superseded_operations.yaml'; - this.superseded_ops = YAML.parse(fs.readFileSync(file_path, 'utf8')); + constructor (root_path: string) { + const file_path = root_path + '/_superseded_operations.yaml' + this.superseded_ops = YAML.parse(fs.readFileSync(file_path, 'utf8')) } - generate(spec: Record): void { - for (const [path, {superseded_by, operations}] of _.entries(this.superseded_ops)) { - const regex = this.path_to_regex(superseded_by); - const operation_keys = operations.map(op => op.toLowerCase()); - const superseded_path = this.copy_params(superseded_by, path); - const path_entry = _.entries(spec.paths).find(([path, _]) => regex.test(path)); - if (!path_entry) console.log(`Path not found: ${superseded_by}`); - else spec.paths[superseded_path] = this.path_object(path_entry[1] as any, operation_keys); + generate (spec: Record): void { + for (const [path, { superseded_by, operations }] of _.entries(this.superseded_ops)) { + const regex = this.path_to_regex(superseded_by) + const operation_keys = operations.map(op => op.toLowerCase()) + const superseded_path = this.copy_params(superseded_by, path) + const path_entry = _.entries(spec.paths).find(([path, _]) => regex.test(path)) + if (!path_entry) console.log(`Path not found: ${superseded_by}`) + else spec.paths[superseded_path] = this.path_object(path_entry[1] as any, operation_keys) } } - path_object(obj: Record, keys: string[]): Record { - const cloned_obj = _.cloneDeep(_.pick(obj, keys)); + path_object (obj: Record, keys: string[]): Record { + const cloned_obj = _.cloneDeep(_.pick(obj, keys)) for (const key in cloned_obj) { - const operation = cloned_obj[key] as OperationSpec; - operation.operationId = operation.operationId + '_superseded'; - operation.deprecated = true; - operation['x-ignorable'] = true; + const operation = cloned_obj[key] as OperationSpec + operation.operationId = operation.operationId + '_superseded' + operation.deprecated = true + operation['x-ignorable'] = true } - return cloned_obj; + return cloned_obj } - path_to_regex(path: string): RegExp { - const source = '^' + path.replace(/\{.+?}/g, '\\{.+?\\}').replace(/\//g, '\\/') + '$'; - return new RegExp(source, 'g'); + path_to_regex (path: string): RegExp { + const source = '^' + path.replace(/\{.+?}/g, '\\{.+?\\}').replace(/\//g, '\\/') + '$' + return new RegExp(source, 'g') } - copy_params(source: string, target: string): string { - const target_parts = target.split('/'); - const target_params = target_parts.filter(part => part.startsWith('{')); - const source_params = source.split('/').filter(part => part.startsWith('{')).reverse(); - if (target_params.length !== source_params.length) - throw new Error('Mismatched parameters in source and target paths: ' + source + ' -> ' + target); - return target_parts.map((part) => part.startsWith('{') ? source_params.pop()! : part).join('/'); + copy_params (source: string, target: string): string { + const target_parts = target.split('/') + const target_params = target_parts.filter(part => part.startsWith('{')) + const source_params = source.split('/').filter(part => part.startsWith('{')).reverse() + if (target_params.length !== source_params.length) { throw new Error('Mismatched parameters in source and target paths: ' + source + ' -> ' + target) } + return target_parts.map((part) => part.startsWith('{') ? source_params.pop()! : part).join('/') } -} \ No newline at end of file +} diff --git a/tools/merger/merge.ts b/tools/merger/merge.ts index 60a07ea49..b4531b4b4 100644 --- a/tools/merger/merge.ts +++ b/tools/merger/merge.ts @@ -1,7 +1,6 @@ -import OpenApiMerger from "./OpenApiMerger"; - +import OpenApiMerger from './OpenApiMerger' const root_path: string = process.argv[2] || '../spec/opensearch-openapi.yaml' const output_path: string = process.argv[3] || '../opensearch-openapi.yaml' -const merger = new OpenApiMerger(root_path); -merger.merge(output_path); +const merger = new OpenApiMerger(root_path) +merger.merge(output_path) diff --git a/tools/test/linter/NamespaceFile.test.ts b/tools/test/linter/NamespaceFile.test.ts index 51b5e2cab..3303bbc87 100644 --- a/tools/test/linter/NamespaceFile.test.ts +++ b/tools/test/linter/NamespaceFile.test.ts @@ -1,99 +1,101 @@ -import {mocked_namespace_file, namespace_file} from "./factories/namespace_file"; - +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('_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]+$/.` - }); -}); + 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: {}}}}); + 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` - }); + 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'); + 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/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` + 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'); + 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/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` + 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'); + 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", + 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", + 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([]); -}); \ No newline at end of file + const valid = mocked_namespace_file({ groups_errors: [[], []] }) + expect(valid.validate()).toEqual([]) +}) diff --git a/tools/test/linter/NamespacesFolder.test.ts b/tools/test/linter/NamespacesFolder.test.ts index 64dff9fa5..2ac2ccba6 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"; +import NamespacesFolder from '../../linter/components/NamespacesFolder' test('validate()', () => { - const validator = new NamespacesFolder('./test/linter/fixtures/folder_validators/namespaces'); + const validator = new NamespacesFolder('./test/linter/fixtures/folder_validators/namespaces') expect(validator.validate()).toEqual([ { - file: "namespaces/indices.txt", - location: "File Extension", + 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: 'Missing description property.' }, { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", + 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: 'Path parameters must match the parameters in the path: {id}, {index}.' }, { - file: "namespaces/invalid_spec.yaml", - location: "Operation: GET /{index}/_doc/{id}", + 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/invalid_yaml.yaml', + location: 'File Content', + message: 'Unable to read or parse YAML.' }, { - file: "namespaces/", - location: "Folder", + file: 'namespaces/', + location: 'Folder', message: "Duplicate path '/{index}' found in namespaces: dup_path_a, dup_path_c." }, { - file: "namespaces/", - location: "Folder", + 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 cc385d74b..48173deed 100644 --- a/tools/test/linter/Operation.test.ts +++ b/tools/test/linter/Operation.test.ts @@ -1,153 +1,166 @@ -import {mocked_operation, operation} from "./factories/operation"; +import { mocked_operation, operation } from './factories/operation' test('validate_group()', () => { - const no_group = operation({'x-operation-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`, - }); + location: 'Operation: POST /{index}/something/{abc_xyz}', + message: 'Missing x-operation-group property' + }) - const invalid_group = operation({'x-operation-group': 'indices_'}); + 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]+)$/.`)); + .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'}); + 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]+)$/.`)); + .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'}); + const valid_group = operation({ 'x-operation-group': 'indices.create' }) expect(valid_group.validate_group()) - .toBeUndefined(); -}); + .toBeUndefined() +}) test('validate_namespace()', () => { - const valid_core = operation({'x-operation-group': 'search'}, '_core.yaml'); + const valid_core = operation({ 'x-operation-group': 'search' }, '_core.yaml') expect(valid_core.validate_namespace()) - .toBeUndefined(); + .toBeUndefined() - const valid_non_core = operation({'x-operation-group': 'indices._create'}, 'indices.yaml'); + const valid_non_core = operation({ 'x-operation-group': 'indices._create' }, 'indices.yaml') expect(valid_non_core.validate_namespace()) - .toBeUndefined(); + .toBeUndefined() - const non_omitted_core = operation({'x-operation-group': '_core.search'}, '_core.yaml'); + 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.`)); + .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'); + 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.`)); -}); + .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'}); + const no_id = operation({ 'x-operation-group': 'indices.create' }) expect(no_id.validate_operationId()) - .toEqual(no_id.error(`Missing operationId property.`)); + .toEqual(no_id.error('Missing operationId property.')) - const invalid_id = operation({'x-operation-group': 'indices.create', operationId: 'create_index'}); + 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.`)); + .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'}); + const valid_id = operation({ 'x-operation-group': 'indices.create', operationId: 'indices.create.1' }) expect(valid_id.validate_operationId()) - .toBeUndefined(); -}); + .toBeUndefined() +}) test('validate_description()', () => { - const no_description = operation({'x-operation-group': 'indices.create'}); + const no_description = operation({ 'x-operation-group': 'indices.create' }) expect(no_description.validate_description()) - .toEqual(no_description.error(`Missing description property.`)); + .toEqual(no_description.error('Missing description property.')) - const invalid_description = operation({'x-operation-group': 'indices.create', description: 'This is a description without 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.`)); + .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.'}); + const valid_description = operation({ 'x-operation-group': 'indices.create', description: 'This is a description with a period.' }) expect(valid_description.validate_description()) - .toBeUndefined(); -}); + .toBeUndefined() +}) test('validate_requestBody()', () => { - const no_body = operation({'x-operation-group': 'indices.create'}); + const no_body = operation({ 'x-operation-group': 'indices.create' }) expect(no_body.validate_requestBody()) - .toBeUndefined(); + .toBeUndefined() - const valid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create'}}); + const valid_body = operation({ 'x-operation-group': 'indices.create', requestBody: { $ref: '#/components/requestBodies/indices.create' } }) expect(valid_body.validate_requestBody()) - .toBeUndefined(); + .toBeUndefined() - const invalid_body = operation({'x-operation-group': 'indices.create', requestBody: {$ref: '#/components/requestBodies/indices.create.1'}}); + 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'.`)); -}); + .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'}, - }}); + 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'.`),]); + 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'}}}); + const valid_responses = operation({ 'x-operation-group': 'cat.info', responses: { 200: { $ref: '#/components/responses/cat.info@200' } } }) expect(valid_responses.validate_responses()) - .toEqual([]); -}); + .toEqual([]) +}) test('validate_parameters()', () => { - const no_parameters = operation({'x-operation-group': 'indices.create'}); + const no_parameters = operation({ 'x-operation-group': 'indices.create' }) expect(no_parameters.validate_parameters()) - .toBeUndefined(); + .toBeUndefined() - const valid_parameters = operation({'x-operation-group': 'indices.create', parameters: [{$ref: '#/components/parameters/indices.create::path.request_timeout'}]}); + 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'}, - ]}); + .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}'.`)); -}); + .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'}]}); + 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}.`)); + .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'} ]}); + 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(); -}); + .toBeUndefined() +}) test('validate()', () => { - const invalid_group = mocked_operation({validate_group: 'Invalid group', validate_namespace: 'Invalid namespace'}); + const invalid_group = mocked_operation({ validate_group: 'Invalid group', validate_namespace: 'Invalid namespace' }) expect(invalid_group.validate()) - .toEqual(['Invalid group']); + .toEqual(['Invalid group']) - const invalid_namespace = mocked_operation({validate_group: undefined, validate_namespace: 'Invalid namespace', validate_operationId: 'Invalid operationId'}); + const invalid_namespace = mocked_operation({ validate_group: undefined, validate_namespace: 'Invalid namespace', validate_operationId: 'Invalid operationId' }) expect(invalid_namespace.validate()) - .toEqual(['Invalid namespace']); + .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_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' }); + 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']); + 'Invalid parameters', 'Invalid path parameters', 'Invalid response 1', 'Invalid response 2']) - const valid = mocked_operation({ validate_responses: [] }); + const valid = mocked_operation({ validate_responses: [] }) expect(valid.validate()) - .toEqual([]); -}); \ No newline at end of file + .toEqual([]) +}) diff --git a/tools/test/linter/OperationGroup.test.ts b/tools/test/linter/OperationGroup.test.ts index 4215a06a5..9dcf8ae4d 100644 --- a/tools/test/linter/OperationGroup.test.ts +++ b/tools/test/linter/OperationGroup.test.ts @@ -1,91 +1,94 @@ -import {mocked_operation_group, operation_group} from "./factories/operation_group"; +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'}]); + { 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.` - }); - + 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'}]); + { description: 'This is a description' }, + { description: 'This is a description' }]) expect(valid_descriptions.validate_description()) - .toBeUndefined(); -}); + .toBeUndefined() +}) test('validate_externalDocs()', () => { const valid_externalDocs = operation_group([ - {externalDocs: {url: 'https://example.com'}}, - {externalDocs: {url: 'https://example.com'}}]); + { externalDocs: { url: 'https://example.com' } }, + { externalDocs: { url: 'https://example.com' } }]) expect(valid_externalDocs.validate_externalDocs()) - .toBeUndefined(); + .toBeUndefined() const invalid_externalDocs = operation_group([ - {externalDocs: {url: 'https://example.com'}}, - {externalDocs: {url: 'https://example.com'}}, - {}]); + { 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.`)); -}); + .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'}}]); + { requestBody: { $ref: '#/components/requestBodies/indices.create' } }, + { requestBody: { $ref: '#/components/requestBodies/indices.create' } }]) expect(valid_requestBodies.validate_requestBody()) - .toBeUndefined(); + .toBeUndefined() const invalid_requestBodies = operation_group([ - {requestBody: {$ref: '#/components/requestBodies/indices.create'}}, - {}]); + { requestBody: { $ref: '#/components/requestBodies/indices.create' } }, + {}]) expect(invalid_requestBodies.validate_requestBody()) - .toEqual(invalid_requestBodies.error(`2 'indices.create' operations must have identical requestBody property.`)); -}); + .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'}}}]); + { 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(); + .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'}}}]); + { 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.`)); -}); + .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'}]}]); + { 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(); + .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'}]}]); + { 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.`)); -}); + .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']]); + 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']); + .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'}); + 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']); + .toEqual(['Invalid description', 'Invalid externalDocs', 'Invalid requestBody', 'Invalid responses', 'Invalid query parameters']) - const no_errors = mocked_operation_group({}); + const no_errors = mocked_operation_group({}) expect(no_errors.validate()) - .toEqual([]); -}); \ No newline at end of file + .toEqual([]) +}) diff --git a/tools/test/linter/PathRefsValidator.test.ts b/tools/test/linter/PathRefsValidator.test.ts index 28c332075..eaf8c2826 100644 --- a/tools/test/linter/PathRefsValidator.test.ts +++ b/tools/test/linter/PathRefsValidator.test.ts @@ -1,31 +1,31 @@ -import PathRefsValidator from "../../linter/PathRefsValidator"; -import RootFile from "../../linter/components/RootFile"; -import NamespacesFolder from "../../linter/components/NamespacesFolder"; +import PathRefsValidator from '../../linter/PathRefsValidator' +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); + 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: '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: '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/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." + 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 1d98e94f0..fe365fa89 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"; +import RootFile from '../../linter/components/RootFile' test('validate()', () => { - const validator = new RootFile('./test/linter/fixtures/root.yaml'); + 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: /', + 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: '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", + 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", + 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 2ca262f52..60319b128 100644 --- a/tools/test/linter/Schema.test.ts +++ b/tools/test/linter/Schema.test.ts @@ -1,15 +1,14 @@ -import {schema} from "./factories/schema"; - +import { schema } from './factories/schema' test('validate_name()', () => { expect(schema('invalid_name').validate_name()).toEqual({ - file: "_common.yaml", - location: "#/components/schemas/invalid_name", + 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()).toBeDefined() - expect(schema('ValidName1').validate_name()).toBeUndefined(); - expect(schema('Valid1Name').validate_name()).toBeUndefined(); - expect(schema('uint').validate_name()).toBeUndefined(); -}); \ No newline at end of file + expect(schema('ValidName1').validate_name()).toBeUndefined() + expect(schema('Valid1Name').validate_name()).toBeUndefined() + expect(schema('uint').validate_name()).toBeUndefined() +}) diff --git a/tools/test/linter/SchemaFile.test.ts b/tools/test/linter/SchemaFile.test.ts index 4e2081f42..ad6b9d897 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"; +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()).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", + 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", + 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([]); -}); \ No newline at end of file + const valid = mocked_schema_file({ schema_errors: [[]] }) + expect(valid.validate()).toEqual([]) +}) diff --git a/tools/test/linter/SchemaRefsValidator.test.ts b/tools/test/linter/SchemaRefsValidator.test.ts index d1b7df909..e122d2536 100644 --- a/tools/test/linter/SchemaRefsValidator.test.ts +++ b/tools/test/linter/SchemaRefsValidator.test.ts @@ -1,30 +1,30 @@ -import SchemasFolder from "../../linter/components/SchemasFolder"; -import NamespacesFolder from "../../linter/components/NamespacesFolder"; -import SchemaRefsValidator from "../../linter/SchemaRefsValidator"; +import SchemasFolder from '../../linter/components/SchemasFolder' +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); + 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: '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: '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/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." + 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 d995fab0d..97b40a2dd 100644 --- a/tools/test/linter/SpecValidator.test.ts +++ b/tools/test/linter/SpecValidator.test.ts @@ -1,24 +1,24 @@ -import SpecValidator from "../../linter/SpecValidator"; +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'}, - ]); + { 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'}, - ]); -}); \ No newline at end of file + { file: 'path_refs', message: 'path refs error' }, + { file: 'schema_refs', message: 'schema refs error' } + ]) +}) diff --git a/tools/test/linter/factories/namespace_file.ts b/tools/test/linter/factories/namespace_file.ts index 87737fd21..5e89cba77 100644 --- a/tools/test/linter/factories/namespace_file.ts +++ b/tools/test/linter/factories/namespace_file.ts @@ -1,47 +1,47 @@ -import NamespaceFile from "../../../linter/components/NamespaceFile"; -import {OpenAPIV3} from "openapi-types"; -import {mocked_operation_group} from "./operation_group"; +import NamespaceFile from '../../../linter/components/NamespaceFile' +import { type 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}`); +export function namespace_file (fixture_file: string): NamespaceFile { + 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'; +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.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) { + 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 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); + 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 + return ns_file +} diff --git a/tools/test/linter/factories/operation.ts b/tools/test/linter/factories/operation.ts index d74ccddff..42791ce12 100644 --- a/tools/test/linter/factories/operation.ts +++ b/tools/test/linter/factories/operation.ts @@ -1,48 +1,48 @@ -import Operation from "../../../linter/components/Operation"; -import {OperationSpec} from "../../../types"; +import Operation from '../../../linter/components/Operation' +import { type 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); +export function operation (spec: Record, file_name = 'indices.yaml') { + 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); +export function mocked_operation (returned_values: MockedReturnedValues) { + const op = new Operation('', '', '', {} as OperationSpec) - if(returned_values.validate) { + if (returned_values.validate) { op.validate = jest.fn(); (op.validate as jest.Mock).mockReturnValue(returned_values.validate) - return op; + 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 d8aa55597..2ac53b6d1 100644 --- a/tools/test/linter/factories/operation_group.ts +++ b/tools/test/linter/factories/operation_group.ts @@ -1,43 +1,43 @@ -import OperationGroup from "../../../linter/components/OperationGroup"; -import {operation, mocked_operation} from "./operation"; +import OperationGroup from '../../../linter/components/OperationGroup' +import { operation, mocked_operation } from './operation' -export function operation_group (operation_specs: Record[]): OperationGroup { +export function operation_group (operation_specs: Array>): OperationGroup { const operations = operation_specs.map((spec) => { - return operation({'x-operation-group': 'indices.create', ...spec}); - }); - return new OperationGroup(`namespaces/indices.yaml`, 'indices.create', operations); + 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); +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) - if(returned_values.validate) { + if (returned_values.validate) { op_group.validate = jest.fn(); (op_group.validate as jest.Mock).mockReturnValue(returned_values.validate) - return op_group; + 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; -} \ No newline at end of file + return op_group +} diff --git a/tools/test/linter/factories/schema.ts b/tools/test/linter/factories/schema.ts index 2bdfa1f59..1176ced67 100644 --- a/tools/test/linter/factories/schema.ts +++ b/tools/test/linter/factories/schema.ts @@ -1,27 +1,27 @@ -import Schema from "../../../linter/components/Schema"; -import {OpenAPIV3} from "openapi-types"; +import Schema from '../../../linter/components/Schema' +import { type OpenAPIV3 } from 'openapi-types' -export function schema(name: string, spec: Record = {}): Schema { - return new Schema(`_common.yaml`, name, spec as OpenAPIV3.SchemaObject); +export function schema (name: string, spec: Record = {}): Schema { + 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); +export function mocked_schema (returned_values: MockedReturnedValues) { + const schema = new Schema('_common.yaml', 'Schema', {} as OpenAPIV3.SchemaObject) - if(returned_values.validate) { + if (returned_values.validate) { schema.validate = jest.fn(); - (schema.validate as jest.Mock).mockReturnValue(returned_values.validate); - return schema; + (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; -} \ No newline at end of file + return schema +} diff --git a/tools/test/linter/factories/schema_file.ts b/tools/test/linter/factories/schema_file.ts index 489f76e0e..3c653c2c5 100644 --- a/tools/test/linter/factories/schema_file.ts +++ b/tools/test/linter/factories/schema_file.ts @@ -1,30 +1,30 @@ -import {mocked_schema, schema} from "./schema"; -import SchemaFile from "../../../linter/components/SchemaFile"; +import { mocked_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}`); +export function schema_file (fixture: string): SchemaFile { + 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})); +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 })) - if(ops.returned_values) { - if(ops.returned_values.validate) { + 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 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; -} \ No newline at end of file + return validator +} diff --git a/tools/test/merger/OpenApiMerger.test.ts b/tools/test/merger/OpenApiMerger.test.ts index 5c1c01597..0e2513dd3 100644 --- a/tools/test/merger/OpenApiMerger.test.ts +++ b/tools/test/merger/OpenApiMerger.test.ts @@ -1,12 +1,10 @@ -import OpenApiMerger from '../../merger/OpenApiMerger'; -import fs from 'fs'; -import yaml from 'yaml'; - +import OpenApiMerger from '../../merger/OpenApiMerger' +import fs from 'fs' test('merge()', async () => { - const merger = new OpenApiMerger('./test/merger/fixtures/spec/opensearch-openapi.yaml'); - merger.merge('./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 + .toEqual(fs.readFileSync('./test/merger/opensearch-openapi.yaml', 'utf8')) + fs.unlinkSync('./test/merger/opensearch-openapi.yaml') +}) diff --git a/tools/types.ts b/tools/types.ts index 9da06a62c..8ad814e56 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -1,32 +1,32 @@ -import {OpenAPIV3} from "openapi-types"; +import { type 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 } export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'TRACE' -export type OperationPath = string; -export type SupersededOperationMap = Record; +export type OperationPath = string +export type SupersededOperationMap = Record