diff --git a/.changeset/evil-states-fold.md b/.changeset/evil-states-fold.md new file mode 100644 index 0000000000..abadb90528 --- /dev/null +++ b/.changeset/evil-states-fold.md @@ -0,0 +1,5 @@ +--- +"@redocly/openapi-core": patch +--- + +Fixed `path-params-defined` rule to correctly skip parameters defined through `$ref`. diff --git a/packages/core/src/rules/common/__tests__/path-params-defined.test.ts b/packages/core/src/rules/common/__tests__/path-params-defined.test.ts index 2f3e755f24..60b62d216a 100644 --- a/packages/core/src/rules/common/__tests__/path-params-defined.test.ts +++ b/packages/core/src/rules/common/__tests__/path-params-defined.test.ts @@ -402,4 +402,30 @@ describe('Oas3 path-params-defined', () => { ] `); }); + + it('should not report on undefined params in case of reference', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.0.0 + paths: + /test-endpoint: + get: + operationId: testEndpoint + tags: + - Test + summary: Test endpoint to reproduce bug + parameters: + - $ref: ./test_params.yaml + `, + 'foobar.yaml' + ); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await createConfig({ rules: { 'path-params-defined': 'error' } }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`); + }); }); diff --git a/packages/core/src/rules/common/path-params-defined.ts b/packages/core/src/rules/common/path-params-defined.ts index 98f41a192c..08d8a3e4c4 100644 --- a/packages/core/src/rules/common/path-params-defined.ts +++ b/packages/core/src/rules/common/path-params-defined.ts @@ -112,11 +112,9 @@ const createOperationHandlers = ( enter() { currentOperationParams = new Set(); }, - leave(_op: unknown, { report, location }: UserContext) { + leave(_operation: unknown, { report, location }: UserContext) { if (!pathContext.current || !currentOperationParams) return; - collectPathParamsFromOperation(_op, currentOperationParams); - validateRequiredPathParams( pathContext.current.templateParams, currentOperationParams, @@ -127,6 +125,8 @@ const createOperationHandlers = ( ); }, Parameter(parameter: Oas2Parameter | Oas3Parameter, { report, location }: UserContext) { + collectPathParamsFromOperation(parameter, currentOperationParams); + if (parameter.in === 'path' && parameter.name && pathContext.current) { currentOperationParams.add(parameter.name); validatePathParameter( @@ -150,13 +150,15 @@ const extractTemplateParams = (path: string): Set => { return new Set(Array.from(path.matchAll(pathRegex)).map((m) => m[1])); }; -const collectPathParamsFromOperation = (operation: unknown, targetSet: Set): void => { - const op = operation as { parameters?: Array<{ in?: string; name?: string }> }; - op?.parameters?.forEach((param) => { - if (param?.in === 'path' && param?.name) { - targetSet.add(param.name); +const collectPathParamsFromOperation = ( + parameter: Oas2Parameter | Oas3Parameter, + targetSet: Set +): void => { + if (parameter && typeof parameter === 'object' && 'in' in parameter && 'name' in parameter) { + if (parameter.in === 'path' && parameter.name) { + targetSet.add(parameter.name); } - }); + } }; const validatePathParameter = (