2021-09-01 18:52:53 +03:00
|
|
|
// Check conformance to Azure operationId conventions:
|
|
|
|
// - operationIds should have the form "noun_verb" with just one underscore separator [R1001, R2055]
|
2022-09-18 04:19:25 +03:00
|
|
|
// - get operation on a collection should have "list" in the operationId verb
|
|
|
|
// - get operation on a single instance should have "get" in the operationId verb
|
|
|
|
// - put operation that returns 201 should have "create" in the operationId verb
|
|
|
|
// - put operation that returns 200 should have "replace" in the operationId verb
|
|
|
|
// - put operation that returns 200 should not have "update" in the operationId verb
|
|
|
|
// - patch operation that returns 201 should have "create" in the operationId verb
|
|
|
|
// - patch operation that returns 200 should have "update" in the operationId verb
|
|
|
|
// - patch operation should not have "patch" in the operationId verb
|
|
|
|
// - post operation should not have "post" in the operationId verb
|
|
|
|
// - delete operation should have "delete" in the operationId verb
|
2021-09-01 18:52:53 +03:00
|
|
|
|
2021-09-03 14:49:06 +03:00
|
|
|
module.exports = (operation, _opts, paths) => {
|
2021-09-01 18:52:53 +03:00
|
|
|
// targetVal should be an operation
|
2021-09-03 14:49:06 +03:00
|
|
|
if (operation === null || typeof operation !== 'object') {
|
2021-09-01 18:52:53 +03:00
|
|
|
return [];
|
|
|
|
}
|
2021-09-10 06:18:35 +03:00
|
|
|
const path = paths.path || paths.target || [];
|
2021-09-01 18:52:53 +03:00
|
|
|
|
|
|
|
const errors = [];
|
|
|
|
|
2021-09-03 14:49:06 +03:00
|
|
|
if (!operation.operationId) {
|
2021-09-01 18:52:53 +03:00
|
|
|
// Missing operationId is caught elsewhere, so just return
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
2021-09-03 14:49:06 +03:00
|
|
|
const m = operation.operationId.match(/[A-Za-z0-9]+_([A-Za-z0-9]+)/);
|
2021-09-01 18:52:53 +03:00
|
|
|
if (!m) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId should be of the form "Noun_Verb"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-03 14:49:06 +03:00
|
|
|
const verb = m ? m[1] : operation.operationId;
|
2021-09-01 18:52:53 +03:00
|
|
|
const method = path[path.length - 1];
|
2022-09-18 04:19:25 +03:00
|
|
|
const statusCodes = operation.responses ? Object.keys(operation.responses) : [];
|
2021-09-01 18:52:53 +03:00
|
|
|
|
2022-09-18 04:19:25 +03:00
|
|
|
if (method === 'get') {
|
|
|
|
const opPath = path[path.length - 2];
|
|
|
|
const pathIsCollection = !opPath.endsWith('}');
|
|
|
|
if (pathIsCollection) {
|
|
|
|
if (!verb.match(/list/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for get on a collection should contain "list"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (!verb.match(/get/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for get on a single object should contain "get"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (method === 'put') {
|
|
|
|
if (statusCodes.includes('200') && statusCodes.includes('201')) {
|
|
|
|
if (!verb.match(/create/i) || !verb.match(/replace/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for put with 200 and 201 responses should contain "create" and "replace"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (statusCodes.includes('200') && !statusCodes.includes('201')) {
|
|
|
|
if (!verb.match(/replace/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for put with 200 response should contain "replace"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (verb.match(/create/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for put without 201 response should not contain "create"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (statusCodes.includes('201') && !statusCodes.includes('200')) {
|
|
|
|
if (!verb.match(/create/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for put with 201 response should contain "create"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (verb.match(/replace/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for put without 200 response should not contain "replace"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Anti-patterns
|
2021-09-01 18:52:53 +03:00
|
|
|
|
2022-09-18 04:19:25 +03:00
|
|
|
// operationId for put should not contain "update"
|
|
|
|
const update = verb.match(/update/i)?.[0];
|
|
|
|
if (update) {
|
2021-09-01 18:52:53 +03:00
|
|
|
errors.push({
|
2022-09-18 04:19:25 +03:00
|
|
|
message: `OperationId for put should not contain "${update}"`,
|
2021-09-01 18:52:53 +03:00
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-18 04:19:25 +03:00
|
|
|
// operationId for put should not contain "put"
|
|
|
|
const put = verb.match(/put/i)?.[0];
|
|
|
|
if (put) {
|
|
|
|
errors.push({
|
|
|
|
message: `OperationId for put should not contain "${put}"`,
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (method === 'patch') {
|
|
|
|
if (statusCodes.includes('200') && statusCodes.includes('201')) {
|
|
|
|
if (!verb.match(/create/i) || !verb.match(/update/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for patch with 200 and 201 responses should contain "create" and "update"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (statusCodes.includes('200') && !statusCodes.includes('201')) {
|
|
|
|
if (!verb.match(/update/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for patch with 200 response should contain "update"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (verb.match(/create/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for patch without 201 response should not contain "create"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (statusCodes.includes('201') && !statusCodes.includes('200')) {
|
|
|
|
if (!verb.match(/create/i)) {
|
2021-09-01 18:52:53 +03:00
|
|
|
errors.push({
|
2022-09-18 04:19:25 +03:00
|
|
|
message: 'OperationId for patch with 201 response should contain "create"',
|
2021-09-01 18:52:53 +03:00
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
2022-09-18 04:19:25 +03:00
|
|
|
}
|
|
|
|
if (verb.match(/update/i)) {
|
2021-09-01 18:52:53 +03:00
|
|
|
errors.push({
|
2022-09-18 04:19:25 +03:00
|
|
|
message: 'OperationId for patch without 200 response should not contain "update"',
|
2021-09-01 18:52:53 +03:00
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-09-18 04:19:25 +03:00
|
|
|
|
|
|
|
// Anti-patterns
|
|
|
|
|
|
|
|
// operationId for patch should not contain "patch"
|
|
|
|
const patch = verb.match(/patch/i)?.[0];
|
|
|
|
if (patch) {
|
|
|
|
errors.push({
|
|
|
|
message: `OperationId for patch should not contain "${patch}"`,
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (method === 'post') {
|
|
|
|
// operationId for post should not contain "post"
|
|
|
|
const post = verb.match(/post/i)?.[0];
|
|
|
|
if (post) {
|
|
|
|
errors.push({
|
|
|
|
message: `OperationId for post should not contain "${post}"`,
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (method === 'delete') {
|
|
|
|
if (!verb.match(/delete/i)) {
|
|
|
|
errors.push({
|
|
|
|
message: 'OperationId for delete should contain "delete"',
|
|
|
|
path: [...path, 'operationId'],
|
|
|
|
});
|
|
|
|
}
|
2021-09-01 18:52:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return errors;
|
|
|
|
};
|