diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73fb75291..fa35994d3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Validate Spec Folder +name: Validate Spec on: push: diff --git a/tools/linter/PathRefsValidator.ts b/tools/linter/PathRefsValidator.ts index 6ec200d11..7875bf208 100644 --- a/tools/linter/PathRefsValidator.ts +++ b/tools/linter/PathRefsValidator.ts @@ -51,7 +51,7 @@ export default class PathRefsValidator { 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}`, + message: `Unresolved path reference: Path ${path} does not exist in namespace file ${ref_file}.`, }; }).filter((e) => e) as ValidationError[]; }); diff --git a/tools/linter/components/NamespaceFile.ts b/tools/linter/components/NamespaceFile.ts index d38ba3a4f..7a3b95476 100644 --- a/tools/linter/components/NamespaceFile.ts +++ b/tools/linter/components/NamespaceFile.ts @@ -60,7 +60,7 @@ export default class NamespaceFile extends FileValidator { 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 this.error(`Invalid namespace name '${name}'. Must match regex: /${NAME_REGEX.source}/.`, 'File Name'); return; } @@ -96,7 +96,7 @@ export default class NamespaceFile extends FileValidator { `#/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 dots`, + `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[]; } diff --git a/tools/linter/components/NamespacesFolder.ts b/tools/linter/components/NamespacesFolder.ts index 52bb94d5c..a8b18c400 100644 --- a/tools/linter/components/NamespacesFolder.ts +++ b/tools/linter/components/NamespacesFolder.ts @@ -22,7 +22,7 @@ export default class NamespacesFolder extends FolderValidator { } return Object.entries(paths).map(([path, namespaces]) => { if(namespaces.length > 1) - return this.error(`Duplicate path '${path}' found in namespaces: ${namespaces.sort().join(', ')}`); + 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 0bcb4f301..1be04bf12 100644 --- a/tools/linter/components/Operation.ts +++ b/tools/linter/components/Operation.ts @@ -41,7 +41,7 @@ export default class Operation extends ValidatorBase { 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}`); + return this.error(`Invalid x-operation-group '${this.group}'. Must match regex: /${GROUP_REGEX.source}/.`); } validate_namespace(): ValidationError | void { @@ -49,27 +49,27 @@ export default class Operation extends ValidatorBase { 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`); + return this.error(`Invalid x-operation-group '${this.group}'. '_core' namespace must be omitted in x-operation-group.`); if(this.namespace === expected_namespace ) return; return this.error(`Invalid x-operation-group '${this.group}'. '${this.namespace}' namespace detected. ` + - `Only '${expected_namespace}' namespace is allowed in this file`); + `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`); + return this.error(`Missing description property.`); if(!description.endsWith('.')) - return this.error(`Description must end with a period`); + 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`); + 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`); + return this.error(`Invalid operationId '${id}'. Must be in {x-operation-group}.{number} format.`); } validate_requestBody(): ValidationError | void { @@ -77,16 +77,16 @@ export default class Operation extends ValidatorBase { 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}'`); + 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`)]; + 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 this.error(`The ${code} response must be a reference object to '${expected}'.`); return; }).filter((error) => error) as ValidationError[]; } @@ -97,7 +97,7 @@ export default class Operation extends ValidatorBase { 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}'`); + return this.error(`Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'.`); } } @@ -105,7 +105,7 @@ export default class Operation extends ValidatorBase { 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('}, {')}}`); + return this.error(`Path parameters must match the parameters in the path: {${expected.join('}, {')}}.`); } path_params(): string[] { diff --git a/tools/linter/components/RootFile.ts b/tools/linter/components/RootFile.ts index 94f583b62..5718ce3d4 100644 --- a/tools/linter/components/RootFile.ts +++ b/tools/linter/components/RootFile.ts @@ -14,7 +14,7 @@ export default class RootFile extends FileValidator { 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}`); + return this.error(`Every path must be a reference object to a path in a namespace file.`, `Path: ${path}`); }).filter((e) => e) as ValidationError[]; } } \ No newline at end of file diff --git a/tools/linter/components/SchemaFile.ts b/tools/linter/components/SchemaFile.ts index 27ed265a8..2d989365a 100644 --- a/tools/linter/components/SchemaFile.ts +++ b/tools/linter/components/SchemaFile.ts @@ -34,9 +34,9 @@ export default class SchemaFile extends FileValidator { 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'); + 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'); + 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/test/linter/NamespaceFile.test.ts b/tools/test/linter/NamespaceFile.test.ts index d0fd8670b..88e47f6a5 100644 --- a/tools/test/linter/NamespaceFile.test.ts +++ b/tools/test/linter/NamespaceFile.test.ts @@ -16,7 +16,7 @@ test('validate_name()', () => { 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]+$/.` }); }); @@ -70,7 +70,7 @@ test('validate_parameter_refs()', () => { { 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 dots" + message: "Invalid parameter name 'ExpandWildcards'. A parameter's name can only contain lower-cased alphanumerics, underscores, and periods." }, { file: "namespaces/invalid_components.yaml", diff --git a/tools/test/linter/NamespacesFolder.test.ts b/tools/test/linter/NamespacesFolder.test.ts index a715ddc3d..9563988aa 100644 --- a/tools/test/linter/NamespacesFolder.test.ts +++ b/tools/test/linter/NamespacesFolder.test.ts @@ -11,22 +11,22 @@ test('validate()', () => { { file: "namespaces/invalid_spec.yaml", location: "Operation: GET /{index}/_doc/{id}", - message: "Missing description property" + message: "Missing description property." }, { file: "namespaces/invalid_spec.yaml", location: "Operation: GET /{index}/_doc/{id}", - message: "Every parameter must be a reference object to '#/components/parameters/{x-operation-group}::{path|query}.{parameter_name}'" + 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}" + message: "Path parameters must match the parameters in the path: {id}, {index}." }, { file: "namespaces/invalid_spec.yaml", location: "Operation: GET /{index}/_doc/{id}", - message: "The 200 response must be a reference object to '#/components/responses/invalid_spec.fetch@200'" + message: "The 200 response must be a reference object to '#/components/responses/invalid_spec.fetch@200'." }, { file: "namespaces/invalid_yaml.yaml", @@ -36,12 +36,12 @@ test('validate()', () => { { file: "namespaces/", location: "Folder", - message: "Duplicate path '/{index}' found in namespaces: dup_path_a, dup_path_c" + message: "Duplicate path '/{index}' found in namespaces: dup_path_a, dup_path_c." }, { file: "namespaces/", location: "Folder", - message: "Duplicate path '/{index}/_rollover' found in namespaces: dup_path_a, dup_path_b, dup_path_c" + 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 d1d705957..43fce1229 100644 --- a/tools/test/linter/Operation.test.ts +++ b/tools/test/linter/Operation.test.ts @@ -10,11 +10,11 @@ test('validate_group()', () => { 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'}); 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'}); expect(valid_group.validate_group()) @@ -32,21 +32,21 @@ test('validate_namespace()', () => { 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'); 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'}); 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'}); 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'}); expect(valid_id.validate_operationId()) @@ -56,11 +56,11 @@ test('validate_operationId()', () => { test('validate_description()', () => { 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'}); 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.'}); expect(valid_description.validate_description()) @@ -78,12 +78,12 @@ test('validate_requestBody()', () => { 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`)]); + 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'}, @@ -91,8 +91,8 @@ test('validate_response()', () => { '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'}}}); expect(valid_responses.validate_responses()) @@ -114,13 +114,13 @@ test('validate_parameters()', () => { {$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'}]}); 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'}, diff --git a/tools/test/linter/PathRefsValidator.test.ts b/tools/test/linter/PathRefsValidator.test.ts index 44fea59ac..527782e3e 100644 --- a/tools/test/linter/PathRefsValidator.test.ts +++ b/tools/test/linter/PathRefsValidator.test.ts @@ -11,7 +11,7 @@ test('validate()', () => { { file: "opensearch-openapi.yaml", location: "Path: /{index}", - message: "Unresolved path reference: Path /{index} does not exist in namespace file namespaces/indices.yaml" + message: "Unresolved path reference: Path /{index} does not exist in namespace file namespaces/indices.yaml." }, { file: "opensearch-openapi.yaml", diff --git a/tools/test/linter/RootFile.test.ts b/tools/test/linter/RootFile.test.ts index 70b612f5f..89749b20f 100644 --- a/tools/test/linter/RootFile.test.ts +++ b/tools/test/linter/RootFile.test.ts @@ -6,12 +6,12 @@ test('validate()', () => { { file: "root.yaml", location: "Path: /", - message: "Every path must be a reference object to a path in a namespace file" + 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" + message: "Every path must be a reference object to a path in a namespace file." } ]); }); \ No newline at end of file diff --git a/tools/test/linter/SchemaFile.test.ts b/tools/test/linter/SchemaFile.test.ts index e09c64fd3..06eb0ffbe 100644 --- a/tools/test/linter/SchemaFile.test.ts +++ b/tools/test/linter/SchemaFile.test.ts @@ -10,12 +10,12 @@ test('validate_category()', () => { expect(validator.validate_category('cat._invalid_name')).toEqual({ file: "schemas/_common.empty.yaml", location: "File Name", - message: "Invalid category name 'cat._invalid_name'. '_invalid_name' does not match regex: ^[a-z]+[a-z_]*[a-z]+$" + message: "Invalid category name 'cat._invalid_name'. '_invalid_name' does not match regex: /^[a-z]+[a-z_]*[a-z]+$/." }); expect(validator.validate_category('invalid_regex')).toEqual({ file: "schemas/_common.empty.yaml", location: "File Name", - message: "Invalid category name 'invalid_regex'. Must match regex: ^[a-z_]+\\.[a-z_]+$" + message: "Invalid category name 'invalid_regex'. Must match regex: /^[a-z_]+\\.[a-z_]+$/." }); });