diff --git a/.changeset/warm-paths-glow.md b/.changeset/warm-paths-glow.md new file mode 100644 index 0000000000..85f9e41b37 --- /dev/null +++ b/.changeset/warm-paths-glow.md @@ -0,0 +1,5 @@ +--- +"@redocly/openapi-core": minor +--- + +Added `excludedPaths` option to the `no-http-verbs-in-paths` rule, allowing specific paths to be excluded from evaluation. diff --git a/docs/@v2/rules/oas/no-http-verbs-in-paths.md b/docs/@v2/rules/oas/no-http-verbs-in-paths.md index 43d0efef05..348bc4eaee 100644 --- a/docs/@v2/rules/oas/no-http-verbs-in-paths.md +++ b/docs/@v2/rules/oas/no-http-verbs-in-paths.md @@ -45,10 +45,11 @@ With the `splitIntoWords` option enabled, "posters" is identified as a resource ## Configuration -| Option | Type | Description | -| -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | -| severity | string | Possible values: `off`, `warn`, `error`. Default `off` (in `recommended` configuration). | -| splitIntoWords | boolean | Matches http verbs when the string is split into words based on casing. This can reduce false positives. Default **false**. | +| Option | Type | Description | +| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | +| severity | string | Possible values: `off`, `warn`, `error`. Default `off` (in `recommended` configuration). | +| splitIntoWords | boolean | Matches http verbs when the string is split into words based on casing. This can reduce false positives. Default **false**. | +| excludedPaths | [string] | List of paths to exclude from the check. Use exact path strings (e.g. `/token`). Default **[]**. | An example configuration: @@ -66,6 +67,17 @@ rules: splitIntoWords: true ``` +An example configuration with `excludedPaths` to allow specific paths that intentionally contain HTTP verbs: + +```yaml +rules: + no-http-verbs-in-paths: + severity: error + excludedPaths: + - /token + - /oauth/callback +``` + ## Examples Given this configuration: diff --git a/packages/core/src/rules/common/__tests__/no-http-verbs-in-paths.test.ts b/packages/core/src/rules/common/__tests__/no-http-verbs-in-paths.test.ts index e34ee92f9e..9b5625f12b 100644 --- a/packages/core/src/rules/common/__tests__/no-http-verbs-in-paths.test.ts +++ b/packages/core/src/rules/common/__tests__/no-http-verbs-in-paths.test.ts @@ -61,4 +61,51 @@ describe('no-http-verbs-in-paths', () => { ] `); }); + + it('should not report on excluded paths', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.1.0 + paths: + /data/post: + get: + summary: Contains http verb post + /get/data: + get: + summary: Contains http verb get + `, + 'foobar.yaml' + ); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await createConfig({ + rules: { + 'no-http-verbs-in-paths': { + severity: 'error', + excludedPaths: ['/data/post'], + }, + }, + }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(` + [ + { + "location": [ + { + "pointer": "#/paths/~1get~1data", + "reportOnKey": true, + "source": "foobar.yaml", + }, + ], + "message": "path \`/get/data\` should not contain http verb get", + "ruleId": "no-http-verbs-in-paths", + "severity": "error", + "suggest": [], + }, + ] + `); + }); }); diff --git a/packages/core/src/rules/common/no-http-verbs-in-paths.ts b/packages/core/src/rules/common/no-http-verbs-in-paths.ts index edda723dd6..7cce941efe 100644 --- a/packages/core/src/rules/common/no-http-verbs-in-paths.ts +++ b/packages/core/src/rules/common/no-http-verbs-in-paths.ts @@ -7,11 +7,12 @@ import type { UserContext } from '../../walk.js'; const httpMethods = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace']; -export const NoHttpVerbsInPaths: Oas3Rule | Oas2Rule = ({ splitIntoWords }) => { +export const NoHttpVerbsInPaths: Oas3Rule | Oas2Rule = ({ splitIntoWords, excludedPaths }) => { return { PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) { const pathKey = key.toString(); if (!pathKey.startsWith('/')) return; + if (excludedPaths?.some((excludedPath: string) => pathKey === excludedPath)) return; const pathSegments = pathKey.split('/'); for (const pathSegment of pathSegments) {