packages/rulesets/src/spectral/functions/param-names-unique.ts (48 lines of code) (raw):
// Check that the parameters of an operation -- including those specified on the path -- are
// are case-insensitive unique regardless of "in".
// Return the "canonical" casing for a string.
// Currently just lowercase but should be extended to convert kebab/camel/snake/Pascal.
function canonical(name:any) {
return typeof (name) === 'string' ? name.toLowerCase() : name;
}
// Accept an array and return a list of unique duplicate entries in canonical form.
// This function is intended to work on strings but is resilient to non-strings.
function dupIgnoreCase(arr:any) {
if (!Array.isArray(arr)) {
return [];
}
const isDup = (value:any, index:number, self:any) => self.indexOf(value) !== index;
return [...new Set(arr.map((v) => canonical(v)).filter(isDup))];
}
// targetVal should be a [path item object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#pathItemObject).
// The code assumes it is running on a resolved doc
const paramNamesUnique = (pathItem:any, _opts:any, paths:any) => {
if (pathItem === null || typeof pathItem !== 'object') {
return [];
}
const path = paths.path || [];
const errors:any[] = [];
const pathParams = pathItem.parameters ? pathItem.parameters.map((p:any) => p.name) : [];
// Check path params for dups
const pathDups = dupIgnoreCase(pathParams);
// Report all dups
pathDups.forEach((dup) => {
// get the index of all names that match dup
const dupKeys = [...pathParams.keys()].filter((k) => canonical(pathParams[k]) === dup);
// Refer back to the first one
const first = `parameters.${dupKeys[0]}`;
// Report errors for all the others
dupKeys.slice(1).forEach((key) => {
errors.push({
message: `Duplicate parameter name (ignoring case) with ${first}.`,
path: [...path, 'parameters', key, 'name'],
});
});
});
['get', 'post', 'put', 'patch', 'delete', 'options', 'head'].forEach((method) => {
// If this method exists and it has parameters, check them
if (pathItem[method] && Array.isArray(pathItem[method].parameters)) {
const allParams = [...pathParams, ...pathItem[method].parameters.map((p:any) => p.name)];
// Check method params for dups -- including path params
const dups = dupIgnoreCase(allParams);
// Report all dups
dups.forEach((dup) => {
// get the index of all names that match dup
const dupKeys = [...allParams.keys()].filter((k) => canonical(allParams[k]) === dup);
// Refer back to the first one - could be path or method
const first = dupKeys[0] < pathParams.length ? `parameters.${dupKeys[0]}`
: `${method}.parameters.${dupKeys[0] - pathParams.length}`;
// Report errors for any others that are method parameters
dupKeys.slice(1).filter((k) => k >= pathParams.length).forEach((key) => {
errors.push({
message: `Duplicate parameter name (ignoring case) with ${first}.`,
path: [...path, method, 'parameters', key - pathParams.length, 'name'],
});
});
});
}
});
return errors;
};
export default paramNamesUnique