Skip to content

Commit

Permalink
# Added periods for validation messages
Browse files Browse the repository at this point in the history
Signed-off-by: Theo Truong <[email protected]>
  • Loading branch information
nhtruong committed Apr 15, 2024
1 parent e29ebd7 commit 3156e2f
Show file tree
Hide file tree
Showing 13 changed files with 47 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Validate Spec Folder
name: Validate Spec

on:
push:
Expand Down
2 changes: 1 addition & 1 deletion tools/linter/PathRefsValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
});
Expand Down
4 changes: 2 additions & 2 deletions tools/linter/components/NamespaceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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[];
}
Expand Down
2 changes: 1 addition & 1 deletion tools/linter/components/NamespacesFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class NamespacesFolder extends FolderValidator<NamespaceFile> {
}
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[];
}
}
24 changes: 12 additions & 12 deletions tools/linter/components/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,52 +41,52 @@ 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 {
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`);
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 {
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}'`);
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[];
}
Expand All @@ -97,15 +97,15 @@ 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}'.`);
}
}

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('}, {')}}`);
return this.error(`Path parameters must match the parameters in the path: {${expected.join('}, {')}}.`);
}

path_params(): string[] {
Expand Down
2 changes: 1 addition & 1 deletion tools/linter/components/RootFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}
}
4 changes: 2 additions & 2 deletions tools/linter/components/SchemaFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
4 changes: 2 additions & 2 deletions tools/test/linter/NamespaceFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]+$/.`
});
});

Expand Down Expand Up @@ -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",
Expand Down
12 changes: 6 additions & 6 deletions tools/test/linter/NamespacesFolder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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."
}
]);
});
28 changes: 14 additions & 14 deletions tools/test/linter/Operation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
Expand All @@ -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())
Expand All @@ -78,21 +78,21 @@ 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'},
'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'}}});
expect(valid_responses.validate_responses())
Expand All @@ -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'},
Expand Down
2 changes: 1 addition & 1 deletion tools/test/linter/PathRefsValidator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tools/test/linter/RootFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
]);
});
4 changes: 2 additions & 2 deletions tools/test/linter/SchemaFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_]+$/."
});
});

Expand Down

0 comments on commit 3156e2f

Please sign in to comment.