azure-api-style-guide/functions/operation-id.js

177 строки
6.1 KiB
JavaScript

// Check conformance to Azure operationId conventions:
// - operationIds should have the form "noun_verb" with just one underscore separator [R1001, R2055]
// - 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
module.exports = (operation, _opts, paths) => {
// targetVal should be an operation
if (operation === null || typeof operation !== 'object') {
return [];
}
const path = paths.path || paths.target || [];
const errors = [];
if (!operation.operationId) {
// Missing operationId is caught elsewhere, so just return
return errors;
}
const m = operation.operationId.match(/[A-Za-z0-9]+_([A-Za-z0-9]+)/);
if (!m) {
errors.push({
message: 'OperationId should be of the form "Noun_Verb"',
path: [...path, 'operationId'],
});
}
const verb = m ? m[1] : operation.operationId;
const method = path[path.length - 1];
const statusCodes = operation.responses ? Object.keys(operation.responses) : [];
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
// operationId for put should not contain "update"
const update = verb.match(/update/i)?.[0];
if (update) {
errors.push({
message: `OperationId for put should not contain "${update}"`,
path: [...path, 'operationId'],
});
}
// 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)) {
errors.push({
message: 'OperationId for patch with 201 response should contain "create"',
path: [...path, 'operationId'],
});
}
if (verb.match(/update/i)) {
errors.push({
message: 'OperationId for patch without 200 response should not contain "update"',
path: [...path, 'operationId'],
});
}
}
// 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'],
});
}
}
return errors;
};