Skip to content

Commit

Permalink
feat(reference): add parser plugin for parsing dehydrated ApiDOM (#3892)
Browse files Browse the repository at this point in the history
Refs #3889
  • Loading branch information
char0n committed Mar 5, 2024
1 parent 3246c9f commit 6b97051
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 4 deletions.
26 changes: 22 additions & 4 deletions packages/apidom-reference/README.md
Expand Up @@ -101,6 +101,20 @@ so providing it is always a better option.

Parse component comes with number of default parser plugins.

#### [apidom-json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/apidom-json)

Parses dehydrated ApiDOM structure and hydrates it.
This parser plugin is uniquely identified by `apidom-json` name.

Supported media types are:

```js
[
'application/vnd.apidom',
'application/vnd.apidom+json',
]
```

#### [openapi-json-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/openapi-json-2)

Wraps [@swagger-api/apidom-parser-adapter-openapi-json-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-openapi-json-2) package
Expand Down Expand Up @@ -361,7 +375,6 @@ returns `true` or until entire list of parser plugins is exhausted (throws error
OpenApiYaml2Parser({ allowEmpty: true, sourceMap: false }),
OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }),
OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }),
AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }),
Expand All @@ -370,6 +383,7 @@ returns `true` or until entire list of parser plugins is exhausted (throws error
WorkflowsYaml1Parser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }),
ApiDOMJsonParser({ allowEmpty: true, sourceMap: false }),
JsonParser({ allowEmpty: true, sourceMap: false }),
YamlParser({ allowEmpty: true, sourceMap: false }),
BinaryParser({ allowEmpty: true }),
Expand All @@ -391,6 +405,7 @@ import AsyncApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/asy
import AsyncApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2';
import WorkflowsJson1Parser from '@swagger-api/apidom-reference/parse/parsers/workflows-json-1';
import WorkflowsYaml1Parser from '@swagger-api/apidom-reference/parse/parsers/workflows-yaml-1';
import ApiDOMJsonParser from '@swagger-api/apidom-reference/parse/parsers/apidom-json';
import ApiDesignSystemsJsonParser from '@swagger-api/apidom-reference/parse/parsers/api-design-systems-json';
import ApiDesignSystemsYamlParser from '@swagger-api/apidom-reference/parse/parsers/api-design-systems-json';
import JsonParser from '@swagger-api/apidom-reference/parse/parsers/json';
Expand All @@ -411,6 +426,7 @@ options.parse.parsers = [
WorkflowsYaml1Parser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }),
ApiDOMJsonParser({ allowEmpty: true, sourceMap: false }),
YamlParser({ allowEmpty: true, sourceMap: false }),
JsonParser({ allowEmpty: true, sourceMap: false }),
BinaryParser({ allowEmpty: true }),
Expand All @@ -431,6 +447,7 @@ import AsyncApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/asy
import AsyncApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2';
import WorkflowsJson1Parser from '@swagger-api/apidom-reference/parse/parsers/workflows-json-1';
import WorkflowsYaml1Parser from '@swagger-api/apidom-reference/parse/parsers/workflows-yaml-1';
import ApiDOMJsonParser from '@swagger-api/apidom-reference/parse/parsers/apidom-json';
import ApiDesignSystemsJsonParser from '@swagger-api/apidom-reference/parse/parsers/api-design-systems-json';
import ApiDesignSystemsYamlParser from '@swagger-api/apidom-reference/parse/parsers/api-design-systems-json';
import JsonParser from '@swagger-api/apidom-reference/parse/parsers/json';
Expand All @@ -443,18 +460,19 @@ await parse('/home/user/oas.json', {
parsers: [
OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml2Parser({ allowEmpty: true, sourceMap: false }),
OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }),
OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }),
OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }),
OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }),
AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }),
AsyncApiYaml2Parser({ allowEmpty: true, sourceMap: false }),
WorkflowsJson1Parser({ allowEmpty: true, sourceMap: false }),
WorkflowsYaml1Parser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }),
YamlParser({ allowEmpty: true, sourceMap: false }),
ApiDOMJsonParser({ allowEmpty: true, sourceMap: false }),
JsonParser({ allowEmpty: true, sourceMap: false }),
YamlParser({ allowEmpty: true, sourceMap: false }),
BinaryParser({ allowEmpty: true }),
],
},
Expand Down
5 changes: 5 additions & 0 deletions packages/apidom-reference/package.json
Expand Up @@ -103,6 +103,11 @@
"require": "./cjs/parse/parsers/workflows-yaml-1/index.cjs",
"types": "./types/parse/parsers/workflows-yaml-1/index.d.ts"
},
"./parse/parsers/apidom-json": {
"import": "./es/parse/parsers/apidom-json/index.mjs",
"require": "./cjs/parse/parsers/apidom-json/index.cjs",
"types": "./types/parse/parsers/apidom-json/index.d.ts"
},
"./parse/parsers/binary": {
"browser": {
"import": "./es/parse/parsers/binary/index-browser.mjs",
Expand Down
2 changes: 2 additions & 0 deletions packages/apidom-reference/src/configuration/saturated.ts
Expand Up @@ -16,6 +16,7 @@ import AsyncApiJson2Parser from '../parse/parsers/asyncapi-json-2';
import AsyncApiYaml2Parser from '../parse/parsers/asyncapi-yaml-2';
import WorkflowsJson1Parser from '../parse/parsers/workflows-json-1';
import WorkflowsYaml1Parser from '../parse/parsers/workflows-yaml-1';
import ApiDOMJsonParser from '../parse/parsers/apidom-json';
import JsonParser from '../parse/parsers/json';
import YamlParser from '../parse/parsers/yaml-1-2';
import BinaryParser from '../parse/parsers/binary/index-node';
Expand All @@ -40,6 +41,7 @@ options.parse.parsers = [
WorkflowsYaml1Parser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }),
ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }),
ApiDOMJsonParser({ allowEmpty: true, sourceMap: false }),
JsonParser({ allowEmpty: true, sourceMap: false }),
YamlParser({ allowEmpty: true, sourceMap: false }),
BinaryParser({ allowEmpty: true }),
Expand Down
64 changes: 64 additions & 0 deletions packages/apidom-reference/src/parse/parsers/apidom-json/index.ts
@@ -0,0 +1,64 @@
import stampit from 'stampit';
import {
ParseResultElement,
isParseResultElement,
namespace as baseNamespace,
} from '@swagger-api/apidom-core';

import ParserError from '../../../errors/ParserError';
import { Parser as IParser, File as IFile } from '../../../types';
import Parser from '../Parser';

const ApiDOMJsonParser: stampit.Stamp<IParser> = stampit(Parser, {
props: {
name: 'apidom-json',
fileExtensions: ['.json'],
mediaTypes: ['application/vnd.apidom', 'application/vnd.apidom+json'],
namespace: baseNamespace,
},
init({ namespace } = {}) {
this.namespace = namespace ?? this.namespace;
},
methods: {
canParse(file: IFile): boolean {
const hasSupportedFileExtension =
this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension);
const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType);

if (!hasSupportedFileExtension) return false;
if (hasSupportedMediaType) return true;
if (!hasSupportedMediaType) {
try {
return this.namespace.fromRefract(JSON.parse(file.toString())) && true;
} catch {
return false;
}
}
return false;
},
parse(file: IFile): ParseResultElement {
const source = file.toString();
const namespace = this['apidom-json']?.namespace ?? this.namespace;

// allow empty files
if (this.allowEmpty && source.trim() === '') {
return new ParseResultElement();
}

try {
const element = namespace.fromRefract(JSON.parse(source));

if (!isParseResultElement(element)) {
element.classes.push('result');
return new ParseResultElement([element]);
}

return element;
} catch (error: unknown) {
throw new ParserError(`Error parsing "${file.uri}"`, { cause: error });
}
},
},
});

export default ApiDOMJsonParser;
@@ -0,0 +1,12 @@
{
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": { "element": "string", "content": "a" },
"value": { "element": "string", "content": "b" }
}
}
]
}
163 changes: 163 additions & 0 deletions packages/apidom-reference/test/parse/parsers/apidom-json/index.ts
@@ -0,0 +1,163 @@
import fs from 'node:fs';
import path from 'node:path';
import { assert } from 'chai';
import { isParseResultElement } from '@swagger-api/apidom-core';

import { ParserError, File } from '../../../../src';
import ApiDOMJsonParser from '../../../../src/parse/parsers/apidom-json';

describe('parsers', function () {
context('ApiDOMJsonParser', function () {
context('canParse', function () {
context('given file with .json extension', function () {
context('and with proper media type', function () {
specify('should return true', async function () {
const file1 = File({
uri: '/path/to/apidom.json',
mediaType: 'application/vnd.apidom',
});
const file2 = File({
uri: '/path/to/apidom.json',
mediaType: 'application/vnd.apidom+json',
});
const parser = ApiDOMJsonParser();

assert.isTrue(await parser.canParse(file1));
assert.isTrue(await parser.canParse(file2));
});
});

context('and with improper media type', function () {
specify('should return false', async function () {
const file = File({
uri: '/path/to/apidom.json',
mediaType: 'application/vnd.aai.asyncapi+json;version=2.6.0',
});
const parser = ApiDOMJsonParser();

assert.isFalse(await parser.canParse(file));
});
});
});

context('given file with unknown extension', function () {
specify('should return false', async function () {
const file = File({
uri: '/path/to/apidom.yaml',
mediaType: 'application/vnd.apidom',
});
const parser = ApiDOMJsonParser();

assert.isFalse(await parser.canParse(file));
});
});

context('given file with no extension', function () {
specify('should return false', async function () {
const file = File({
uri: '/path/to/apidom',
mediaType: 'application/vnd.apidom',
});
const parser = ApiDOMJsonParser();

assert.isFalse(await parser.canParse(file));
});
});

context('given file with supported extension', function () {
context('and file data is buffer and can be detected as ApiDOM', function () {
specify('should return true', async function () {
const url = path.join(__dirname, 'fixtures', 'apidom.json');
const file = File({
uri: '/path/to/apidom.json',
data: fs.readFileSync(url),
});
const parser = ApiDOMJsonParser();

assert.isTrue(await parser.canParse(file));
});
});

context('and file data is string and can be detected as ApiDOM', function () {
specify('should return true', async function () {
const url = path.join(__dirname, 'fixtures', 'apidom.json');
const file = File({
uri: '/path/to/apidom.json',
data: fs.readFileSync(url).toString(),
});
const parser = ApiDOMJsonParser();

assert.isTrue(await parser.canParse(file));
});
});
});
});

context('parse', function () {
context('given ApiDOM JSON data', function () {
specify('should return parse result', async function () {
const uri = path.join(__dirname, 'fixtures', 'apidom.json');
const data = fs.readFileSync(uri).toString();
const file = File({
uri,
data,
mediaType: 'application/vnd.apidom+json',
});
const parser = ApiDOMJsonParser();
const parseResult = await parser.parse(file);

assert.isTrue(isParseResultElement(parseResult));
});
});

context('given ApiDOM JSON data as buffer', function () {
specify('should return parse result', async function () {
const uri = path.join(__dirname, 'fixtures', 'apidom.json');
const data = fs.readFileSync(uri);
const file = File({
uri,
data,
mediaType: 'application/vnd.apidom+json',
});
const parser = ApiDOMJsonParser();
const parseResult = await parser.parse(file);

assert.isTrue(isParseResultElement(parseResult));
});
});

context('given data that is not an ApiDOM JSON data', function () {
specify('should throw error', async function () {
const file = File({
uri: '/path/to/file.json',
data: 1,
mediaType: 'application/vnd.apidom+json',
});
const parser = ApiDOMJsonParser();

try {
await parser.parse(file);
assert.fail('Should throw ParserError');
} catch (e) {
assert.instanceOf(e, ParserError);
}
});
});

context('given empty file', function () {
specify('should return empty parse result', async function () {
const file = File({
uri: '/path/to/file.json',
data: '',
mediaType: 'application/vnd.apidom+json',
});
const parser = ApiDOMJsonParser();
const parseResult = await parser.parse(file);

assert.isTrue(isParseResultElement(parseResult));
assert.isTrue(parseResult.isEmpty);
});
});
});
});
});

0 comments on commit 6b97051

Please sign in to comment.