Skip to content

Commit

Permalink
feat(ls): add rules for OpenAPI 2.0 Response Object (#3627)
Browse files Browse the repository at this point in the history
Refs #3607
  • Loading branch information
char0n authored Jan 4, 2024
1 parent 2ab2840 commit 2c591b6
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 47 deletions.
7 changes: 7 additions & 0 deletions packages/apidom-ls/src/config/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,13 @@ enum ApilintCodes {
OPENAPI2_ITEMS_FIELD_ENUM_TYPE = 3111200,
OPENAPI2_ITEMS_FIELD_MULTIPLE_OF_TYPE = 3111300,

OPENAPI2_RESPONSE = 3120000,
OPENAPI2_RESPONSE_FIELD_DESCRIPTION_TYPE = 3120100,
OPENAPI2_RESPONSE_FIELD_DESCRIPTION_REQUIRED,
OPENAPI2_RESPONSE_FIELD_SCHEMA_TYPE = 3120200,
OPENAPI2_RESPONSE_FIELD_HEADERS_TYPE = 3120300,
OPENAPI2_RESPONSE_FIELD_EXAMPLES_TYPE = 3120400,

OPENAPI3_0 = 5000000,

OPENAPI3_0_OPENAPI_VALUE_PATTERN_3_0_0 = 5000100,
Expand Down
60 changes: 58 additions & 2 deletions packages/apidom-ls/src/config/openapi/response/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
CompletionFormat,
CompletionType,
} from '../../../apidom-language-types';
import { OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs';
import { OpenAPI2, OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs';

const completion: ApidomCompletionItem[] = [
{
Expand All @@ -17,7 +17,21 @@ const completion: ApidomCompletionItem[] = [
kind: 'markdown',
value: 'A reference to a Response.',
},
targetSpecs: OpenAPI3,
targetSpecs: [...OpenAPI2, ...OpenAPI3],
},
{
label: 'description',
insertText: 'description',
kind: 14,
format: CompletionFormat.QUOTED,
type: CompletionType.PROPERTY,
insertTextFormat: 2,
documentation: {
kind: 'markdown',
value:
'**Required.** A short description of the response. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.',
},
targetSpecs: OpenAPI2,
},
{
label: 'description',
Expand All @@ -33,6 +47,34 @@ const completion: ApidomCompletionItem[] = [
},
targetSpecs: OpenAPI3,
},
{
label: 'schema',
insertText: 'schema',
kind: 14,
format: CompletionFormat.OBJECT,
type: CompletionType.PROPERTY,
insertTextFormat: 2,
documentation: {
kind: 'markdown',
value:
'[Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject)\n\\\n\\\nA definition of the response structure. It can be a primitive, an array or an object. If this field does not exist, it means no content is returned as part of the response. As an extension to the [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject), its root `type` value may also be `"file"`. This SHOULD be accompanied by a relevant `produces` mime-type.',
},
targetSpecs: OpenAPI2,
},
{
label: 'headers',
insertText: 'headers',
kind: 14,
format: CompletionFormat.OBJECT,
type: CompletionType.PROPERTY,
insertTextFormat: 2,
documentation: {
kind: 'markdown',
value:
'[Headers Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#headersObject)\n\\\n\\\nA list of headers that are sent with the response.',
},
targetSpecs: OpenAPI2,
},
{
label: 'headers',
insertText: 'headers',
Expand Down Expand Up @@ -61,6 +103,20 @@ const completion: ApidomCompletionItem[] = [
},
targetSpecs: OpenAPI31,
},
{
label: 'examples',
insertText: 'examples',
kind: 14,
format: CompletionFormat.OBJECT,
type: CompletionType.PROPERTY,
insertTextFormat: 2,
documentation: {
kind: 'markdown',
value:
'[Example Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#exampleObject)\n\\\n\\\nAn example of the response message.',
},
targetSpecs: OpenAPI2,
},
{
label: 'content',
insertText: 'content',
Expand Down
26 changes: 25 additions & 1 deletion packages/apidom-ls/src/config/openapi/response/documentation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs';
import { OpenAPI2, OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs';

const documentation = [
{
target: 'description',
docs: '**REQUIRED**. A description of the response. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.',
targetSpecs: OpenAPI3,
},
{
target: 'description',
docs: '**Required.** A short description of the response. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.',
targetSpecs: OpenAPI2,
},
{
target: 'schema',
docs: '[Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject)\n\\\n\\\nA definition of the response structure. It can be a primitive, an array or an object. If this field does not exist, it means no content is returned as part of the response. As an extension to the [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject), its root `type` value may also be `"file"`. This SHOULD be accompanied by a relevant `produces` mime-type.',
targetSpecs: OpenAPI2,
},
{
target: 'headers',
docs: '[Headers Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#headersObject)\n\\\n\\\nA list of headers that are sent with the response.',
targetSpecs: OpenAPI2,
},
{
target: 'headers',
docs: 'Map[`string`, [Header Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#headerObject) | [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject)]\n\\\n\\\nMaps a header name to its definition. [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) states header names are case insensitive. If a response header is defined with the name `"Content-Type"`, it SHALL be ignored.',
Expand All @@ -16,6 +31,11 @@ const documentation = [
docs: 'Map[`string`, [Header Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#headerObject) | [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#referenceObject)]\n\\\n\\\nMaps a header name to its definition. [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) states header names are case insensitive. If a response header is defined with the name `"Content-Type"`, it SHALL be ignored.',
targetSpecs: OpenAPI31,
},
{
target: 'examples',
docs: '[Example Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#exampleObject)\n\\\n\\\nAn example of the response message.',
targetSpecs: OpenAPI2,
},
{
target: 'content',
docs: 'Map[`string`, [Media Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject)]\n\\\n\\\nA map containing descriptions of potential response payloads. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*',
Expand All @@ -36,6 +56,10 @@ const documentation = [
docs: 'Map[`string`, [Link Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#linkObject) | [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#referenceObject)]\n\\\n\\\nA map of operations links that can be followed from the response. The key of the map is a short name for the link, following the naming constraints of the names for [Component Objects](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#componentsObject).',
targetSpecs: OpenAPI31,
},
{
docs: '#### [Response Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#response-object)\n\nDescribes a single response from an API Operation.\n\n##### Fixed Fields\nField Name | Type | Description\n---|:---:|---\ndescription | `string` | **Required.** A short description of the response. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.\nschema | [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject) | A definition of the response structure. It can be a primitive, an array or an object. If this field does not exist, it means no content is returned as part of the response. As an extension to the [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject), its root `type` value may also be `"file"`. This SHOULD be accompanied by a relevant `produces` mime-type.\nheaders | [Headers Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#headersObject) | A list of headers that are sent with the response.\nexamples | [Example Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#exampleObject) | An example of the response message.\n\n##### Patterned Objects\n\nField Pattern | Type | Description\n---|:---:|---\n^x- | Any | Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See [Vendor Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#vendorExtensions) for further details.\n\n##### Response Object Examples\n\nResponse of an array of a complex type:\n\n```js\n{\n "description": "A complex object array response",\n "schema": {\n "type": "array",\n "items": {\n "$ref": "#/definitions/VeryComplexType"\n }\n }\n}\n```\n\n\n\\\nYAML\n```yaml\ndescription: A complex object array response\nschema:\n type: array\n items:\n $ref: \'#/definitions/VeryComplexType\'\n```\n\nResponse with a string type:\n\n```js\n{\n "description": "A simple string response",\n "schema": {\n "type": "string"\n }\n}\n```\n\n```yaml\ndescription: A simple string response\nschema:\n type: string\n```\n\nResponse with headers:\n\n```js\n{\n "description": "A simple string response",\n "schema": {\n "type": "string"\n },\n "headers": {\n "X-Rate-Limit-Limit": {\n "description": "The number of allowed requests in the current period",\n "type": "integer"\n },\n "X-Rate-Limit-Remaining": {\n "description": "The number of remaining requests in the current period",\n "type": "integer"\n },\n "X-Rate-Limit-Reset": {\n "description": "The number of seconds left in the current period",\n "type": "integer"\n }\n }\n}\n```\n\n```yaml\ndescription: A simple string response\nschema:\n type: string\nheaders:\n X-Rate-Limit-Limit:\n description: The number of allowed requests in the current period\n type: integer\n X-Rate-Limit-Remaining:\n description: The number of remaining requests in the current period\n type: integer\n X-Rate-Limit-Reset:\n description: The number of seconds left in the current period\n type: integer\n```\n\nResponse with no return value:\n\n```js\n{\n "description": "object created"\n}\n```\n\n```yaml\ndescription: object created\n```',
targetSpecs: OpenAPI2,
},
{
docs: '#### [Response Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responseObject)\nDescribes a single response from an API Operation, including design-time, static\n`links` to operations based on the response.\n\n##### Fixed Fields\nField Name | Type | Description\n---|:---:|---\ndescription | `string` | **REQUIRED**. A short description of the response. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.\nheaders | Map[`string`, [Header Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#headerObject) \\| [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject)] | Maps a header name to its definition. [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) states header names are case insensitive. If a response header is defined with the name `"Content-Type"`, it SHALL be ignored.\ncontent | Map[`string`, [Media Type Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject)] | A map containing descriptions of potential response payloads. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*\nlinks | Map[`string`, [Link Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#linkObject) \\| [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject)] | A map of operations links that can be followed from the response. The key of the map is a short name for the link, following the naming constraints of the names for [Component Objects](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#componentsObject).\n\nThis object MAY be extended with [Specification Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions).\n\n##### Response Object Examples\n\nResponse of an array of a complex type:\n\n\n\\\nJSON\n```json\n{\n "description": "A complex object array response",\n "content": {\n "application/json": {\n "schema": {\n "type": "array",\n "items": {\n "$ref": "#/components/schemas/VeryComplexType"\n }\n }\n }\n }\n}\n```\n\n\n\\\nYAML\n```yaml\ndescription: A complex object array response\ncontent:\n application/json:\n schema:\n type: array\n items:\n $ref: \'#/components/schemas/VeryComplexType\'\n```\n\nResponse with a string type:\n\n```json\n{\n "description": "A simple string response",\n "content": {\n "text/plain": {\n "schema": {\n "type": "string"\n }\n }\n }\n\n}\n```\n\n```yaml\ndescription: A simple string response\ncontent:\n text/plain:\n schema:\n type: string\n```\n\nPlain text response with headers:\n\n```json\n{\n "description": "A simple string response",\n "content": {\n "text/plain": {\n "schema": {\n "type": "string",\n "example": "whoa!"\n }\n }\n },\n "headers": {\n "X-Rate-Limit-Limit": {\n "description": "The number of allowed requests in the current period",\n "schema": {\n "type": "integer"\n }\n },\n "X-Rate-Limit-Remaining": {\n "description": "The number of remaining requests in the current period",\n "schema": {\n "type": "integer"\n }\n },\n "X-Rate-Limit-Reset": {\n "description": "The number of seconds left in the current period",\n "schema": {\n "type": "integer"\n }\n }\n }\n}\n```\n\n```yaml\ndescription: A simple string response\ncontent:\n text/plain:\n schema:\n type: string\n example: \'whoa!\'\nheaders:\n X-Rate-Limit-Limit:\n description: The number of allowed requests in the current period\n schema:\n type: integer\n X-Rate-Limit-Remaining:\n description: The number of remaining requests in the current period\n schema:\n type: integer\n X-Rate-Limit-Reset:\n description: The number of seconds left in the current period\n schema:\n type: integer\n```\n\nResponse with no return value:\n\n```json\n{\n "description": "object created"\n}\n```\n\n```yaml\ndescription: object created\n```',
targetSpecs: OpenAPI30,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';
import { OpenAPI2 } from '../../target-specs';

// eslint-disable-next-line @typescript-eslint/naming-convention
const allowedFields2_0Lint: LinterMeta = {
code: ApilintCodes.NOT_ALLOWED_FIELDS,
source: 'apilint',
message: 'Object includes not allowed fields',
severity: DiagnosticSeverity.Error,
linterFunction: 'allowedFields',
linterParams: [['description', 'schema', 'headers', 'examples', '$ref'], 'x-'],
marker: 'key',
targetSpecs: OpenAPI2,
};

export default allowedFields2_0Lint;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';
import { OpenAPI3 } from '../../target-specs';
import { OpenAPI2, OpenAPI3 } from '../../target-specs';

const descriptionRequiredLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_RESPONSE_FIELD_DESCRIPTION_REQUIRED,
code: ApilintCodes.OPENAPI2_RESPONSE_FIELD_DESCRIPTION_REQUIRED,
source: 'apilint',
message: "should always have a 'description'",
severity: DiagnosticSeverity.Error,
Expand All @@ -28,7 +28,7 @@ const descriptionRequiredLint: LinterMeta = {
params: ['$ref'],
},
],
targetSpecs: OpenAPI3,
targetSpecs: [...OpenAPI2, ...OpenAPI3],
};

export default descriptionRequiredLint;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';
import { OpenAPI3 } from '../../target-specs';
import { OpenAPI2, OpenAPI3 } from '../../target-specs';

const descriptionTypeLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_RESPONSE_FIELD_DESCRIPTION_TYPE,
code: ApilintCodes.OPENAPI2_RESPONSE_FIELD_DESCRIPTION_TYPE,
source: 'apilint',
message: 'description must be a string',
severity: DiagnosticSeverity.Error,
Expand All @@ -14,7 +14,7 @@ const descriptionTypeLint: LinterMeta = {
marker: 'value',
target: 'description',
data: {},
targetSpecs: OpenAPI3,
targetSpecs: [...OpenAPI2, ...OpenAPI3],
};

export default descriptionTypeLint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';
import { OpenAPI2 } from '../../target-specs';

const examplesTypeLint: LinterMeta = {
code: ApilintCodes.OPENAPI2_RESPONSE_FIELD_EXAMPLES_TYPE,
source: 'apilint',
message: '"examples" must be an object',
severity: DiagnosticSeverity.Error,
linterFunction: 'apilintElementOrClass',
linterParams: ['example'],
marker: 'key',
markerTarget: 'examples',
target: 'examples',
data: {},
targetSpecs: OpenAPI2,
};

export default examplesTypeLint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes';
import { LinterMeta } from '../../../../apidom-language-types';
import { OpenAPI2 } from '../../target-specs';

const headersTypeLint: LinterMeta = {
code: ApilintCodes.OPENAPI2_RESPONSE_FIELD_HEADERS_TYPE,
source: 'apilint',
message: '"headers" must be an object',
severity: DiagnosticSeverity.Error,
linterFunction: 'apilintElementOrClass',
linterParams: ['headers'],
marker: 'key',
markerTarget: 'headers',
target: 'headers',
data: {},
targetSpecs: OpenAPI2,
};

export default headersTypeLint;
8 changes: 8 additions & 0 deletions packages/apidom-ls/src/config/openapi/response/lint/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import allowedFields2_0Lint from './allowed-fields-2-0';
import allowedFields3_0Lint from './allowed-fields-3-0';
import allowedFields3_1Lint from './allowed-fields-3-1';
import descriptionTypeLint from './description--type';
import descriptionRequiredLint from './description--required';
import headersValuesTypeLint from './headers--values-type';
import headersTypeLint from './headers--type';
import contentValuesTypeLint from './content--values-type';
import linksValuesTypeLint from './links--values-type';
import schemaTypeLint from './schema--type';
import examplesTypeLint from './examples--type';

const lints = [
descriptionTypeLint,
descriptionRequiredLint,
headersTypeLint,
headersValuesTypeLint,
contentValuesTypeLint,
linksValuesTypeLint,
schemaTypeLint,
examplesTypeLint,
allowedFields2_0Lint,
allowedFields3_0Lint,
allowedFields3_1Lint,
];
Expand Down
Loading

0 comments on commit 2c591b6

Please sign in to comment.