Skip to content

Commit e47be92

Browse files
frantumachar0n
andauthored
feat(ls): add initial support for OpenAPI 2.0 (#3470)
Refs #3103 Replaces #3466 Co-authored-by: Vladimir Gorej <[email protected]>
1 parent 54ad3fe commit e47be92

18 files changed

+187
-12
lines changed

package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/apidom-ls/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"@swagger-api/apidom-json-pointer": "^0.84.0",
9999
"@swagger-api/apidom-ns-api-design-systems": "^0.84.0",
100100
"@swagger-api/apidom-ns-asyncapi-2": "^0.84.0",
101+
"@swagger-api/apidom-ns-openapi-2": "^0.84.0",
101102
"@swagger-api/apidom-ns-openapi-3-0": "^0.84.0",
102103
"@swagger-api/apidom-ns-openapi-3-1": "^0.84.0",
103104
"@swagger-api/apidom-parser": "^0.84.0",
@@ -106,6 +107,8 @@
106107
"@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.84.0",
107108
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.84.0",
108109
"@swagger-api/apidom-parser-adapter-json": "^0.84.0",
110+
"@swagger-api/apidom-parser-adapter-openapi-json-2": "^0.84.0",
111+
"@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^0.84.0",
109112
"@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.84.0",
110113
"@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.84.0",
111114
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.84.0",

packages/apidom-ls/src/config/config.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import configAsyncapi from './asyncapi/config';
2-
import configOpenapi from './openapi/config';
3-
import configAds from './ads/config';
1+
import configAsyncAPI from './asyncapi/config';
2+
import configOpenAPI from './openapi/config';
3+
import configADS from './ads/config';
44
import { Metadata } from '../apidom-language-types';
55
import symbols from './symbols';
66
import tokens from './tokens';
@@ -9,9 +9,9 @@ import tokens from './tokens';
99
export function config(): Metadata {
1010
return {
1111
metadataMaps: {
12-
openapi: configOpenapi,
13-
asyncapi: configAsyncapi,
14-
ads: configAds,
12+
openapi: configOpenAPI,
13+
asyncapi: configAsyncAPI,
14+
ads: configADS,
1515
},
1616
linterFunctions: {},
1717
symbols,

packages/apidom-ls/src/config/openapi/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import securityRequirementMeta from './security-requirement/meta';
3030
import securitySchemeMeta from './security-scheme/meta';
3131
import serverMeta from './server/meta';
3232
import serverVariableMeta from './server-variable/meta';
33+
import swaggerMeta from './swagger/meta';
3334
import tagMeta from './tag/meta';
3435
import xmlMeta from './xml/meta';
3536
import schemaMeta from '../common/schema/meta';
@@ -78,6 +79,7 @@ export default {
7879
securityScheme: securitySchemeMeta,
7980
server: serverMeta,
8081
serverVariable: serverVariableMeta,
82+
swagger: swaggerMeta,
8183
tag: tagMeta,
8284
xml: xmlMeta,
8385
schema: schemaMeta,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ApidomCompletionItem } from '../../../apidom-language-types';
2+
3+
const completion: ApidomCompletionItem[] = [];
4+
5+
export default completion;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { DocumentationMeta } from '../../../apidom-language-types';
2+
3+
const documentation: DocumentationMeta[] = [];
4+
5+
export default documentation;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { DiagnosticSeverity } from 'vscode-languageserver-types';
2+
3+
import ApilintCodes from '../../../codes';
4+
import { LinterMeta } from '../../../../apidom-language-types';
5+
6+
const allowedFieldsLint: LinterMeta = {
7+
code: ApilintCodes.NOT_ALLOWED_FIELDS,
8+
source: 'apilint',
9+
message: 'Object includes not allowed fields',
10+
severity: DiagnosticSeverity.Error,
11+
linterFunction: 'allowedFields',
12+
linterParams: [
13+
[
14+
'swagger',
15+
'info',
16+
'host',
17+
'basePath',
18+
'schemes',
19+
'consumes',
20+
'produces',
21+
'paths',
22+
'definitions',
23+
'parameters',
24+
'responses',
25+
'securityDefinitions',
26+
'security',
27+
'tags',
28+
'externalDocs',
29+
],
30+
'x-',
31+
],
32+
marker: 'key',
33+
targetSpecs: [{ namespace: 'openapi', version: '2.0' }],
34+
};
35+
36+
export default allowedFieldsLint;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import allowedFieldsLint from './allowed-fields';
2+
3+
const lints = [allowedFieldsLint];
4+
5+
export default lints;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lint from './lint';
2+
import completion from './completion';
3+
import documentation from './documentation';
4+
import { FormatMeta } from '../../../apidom-language-types';
5+
6+
const meta: FormatMeta = {
7+
lint,
8+
completion,
9+
documentation,
10+
};
11+
12+
export default meta;

packages/apidom-ls/src/config/symbols.ts

+5
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ export default [
3434
'identifier',
3535
'license',
3636
'message',
37+
'security',
38+
'parametersDefinitions',
39+
'responsesDefinitions',
40+
'parametersDefinitions',
41+
'definitions',
3742
];

packages/apidom-ls/src/config/tokens.ts

+5
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,9 @@ export default [
7575
'messageTraits',
7676
'operationTrait',
7777
'operationTraits',
78+
'security',
79+
'parametersDefinitions',
80+
'responsesDefinitions',
81+
'parametersDefinitions',
82+
'definitions',
7883
];

packages/apidom-ls/src/parser-factory.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as openapi2AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-2';
2+
import * as openapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-2';
13
import * as openapi3_0AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-0';
24
import * as openapi3_0AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-3-0';
35
import * as openapi3_1AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
@@ -9,6 +11,7 @@ import * as adsAdapterYaml from '@swagger-api/apidom-parser-adapter-api-design-s
911
import * as adapterJson from '@swagger-api/apidom-parser-adapter-json';
1012
import * as adapterYaml from '@swagger-api/apidom-parser-adapter-yaml-1-2';
1113
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementAsyncAPI2 } from '@swagger-api/apidom-ns-asyncapi-2';
14+
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI2 } from '@swagger-api/apidom-ns-openapi-2';
1215
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_0 } from '@swagger-api/apidom-ns-openapi-3-0';
1316
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_1 } from '@swagger-api/apidom-ns-openapi-3-1';
1417
import { TextDocument } from 'vscode-languageserver-textdocument';
@@ -45,6 +48,24 @@ export async function parse(
4548
options.refractorOpts = { plugins: [refractorPluginReplaceEmptyElementAsyncAPI2()] };
4649
}
4750
result = await asyncapi2AdapterYaml.parse(text, options);
51+
} else if (
52+
contentLanguage.namespace === 'openapi' &&
53+
contentLanguage.version === '2.0' &&
54+
contentLanguage.format === 'JSON'
55+
) {
56+
result = await openapi2AdapterJson.parse(text, { sourceMap: true });
57+
} else if (
58+
contentLanguage.namespace === 'openapi' &&
59+
contentLanguage.version === '2.0' &&
60+
contentLanguage.format === 'YAML'
61+
) {
62+
const options: Record<string, unknown> = {
63+
sourceMap: true,
64+
};
65+
if (registerPlugins) {
66+
options.refractorOpts = { plugins: [refractorPluginReplaceEmptyElementOpenAPI2()] };
67+
}
68+
result = await openapi2AdapterYaml.parse(text, options);
4869
} else if (
4970
contentLanguage.namespace === 'openapi' &&
5071
contentLanguage.version?.startsWith('3.0') &&

packages/apidom-ls/src/services/completion/completion-service.ts

+11
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,17 @@ export class DefaultCompletionService implements CompletionService {
325325
oasItem.insertTextFormat = 2;
326326
oasItem.insertTextMode = 2;
327327
completionList.items.push(oasItem);
328+
const swaggerItem = CompletionItem.create('swagger');
329+
swaggerItem.insertText = `swagger: '2.0'${isEmpty ? '$1' : '\n$1'}`;
330+
swaggerItem.documentation = {
331+
kind: 'markdown',
332+
value:
333+
'**REQUIRED**. Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be "2.0".',
334+
};
335+
swaggerItem.kind = CompletionItemKind.Keyword;
336+
swaggerItem.insertTextFormat = 2;
337+
swaggerItem.insertTextMode = 2;
338+
completionList.items.push(swaggerItem);
328339
trace('doCompletion - no version', `completionList: ${JSON.stringify(completionList)}`);
329340
}
330341

packages/apidom-ls/src/services/definition/definition-service.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ export class DefaultDefinitionService implements DefinitionService {
7979
textDocument.getText(),
8080
this.settings?.defaultContentLanguage,
8181
);
82-
// TODO atm only support and default to OAS 3.1
83-
const specVersion =
84-
contentLanguage.namespace === 'openapi' ? '3.1.0' : getSpecVersion(api);
82+
const specVersion = getSpecVersion(api);
8583

8684
const format = contentLanguage.format ? contentLanguage.format.toLowerCase() : 'json';
8785
const mediaTypePrefix =

packages/apidom-ls/src/services/hover/hover-service.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,7 @@ export class DefaultHoverService implements HoverService {
183183
textDocument.getText(),
184184
this.settings?.defaultContentLanguage,
185185
);
186-
// TODO atm only support and default to OAS 3.1
187-
const nonStrictSpecVersion =
188-
contentLanguage.namespace === 'openapi' ? '3.1.0' : getSpecVersion(api);
186+
const nonStrictSpecVersion = getSpecVersion(api);
189187

190188
const format = contentLanguage.format ? contentLanguage.format.toLowerCase() : 'json';
191189
const mediaTypePrefix =

packages/apidom-ls/src/utils/utils.ts

+27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as openapi2AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-2';
2+
import * as openapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-2';
13
import * as openapi3_0AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-0';
24
import * as openapi3_0AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-3-0';
35
import * as openapi3_1AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
@@ -807,6 +809,31 @@ export async function findNamespace(
807809
mediaType: `application/vnd.aai.asyncapi+yaml;version=${version}`,
808810
};
809811
}
812+
if (await openapi2AdapterJson.detect(text)) {
813+
const versionMatch = text.match(openapi2AdapterJson.detectionRegExp);
814+
const version = versionMatch?.groups?.version_json ? versionMatch?.groups?.version_json : '2.0';
815+
816+
return {
817+
namespace: 'openapi',
818+
version,
819+
format: 'JSON',
820+
mediaType: `application/vnd.oai.openapi+json;version=${version}`,
821+
};
822+
}
823+
if (await openapi2AdapterYaml.detect(text)) {
824+
const versionMatch = text.match(openapi2AdapterYaml.detectionRegExp);
825+
const version = versionMatch?.groups?.version_yaml
826+
? versionMatch?.groups?.version_yaml
827+
: versionMatch?.groups?.version_json
828+
? versionMatch?.groups?.version_json
829+
: '2.0';
830+
return {
831+
namespace: 'openapi',
832+
version,
833+
format: 'YAML',
834+
mediaType: `application/vnd.oai.openapi+yaml;version=${version}`,
835+
};
836+
}
810837
if (await openapi3_0AdapterJson.detect(text)) {
811838
const versionMatch = text.match(openapi3_0AdapterJson.detectionRegExp);
812839
const version = versionMatch?.groups?.version_json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
swagger: "2.0"
2+
unknown: {}

packages/apidom-ls/test/validate.ts

+37
Original file line numberDiff line numberDiff line change
@@ -3307,4 +3307,41 @@ describe('apidom-ls-validate', function () {
33073307

33083308
languageService.terminate();
33093309
});
3310+
3311+
it('oas 2.0 / yaml', async function () {
3312+
const validationContext: ValidationContext = {
3313+
comments: DiagnosticSeverity.Error,
3314+
maxNumberOfProblems: 100,
3315+
relatedInformation: false,
3316+
};
3317+
3318+
const spec = fs
3319+
.readFileSync(path.join(__dirname, 'fixtures', 'sample-api-openapi-yaml-2-0.yaml'))
3320+
.toString();
3321+
const doc: TextDocument = TextDocument.create('foo://bar/openapi-2-0.yaml', 'yaml', 0, spec);
3322+
const languageService: LanguageService = getLanguageService(contextNoSchema);
3323+
const result = await languageService.doValidation(doc, validationContext);
3324+
const expected: Diagnostic[] = [
3325+
{
3326+
code: 15000,
3327+
message: 'Object includes not allowed fields',
3328+
range: {
3329+
end: {
3330+
character: 5,
3331+
line: 0,
3332+
},
3333+
start: {
3334+
character: 0,
3335+
line: 0,
3336+
},
3337+
},
3338+
severity: 1,
3339+
source: 'apilint',
3340+
},
3341+
];
3342+
3343+
assert.deepEqual(result, expected as Diagnostic[]);
3344+
3345+
languageService.terminate();
3346+
});
33103347
});

0 commit comments

Comments
 (0)