diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 1904db860..4c74d6743 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, 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 ac07a4845..001f521c3 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,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 requiredFieldsLint from './required-fields.ts'; -const lints = [valuesTypeLint, allowedFields2_0Lint, allowedFields3_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.ts b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields.ts new file mode 100644 index 000000000..6759c1013 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/responses/lint/required-fields.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, OpenAPI3 } from '../../target-specs.ts'; + +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, ...OpenAPI3], +}; + +export default requiredFieldsLint; diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index e4723eae0..dbc488801 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; + }, + }, ]; 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 51fcd4637..6f13158d5 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 5bcb126e6..2ef654b64 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 000000000..a68b2c832 --- /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 000000000..b17b63741 --- /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 07ca49108..19008cfe9 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: 3250001, + source: 'apilint', + range: { start: { line: 7, character: 6 }, end: { line: 7, character: 15 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }); });