diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 0ce246d7c3..3e90472cc9 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -667,6 +667,7 @@ enum ApilintCodes { OPENAPI2_PATH_TEMPLATE = 3040000, OPENAPI2_PATH_TEMPLATE_VALUE_WELL_FORMED = 3040100, OPENAPI2_PATH_TEMPLATE_VALUE_VALID, + OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, OPENAPI2_LICENSE = 3050000, OPENAPI2_LICENSE_FIELD_NAME_TYPE = 3050100, diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts index 9150ac9932..a641aa5173 100644 --- a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts @@ -1,6 +1,7 @@ import valueWellFormedLint from './value--well-formed.ts'; import valueValidLint from './value--valid.ts'; +import valueEquivalentNotAllowedLint from './value--equivalent-not-allowed.ts'; -const lints = [valueWellFormedLint, valueValidLint]; +const lints = [valueWellFormedLint, valueValidLint, valueEquivalentNotAllowedLint]; export default lints; diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts new file mode 100644 index 0000000000..ed8731c0f3 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts @@ -0,0 +1,17 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { OpenAPI2 } from '../../target-specs.ts'; + +const valueEquivalentNotAllowedLint: LinterMeta = { + code: ApilintCodes.OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, + source: 'apilint', + message: 'Equivalent paths are not allowed', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintOpenAPIPathTemplateNoEquivalent', + marker: 'value', + targetSpecs: OpenAPI2, +}; + +export default valueEquivalentNotAllowedLint; diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index 196d248c27..3d84bd3138 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -1133,4 +1133,24 @@ export const standardLinterfunctions: FunctionItem[] = [ return true; }, }, + { + functionName: 'apilintOpenAPIPathTemplateNoEquivalent', + function: (element: Element): boolean => { + const PATH_TEMPLATES_REGEX = /\{[^}]+\}/g; + const isFirstOccurrence = (currentKey: string, allKeys: unknown[]) => { + const normalize = (x: string) => x.replace(PATH_TEMPLATES_REGEX, '~~'); + const currentKeyNormalized = normalize(currentKey); + const firstIndex = allKeys.findIndex( + (e) => typeof e === 'string' && normalize(e) === currentKeyNormalized, + ); + + return allKeys[firstIndex] === currentKey; + }; + const paths = element.parent.parent; + + return isStringElement(element) && isObject(paths) + ? isFirstOccurrence(element.toValue(), paths.keys()) + : true; + }, + }, ]; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/path-template-equivalent-not-allowed.yaml b/packages/apidom-ls/test/fixtures/validation/oas/path-template-equivalent-not-allowed.yaml new file mode 100644 index 0000000000..412b7523ef --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/path-template-equivalent-not-allowed.yaml @@ -0,0 +1,28 @@ +swagger: '2.0' +info: + title: Test API + version: 1.0.0 +paths: + /items/{id}: + get: + summary: Get item by ID + parameters: + - name: id + in: path + required: true + type: string + responses: + 200: + description: OK + /items/{itemId}: + get: + summary: Get item by itemId + parameters: + - name: itemId + in: path + required: true + type: string + responses: + 200: + description: OK + diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index fb4564857a..7c05df810c 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3788,4 +3788,40 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + + it('oas 2.0 should not allow equivalent paths', async function () { + const spec = fs + .readFileSync( + path.join( + __dirname, + 'fixtures', + 'validation', + 'oas', + 'path-template-equivalent-not-allowed.yaml', + ), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'path-template-equivalent-not-allowed.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc); + const expected: Diagnostic[] = [ + { + message: 'Equivalent paths are not allowed', + severity: 1, + code: 3040102, + source: 'apilint', + range: { start: { line: 16, character: 2 }, end: { line: 16, character: 17 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }); });