Skip to content

Commit

Permalink
feat(ls): add initial support for OpenAPI 2.0 (#3470)
Browse files Browse the repository at this point in the history
Refs #3103
Replaces #3466

Co-authored-by: Vladimir Gorej <vladimir.gorej@gmail.com>
  • Loading branch information
frantuma and char0n committed Nov 29, 2023
1 parent 54ad3fe commit e47be92
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 12 deletions.
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/apidom-ls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"@swagger-api/apidom-json-pointer": "^0.84.0",
"@swagger-api/apidom-ns-api-design-systems": "^0.84.0",
"@swagger-api/apidom-ns-asyncapi-2": "^0.84.0",
"@swagger-api/apidom-ns-openapi-2": "^0.84.0",
"@swagger-api/apidom-ns-openapi-3-0": "^0.84.0",
"@swagger-api/apidom-ns-openapi-3-1": "^0.84.0",
"@swagger-api/apidom-parser": "^0.84.0",
Expand All @@ -106,6 +107,8 @@
"@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.84.0",
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.84.0",
"@swagger-api/apidom-parser-adapter-json": "^0.84.0",
"@swagger-api/apidom-parser-adapter-openapi-json-2": "^0.84.0",
"@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^0.84.0",
"@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.84.0",
"@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.84.0",
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.84.0",
Expand Down
12 changes: 6 additions & 6 deletions packages/apidom-ls/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import configAsyncapi from './asyncapi/config';
import configOpenapi from './openapi/config';
import configAds from './ads/config';
import configAsyncAPI from './asyncapi/config';
import configOpenAPI from './openapi/config';
import configADS from './ads/config';
import { Metadata } from '../apidom-language-types';
import symbols from './symbols';
import tokens from './tokens';
Expand All @@ -9,9 +9,9 @@ import tokens from './tokens';
export function config(): Metadata {
return {
metadataMaps: {
openapi: configOpenapi,
asyncapi: configAsyncapi,
ads: configAds,
openapi: configOpenAPI,
asyncapi: configAsyncAPI,
ads: configADS,
},
linterFunctions: {},
symbols,
Expand Down
2 changes: 2 additions & 0 deletions packages/apidom-ls/src/config/openapi/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import securityRequirementMeta from './security-requirement/meta';
import securitySchemeMeta from './security-scheme/meta';
import serverMeta from './server/meta';
import serverVariableMeta from './server-variable/meta';
import swaggerMeta from './swagger/meta';
import tagMeta from './tag/meta';
import xmlMeta from './xml/meta';
import schemaMeta from '../common/schema/meta';
Expand Down Expand Up @@ -78,6 +79,7 @@ export default {
securityScheme: securitySchemeMeta,
server: serverMeta,
serverVariable: serverVariableMeta,
swagger: swaggerMeta,
tag: tagMeta,
xml: xmlMeta,
schema: schemaMeta,
Expand Down
5 changes: 5 additions & 0 deletions packages/apidom-ls/src/config/openapi/swagger/completion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ApidomCompletionItem } from '../../../apidom-language-types';

const completion: ApidomCompletionItem[] = [];

export default completion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DocumentationMeta } from '../../../apidom-language-types';

const documentation: DocumentationMeta[] = [];

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

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

const allowedFieldsLint: LinterMeta = {
code: ApilintCodes.NOT_ALLOWED_FIELDS,
source: 'apilint',
message: 'Object includes not allowed fields',
severity: DiagnosticSeverity.Error,
linterFunction: 'allowedFields',
linterParams: [
[
'swagger',
'info',
'host',
'basePath',
'schemes',
'consumes',
'produces',
'paths',
'definitions',
'parameters',
'responses',
'securityDefinitions',
'security',
'tags',
'externalDocs',
],
'x-',
],
marker: 'key',
targetSpecs: [{ namespace: 'openapi', version: '2.0' }],
};

export default allowedFieldsLint;
5 changes: 5 additions & 0 deletions packages/apidom-ls/src/config/openapi/swagger/lint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import allowedFieldsLint from './allowed-fields';

const lints = [allowedFieldsLint];

export default lints;
12 changes: 12 additions & 0 deletions packages/apidom-ls/src/config/openapi/swagger/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lint from './lint';
import completion from './completion';
import documentation from './documentation';
import { FormatMeta } from '../../../apidom-language-types';

const meta: FormatMeta = {
lint,
completion,
documentation,
};

export default meta;
5 changes: 5 additions & 0 deletions packages/apidom-ls/src/config/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ export default [
'identifier',
'license',
'message',
'security',
'parametersDefinitions',
'responsesDefinitions',
'parametersDefinitions',
'definitions',
];
5 changes: 5 additions & 0 deletions packages/apidom-ls/src/config/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ export default [
'messageTraits',
'operationTrait',
'operationTraits',
'security',
'parametersDefinitions',
'responsesDefinitions',
'parametersDefinitions',
'definitions',
];
21 changes: 21 additions & 0 deletions packages/apidom-ls/src/parser-factory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as openapi2AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-2';
import * as openapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-2';
import * as openapi3_0AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-0';
import * as openapi3_0AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-3-0';
import * as openapi3_1AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
Expand All @@ -9,6 +11,7 @@ import * as adsAdapterYaml from '@swagger-api/apidom-parser-adapter-api-design-s
import * as adapterJson from '@swagger-api/apidom-parser-adapter-json';
import * as adapterYaml from '@swagger-api/apidom-parser-adapter-yaml-1-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementAsyncAPI2 } from '@swagger-api/apidom-ns-asyncapi-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI2 } from '@swagger-api/apidom-ns-openapi-2';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_0 } from '@swagger-api/apidom-ns-openapi-3-0';
import { refractorPluginReplaceEmptyElement as refractorPluginReplaceEmptyElementOpenAPI3_1 } from '@swagger-api/apidom-ns-openapi-3-1';
import { TextDocument } from 'vscode-languageserver-textdocument';
Expand Down Expand Up @@ -45,6 +48,24 @@ export async function parse(
options.refractorOpts = { plugins: [refractorPluginReplaceEmptyElementAsyncAPI2()] };
}
result = await asyncapi2AdapterYaml.parse(text, options);
} else if (
contentLanguage.namespace === 'openapi' &&
contentLanguage.version === '2.0' &&
contentLanguage.format === 'JSON'
) {
result = await openapi2AdapterJson.parse(text, { sourceMap: true });
} else if (
contentLanguage.namespace === 'openapi' &&
contentLanguage.version === '2.0' &&
contentLanguage.format === 'YAML'
) {
const options: Record<string, unknown> = {
sourceMap: true,
};
if (registerPlugins) {
options.refractorOpts = { plugins: [refractorPluginReplaceEmptyElementOpenAPI2()] };
}
result = await openapi2AdapterYaml.parse(text, options);
} else if (
contentLanguage.namespace === 'openapi' &&
contentLanguage.version?.startsWith('3.0') &&
Expand Down
11 changes: 11 additions & 0 deletions packages/apidom-ls/src/services/completion/completion-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,17 @@ export class DefaultCompletionService implements CompletionService {
oasItem.insertTextFormat = 2;
oasItem.insertTextMode = 2;
completionList.items.push(oasItem);
const swaggerItem = CompletionItem.create('swagger');
swaggerItem.insertText = `swagger: '2.0'${isEmpty ? '$1' : '\n$1'}`;
swaggerItem.documentation = {
kind: 'markdown',
value:
'**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".',
};
swaggerItem.kind = CompletionItemKind.Keyword;
swaggerItem.insertTextFormat = 2;
swaggerItem.insertTextMode = 2;
completionList.items.push(swaggerItem);
trace('doCompletion - no version', `completionList: ${JSON.stringify(completionList)}`);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ export class DefaultDefinitionService implements DefinitionService {
textDocument.getText(),
this.settings?.defaultContentLanguage,
);
// TODO atm only support and default to OAS 3.1
const specVersion =
contentLanguage.namespace === 'openapi' ? '3.1.0' : getSpecVersion(api);
const specVersion = getSpecVersion(api);

const format = contentLanguage.format ? contentLanguage.format.toLowerCase() : 'json';
const mediaTypePrefix =
Expand Down
4 changes: 1 addition & 3 deletions packages/apidom-ls/src/services/hover/hover-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,7 @@ export class DefaultHoverService implements HoverService {
textDocument.getText(),
this.settings?.defaultContentLanguage,
);
// TODO atm only support and default to OAS 3.1
const nonStrictSpecVersion =
contentLanguage.namespace === 'openapi' ? '3.1.0' : getSpecVersion(api);
const nonStrictSpecVersion = getSpecVersion(api);

const format = contentLanguage.format ? contentLanguage.format.toLowerCase() : 'json';
const mediaTypePrefix =
Expand Down
27 changes: 27 additions & 0 deletions packages/apidom-ls/src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as openapi2AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-2';
import * as openapi2AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-2';
import * as openapi3_0AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-0';
import * as openapi3_0AdapterYaml from '@swagger-api/apidom-parser-adapter-openapi-yaml-3-0';
import * as openapi3_1AdapterJson from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
Expand Down Expand Up @@ -807,6 +809,31 @@ export async function findNamespace(
mediaType: `application/vnd.aai.asyncapi+yaml;version=${version}`,
};
}
if (await openapi2AdapterJson.detect(text)) {
const versionMatch = text.match(openapi2AdapterJson.detectionRegExp);
const version = versionMatch?.groups?.version_json ? versionMatch?.groups?.version_json : '2.0';

return {
namespace: 'openapi',
version,
format: 'JSON',
mediaType: `application/vnd.oai.openapi+json;version=${version}`,
};
}
if (await openapi2AdapterYaml.detect(text)) {
const versionMatch = text.match(openapi2AdapterYaml.detectionRegExp);
const version = versionMatch?.groups?.version_yaml
? versionMatch?.groups?.version_yaml
: versionMatch?.groups?.version_json
? versionMatch?.groups?.version_json
: '2.0';
return {
namespace: 'openapi',
version,
format: 'YAML',
mediaType: `application/vnd.oai.openapi+yaml;version=${version}`,
};
}
if (await openapi3_0AdapterJson.detect(text)) {
const versionMatch = text.match(openapi3_0AdapterJson.detectionRegExp);
const version = versionMatch?.groups?.version_json
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
swagger: "2.0"
unknown: {}
37 changes: 37 additions & 0 deletions packages/apidom-ls/test/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3307,4 +3307,41 @@ describe('apidom-ls-validate', function () {

languageService.terminate();
});

it('oas 2.0 / yaml', async function () {
const validationContext: ValidationContext = {
comments: DiagnosticSeverity.Error,
maxNumberOfProblems: 100,
relatedInformation: false,
};

const spec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'sample-api-openapi-yaml-2-0.yaml'))
.toString();
const doc: TextDocument = TextDocument.create('foo://bar/openapi-2-0.yaml', 'yaml', 0, spec);
const languageService: LanguageService = getLanguageService(contextNoSchema);
const result = await languageService.doValidation(doc, validationContext);
const expected: Diagnostic[] = [
{
code: 15000,
message: 'Object includes not allowed fields',
range: {
end: {
character: 5,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
severity: 1,
source: 'apilint',
},
];

assert.deepEqual(result, expected as Diagnostic[]);

languageService.terminate();
});
});

0 comments on commit e47be92

Please sign in to comment.