From 4bdb0d798bb3a6854483c10238f20986937d99c6 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 3 Nov 2025 10:32:47 +0100 Subject: [PATCH 1/3] feat(ls): add validation of empty responses object --- packages/apidom-ls/src/config/codes.ts | 4 + .../config/openapi/responses/lint/index.ts | 10 ++- .../responses/lint/required-fields-2-0.ts | 20 +++++ .../lint/required-fields-3-0--3-1.ts | 23 ++++++ .../oas/parameter-unique-name-2-0.yaml | 2 + .../oas/parameter-unique-name-3-0.yaml | 2 + .../oas/responses-required-fields-2-0.yaml | 8 ++ .../oas/responses-required-fields-3-0.yaml | 8 ++ packages/apidom-ls/test/validate.ts | 76 +++++++++++++++++-- 9 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts create mode 100644 packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-2-0.yaml create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-3-0.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 1904db860b..bc4dda4e88 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -868,6 +868,9 @@ enum ApilintCodes { OPENAPI2_REFERENCE_FIELD_$REF_FORMAT_URI = 3240100, OPENAPI2_REFERENCE_NOT_USED = 3240300, + OPENAPI2_RESPONSES = 3250000, + OPENAPI2_RESPONSES_REQUIRED_FIELDS, + OPENAPI3_0 = 5000000, OPENAPI3_0_OPENAPI_VALUE_PATTERN_3_0_0 = 5000100, @@ -993,6 +996,7 @@ enum ApilintCodes { OPENAPI3_0_RESPONSES = 5140000, OPENAPI3_0_RESPONSES_VALUES_TYPE, + OPENAPI3_0_RESPONSES_REQUIRED_FIELDS, OPENAPI3_0_PARAMETER = 5150000, OPENAPI3_0_PARAMETER_REQUIRED_FIELDS, diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/index.ts b/packages/apidom-ls/src/config/openapi/responses/lint/index.ts index ac07a48457..0b74e69dd7 100644 --- a/packages/apidom-ls/src/config/openapi/responses/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/responses/lint/index.ts @@ -1,7 +1,15 @@ import allowedFields2_0Lint from './allowed-fields-2-0.ts'; import allowedFields3_0__3_1Lint from './allowed-fields-3-0--3-1.ts'; import valuesTypeLint from './values--type.ts'; +import requiredFields2_0Lint from './required-fields-2-0.ts'; +import requiredFields3_0__3_1Lint from './required-fields-3-0--3-1.ts'; -const lints = [valuesTypeLint, allowedFields2_0Lint, allowedFields3_0__3_1Lint]; +const lints = [ + valuesTypeLint, + allowedFields2_0Lint, + allowedFields3_0__3_1Lint, + requiredFields2_0Lint, + requiredFields3_0__3_1Lint, +]; export default lints; diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts new file mode 100644 index 0000000000..23458c7f4b --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts @@ -0,0 +1,20 @@ +import { range } from 'ramda'; +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { OpenAPI2 } from '../../target-specs.ts'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const requiredFields2_0Lint: LinterMeta = { + code: ApilintCodes.OPENAPI2_RESPONSES_REQUIRED_FIELDS, + source: 'apilint', + message: 'Responses Object should define at least one response', + severity: DiagnosticSeverity.Error, + linterFunction: 'existAnyOfFields', + linterParams: [['default', ...range(100, 600).map(String), ...range(100, 600)], false], + marker: 'key', + targetSpecs: OpenAPI2, +}; + +export default requiredFields2_0Lint; diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts new file mode 100644 index 0000000000..4dcaaed307 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts @@ -0,0 +1,23 @@ +import { range } from 'ramda'; +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { OpenAPI3 } from '../../target-specs.ts'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const requiredFields3_0__3_1Lint: LinterMeta = { + code: ApilintCodes.OPENAPI3_0_RESPONSES_REQUIRED_FIELDS, + source: 'apilint', + message: 'Responses Object should define at least one response', + severity: DiagnosticSeverity.Error, + linterFunction: 'existAnyOfFields', + linterParams: [ + ['default', '1XX', '2XX', '3XX', '4XX', '5XX', ...range(100, 600).map(String)], + false, + ], + marker: 'key', + targetSpecs: OpenAPI3, +}; + +export default requiredFields3_0__3_1Lint; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml index 51fcd46379..6f13158d52 100644 --- a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml @@ -6,6 +6,8 @@ paths: /pets: get: responses: + '200': + description: ok parameters: - name: pathLevel type: string diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml index 5bcb126e6d..2ef654b641 100644 --- a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml @@ -6,6 +6,8 @@ paths: /pets: get: responses: + '200': + description: ok parameters: - name: pathLevel in: query diff --git a/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-2-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-2-0.yaml new file mode 100644 index 0000000000..a68b2c8324 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-2-0.yaml @@ -0,0 +1,8 @@ +swagger: '2.0' +info: + title: Test + version: 1.0.0 +paths: + /: + get: + responses: diff --git a/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-3-0.yaml new file mode 100644 index 0000000000..b17b637414 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/responses-required-fields-3-0.yaml @@ -0,0 +1,8 @@ +openapi: 3.0.0 +info: + title: Test + version: 1.0.0 +paths: + /: + get: + responses: diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index 07ca49108e..c73044f3da 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -4205,11 +4205,11 @@ describe('apidom-ls-validate', function () { range: { end: { character: 14, - line: 9, + line: 11, }, start: { character: 10, - line: 9, + line: 11, }, }, severity: 1, @@ -4222,11 +4222,11 @@ describe('apidom-ls-validate', function () { range: { end: { character: 14, - line: 14, + line: 16, }, start: { character: 10, - line: 14, + line: 16, }, }, severity: 1, @@ -4268,11 +4268,11 @@ describe('apidom-ls-validate', function () { range: { end: { character: 14, - line: 9, + line: 11, }, start: { character: 10, - line: 9, + line: 11, }, }, severity: 1, @@ -4285,11 +4285,11 @@ describe('apidom-ls-validate', function () { range: { end: { character: 14, - line: 15, + line: 17, }, start: { character: 10, - line: 15, + line: 17, }, }, severity: 1, @@ -6099,4 +6099,64 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + + it('oas 2.0 - Responses Object should define at least one response', async function () { + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'responses-required-fields-2-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/responses-required-fields-2-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc); + const expected: Diagnostic[] = [ + { + message: 'Responses Object should define at least one response', + severity: 1, + code: 3250001, + source: 'apilint', + range: { start: { line: 7, character: 6 }, end: { line: 7, character: 15 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }); + + it('oas 3.x - Responses Object should define at least one response', async function () { + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'responses-required-fields-3-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/responses-required-fields-3-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc); + const expected: Diagnostic[] = [ + { + message: 'Responses Object should define at least one response', + severity: 1, + code: 5140002, + source: 'apilint', + range: { start: { line: 7, character: 6 }, end: { line: 7, character: 15 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }); }); From 435e54a1af312df13fb39086bd3b429ab5643a23 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 5 Nov 2025 14:18:15 +0100 Subject: [PATCH 2/3] fix: apply validation to all non-extension keys --- .../openapi/responses/lint/required-fields-2-0.ts | 4 +--- .../responses/lint/required-fields-3-0--3-1.ts | 7 +------ .../src/services/validation/linter-functions.ts | 13 +++++++++++++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts index 23458c7f4b..f7a232a6fa 100644 --- a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts @@ -1,4 +1,3 @@ -import { range } from 'ramda'; import { DiagnosticSeverity } from 'vscode-languageserver-types'; import ApilintCodes from '../../../codes.ts'; @@ -11,8 +10,7 @@ const requiredFields2_0Lint: LinterMeta = { source: 'apilint', message: 'Responses Object should define at least one response', severity: DiagnosticSeverity.Error, - linterFunction: 'existAnyOfFields', - linterParams: [['default', ...range(100, 600).map(String), ...range(100, 600)], false], + linterFunction: 'apilintOpenAPIEmptyResponses', marker: 'key', targetSpecs: OpenAPI2, }; diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts index 4dcaaed307..c72e24470b 100644 --- a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts @@ -1,4 +1,3 @@ -import { range } from 'ramda'; import { DiagnosticSeverity } from 'vscode-languageserver-types'; import ApilintCodes from '../../../codes.ts'; @@ -11,11 +10,7 @@ const requiredFields3_0__3_1Lint: LinterMeta = { source: 'apilint', message: 'Responses Object should define at least one response', severity: DiagnosticSeverity.Error, - linterFunction: 'existAnyOfFields', - linterParams: [ - ['default', '1XX', '2XX', '3XX', '4XX', '5XX', ...range(100, 600).map(String)], - false, - ], + linterFunction: 'apilintOpenAPIEmptyResponses', marker: 'key', targetSpecs: OpenAPI3, }; diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index e4723eae07..dbc4888011 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -1479,4 +1479,17 @@ export const standardLinterfunctions: FunctionItem[] = [ : true; }, }, + { + functionName: 'apilintOpenAPIEmptyResponses', + function: (element: Element): boolean => { + if (element && isObject(element)) { + if (!element.keys() || element.keys().length === 0) { + return false; + } + + return element.keys().some((k) => typeof k !== 'string' || !k.startsWith('x-')); + } + return true; + }, + }, ]; From de7acdedc6d9ce5a5ad95da9c0c9f3d61ed96105 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Thu, 6 Nov 2025 07:55:39 +0100 Subject: [PATCH 3/3] fix: use one rule for all specs --- packages/apidom-ls/src/config/codes.ts | 1 - .../src/config/openapi/responses/lint/index.ts | 11 ++--------- .../responses/lint/required-fields-3-0--3-1.ts | 18 ------------------ ...quired-fields-2-0.ts => required-fields.ts} | 9 ++++----- packages/apidom-ls/test/validate.ts | 2 +- 5 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts rename packages/apidom-ls/src/config/openapi/responses/lint/{required-fields-2-0.ts => required-fields.ts} (66%) diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index bc4dda4e88..4c74d6743c 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -996,7 +996,6 @@ enum ApilintCodes { OPENAPI3_0_RESPONSES = 5140000, OPENAPI3_0_RESPONSES_VALUES_TYPE, - OPENAPI3_0_RESPONSES_REQUIRED_FIELDS, OPENAPI3_0_PARAMETER = 5150000, OPENAPI3_0_PARAMETER_REQUIRED_FIELDS, diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/index.ts b/packages/apidom-ls/src/config/openapi/responses/lint/index.ts index 0b74e69dd7..001f521c38 100644 --- a/packages/apidom-ls/src/config/openapi/responses/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/responses/lint/index.ts @@ -1,15 +1,8 @@ import allowedFields2_0Lint from './allowed-fields-2-0.ts'; import allowedFields3_0__3_1Lint from './allowed-fields-3-0--3-1.ts'; import valuesTypeLint from './values--type.ts'; -import requiredFields2_0Lint from './required-fields-2-0.ts'; -import requiredFields3_0__3_1Lint from './required-fields-3-0--3-1.ts'; +import requiredFieldsLint from './required-fields.ts'; -const lints = [ - valuesTypeLint, - allowedFields2_0Lint, - allowedFields3_0__3_1Lint, - requiredFields2_0Lint, - requiredFields3_0__3_1Lint, -]; +const lints = [valuesTypeLint, allowedFields2_0Lint, allowedFields3_0__3_1Lint, requiredFieldsLint]; export default lints; diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts deleted file mode 100644 index c72e24470b..0000000000 --- a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-3-0--3-1.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DiagnosticSeverity } from 'vscode-languageserver-types'; - -import ApilintCodes from '../../../codes.ts'; -import { LinterMeta } from '../../../../apidom-language-types.ts'; -import { OpenAPI3 } from '../../target-specs.ts'; - -// eslint-disable-next-line @typescript-eslint/naming-convention -const requiredFields3_0__3_1Lint: LinterMeta = { - code: ApilintCodes.OPENAPI3_0_RESPONSES_REQUIRED_FIELDS, - source: 'apilint', - message: 'Responses Object should define at least one response', - severity: DiagnosticSeverity.Error, - linterFunction: 'apilintOpenAPIEmptyResponses', - marker: 'key', - targetSpecs: OpenAPI3, -}; - -export default requiredFields3_0__3_1Lint; diff --git a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields.ts similarity index 66% rename from packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts rename to packages/apidom-ls/src/config/openapi/responses/lint/required-fields.ts index f7a232a6fa..6759c1013a 100644 --- a/packages/apidom-ls/src/config/openapi/responses/lint/required-fields-2-0.ts +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields.ts @@ -2,17 +2,16 @@ import { DiagnosticSeverity } from 'vscode-languageserver-types'; import ApilintCodes from '../../../codes.ts'; import { LinterMeta } from '../../../../apidom-language-types.ts'; -import { OpenAPI2 } from '../../target-specs.ts'; +import { OpenAPI2, OpenAPI3 } from '../../target-specs.ts'; -// eslint-disable-next-line @typescript-eslint/naming-convention -const requiredFields2_0Lint: LinterMeta = { +const requiredFieldsLint: LinterMeta = { code: ApilintCodes.OPENAPI2_RESPONSES_REQUIRED_FIELDS, source: 'apilint', message: 'Responses Object should define at least one response', severity: DiagnosticSeverity.Error, linterFunction: 'apilintOpenAPIEmptyResponses', marker: 'key', - targetSpecs: OpenAPI2, + targetSpecs: [...OpenAPI2, ...OpenAPI3], }; -export default requiredFields2_0Lint; +export default requiredFieldsLint; diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index c73044f3da..19008cfe97 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -6150,7 +6150,7 @@ describe('apidom-ls-validate', function () { { message: 'Responses Object should define at least one response', severity: 1, - code: 5140002, + code: 3250001, source: 'apilint', range: { start: { line: 7, character: 6 }, end: { line: 7, character: 15 } }, },