Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ls): add initial support for OpenAPI 2.0 #3470

Merged
merged 2 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
});
});