UNPKG

@redocly/openapi-core

Version:

See https://github.com/Redocly/redocly-cli

124 lines 4.99 kB
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