@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
124 lines • 4.99 kB
JavaScript
const pathRegex = /\{([a-zA-Z0-9_.-]+)\}+/g;
const MAX_DEPTH = 2; // Only first callback level is supported
export const PathParamsDefined = () => {
const pathContext = { current: null };
const currentOperationParams = new Set();
return {
PathItem: {
enter(_, { key }) {
pathItemEnter(pathContext, key);
},
leave() {
pathItemLeave(pathContext);
},
Parameter(parameter, { report, location }) {
createPathItemParameterHandler(parameter, pathContext, report, location);
},
Operation: createOperationHandlers(pathContext, currentOperationParams),
},
};
};
const pathItemEnter = (pathContext, key) => {
pathContext.current = {
path: key,
templateParams: extractTemplateParams(key),
definedParams: new Set(),
};
};
const pathItemLeave = (pathContext) => {
pathContext.current = null;
};
const createPathItemParameterHandler = (parameter, pathContext, report, location) => {
if (parameter.in === 'path' && parameter.name && pathContext.current) {
pathContext.current.definedParams.add(parameter.name);
validatePathParameter(parameter.name, pathContext.current.templateParams, pathContext.current.path, report, location);
}
};
const createOperationHandlers = (pathContext, currentOperationParams, depth = 0) => {
const reportMaxDepthWarning = (report, location, depth) => {
report({
message: `Maximum callback nesting depth (${depth}) reached. Path parameter validation is limited beyond this depth to prevent infinite recursion.`,
location: location,
});
};
if (depth >= MAX_DEPTH) {
return {
enter: () => { },
leave: (_op, { report, location }) => {
reportMaxDepthWarning(report, location, depth);
},
Parameter: () => { },
Callback: undefined,
};
}
const createCallbackPathItem = () => {
let parentPathContext = null;
return {
enter(_, { key }) {
parentPathContext = pathContext.current;
pathItemEnter(pathContext, key);
},
leave() {
pathContext.current = parentPathContext;
},
Parameter(parameter, { report, location }) {
createPathItemParameterHandler(parameter, pathContext, report, location);
},
get Operation() {
return createOperationHandlers(pathContext, currentOperationParams, depth + 1);
},
};
};
return {
enter() {
currentOperationParams = new Set();
},
leave(_operation, { report, location }) {
if (!pathContext.current || !currentOperationParams)
return;
validateRequiredPathParams(pathContext.current.templateParams, currentOperationParams, pathContext.current.definedParams, pathContext.current.path, report, location);
},
Parameter(parameter, { report, location }) {
collectPathParamsFromOperation(parameter, currentOperationParams);
if (parameter.in === 'path' && parameter.name && pathContext.current) {
currentOperationParams.add(parameter.name);
validatePathParameter(parameter.name, pathContext.current.templateParams, pathContext.current.path, report, location);
}
},
Callback: {
get PathItem() {
return createCallbackPathItem();
},
},
};
};
const extractTemplateParams = (path) => {
return new Set(Array.from(path.matchAll(pathRegex)).map((m) => m[1]));
};
const collectPathParamsFromOperation = (parameter, targetSet) => {
if (parameter && typeof parameter === 'object' && 'in' in parameter && 'name' in parameter) {
if (parameter.in === 'path' && parameter.name) {
targetSet.add(parameter.name);
}
}
};
const validatePathParameter = (paramName, templateParams, path, report, location) => {
if (!templateParams.has(paramName)) {
report({
message: `Path parameter \`${paramName}\` is not used in the path \`${path}\`.`,
location: location.child(['name']),
});
}
};
const validateRequiredPathParams = (templateParams, definedOperationParams, definedPathParams, path, report, location) => {
const allDefinedParams = new Set([...definedOperationParams, ...definedPathParams]);
for (const templateParam of templateParams) {
if (!allDefinedParams.has(templateParam)) {
report({
message: `The operation does not define the path parameter \`{${templateParam}}\` expected by path \`${path}\`.`,
location: location.child(['parameters']).key(),
});
}
}
};
//# sourceMappingURL=path-params-defined.js.map