From 678e3c004d52e4ddc734dd3e5e96bbe6039d8603 Mon Sep 17 00:00:00 2001 From: Krzysztof Kowalczyk Date: Thu, 14 Dec 2023 09:30:50 +0100 Subject: [PATCH] feat(apidom-ls): path template match parameter object lint --- .../services/validation/linter-functions.ts | 34 +++++++++++-- .../oas/path-template-all-defined.yaml | 50 +++++++++++++++++++ packages/apidom-ls/test/validate.ts | 42 ++++++++++++++++ 3 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/path-template-all-defined.yaml diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index c595ff234a..79a227e37d 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -10,7 +10,7 @@ import { ObjectElement, } from '@swagger-api/apidom-core'; import { CompletionItem } from 'vscode-languageserver-types'; -import { test } from 'openapi-path-templating'; +import { test, parse } from 'openapi-path-templating'; // eslint-disable-next-line import/no-cycle import { @@ -1009,9 +1009,37 @@ export const standardLinterfunctions: FunctionItem[] = [ functionName: 'apilintOpenAPIPathTemplateValid', function: (element: Element) => { if (isStringElement(element)) { - return true; + const parseResult = parse(toValue(element)); + const parts: string[] = []; + parseResult.ast.translate(parts); + + const templateExpressions = parts + .filter((part) => part[0] === 'template-expression') + .map((part) => part[1].slice(1, -1)); + + if (templateExpressions.length === 0) { + return true; + } + + const httpVerbsWithParameters: { + [key: string]: { + parameters: { + name: string; + }[]; + }; + } = element.parent.toValue().value; + + const allExpressionsHaveMatchingParameter = Object.values(httpVerbsWithParameters).every( + ({ parameters }) => { + return templateExpressions.every((expression) => + parameters.find(({ name }) => name === expression), + ); + }, + ); + return allExpressionsHaveMatchingParameter; } - return true; + + return false; }, }, ]; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/path-template-all-defined.yaml b/packages/apidom-ls/test/fixtures/validation/oas/path-template-all-defined.yaml new file mode 100644 index 0000000000..295e6d3648 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/path-template-all-defined.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Foo + version: 0.1.0 +paths: + /foo/bar/{baz}/test/{foo_id}/baz/{bar_id}: + delete: + summary: Delete foo bar baz + operationId: >- + deleteFooBarBaz + parameters: + - name: foo_id + in: path + required: true + schema: + type: string + format: uuid + title: Foo Id + - name: bar_id + in: path + required: true + schema: + type: string + format: uuid + title: Bar Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + /test/{foo_id}/{bar_id}: + get: + summary: Get test foo bar + operationId: >- + getTestFooBar + parameters: + - name: foo_id + in: path + required: true + schema: + type: string + format: uuid + title: Foo Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index 5446403be6..e0fc8d7fcc 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3376,6 +3376,48 @@ describe('apidom-ls-validate', function () { assert.deepEqual(result, expected as Diagnostic[]); + languageService.terminate(); + }); + it('oas / yaml - every path template should be defined', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'path-template-all-defined.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/path-template-all-defined.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + range: { start: { line: 5, character: 2 }, end: { line: 5, character: 43 } }, + message: 'path template expressions is not matched with Parameter Object(s)', + severity: 1, + code: 3040101, + source: 'apilint', + }, + { + range: { start: { line: 31, character: 2 }, end: { line: 31, character: 25 } }, + message: 'path template expressions is not matched with Parameter Object(s)', + severity: 1, + code: 3040101, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + languageService.terminate(); }); });