From 7100994721e0612d0a39b9acba9969a0baaba060 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Fri, 17 Jun 2022 11:42:38 +0200 Subject: [PATCH] feat(reference): add support for API Design Systems Refs #1603 --- package-lock.json | 4 + packages/apidom-reference/README.md | 41 +- packages/apidom-reference/package.json | 2 + packages/apidom-reference/src/index.ts | 2 + .../apidom-reference/src/options/index.ts | 6 +- .../index.ts | 31 ++ .../index.ts | 31 ++ .../fixtures/api-design-systems.json | 356 ++++++++++++++++++ .../index.ts | 172 +++++++++ .../fixtures/api-design-systems.yaml | 183 +++++++++ .../index.ts | 195 ++++++++++ 11 files changed, 1021 insertions(+), 2 deletions(-) create mode 100644 packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts create mode 100644 packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts create mode 100644 packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/fixtures/api-design-systems.json create mode 100644 packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts create mode 100644 packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/fixtures/api-design-systems.yaml create mode 100644 packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts diff --git a/package-lock.json b/package-lock.json index 7c2ad6f05c..0ee874ee3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29383,6 +29383,8 @@ "@swagger-api/apidom-json-pointer": "^0.30.0", "@swagger-api/apidom-ns-asyncapi-2": "^0.30.0", "@swagger-api/apidom-ns-openapi-3-1": "^0.30.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.30.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-json": "^0.30.1", @@ -35811,6 +35813,8 @@ "@swagger-api/apidom-json-pointer": "^0.30.0", "@swagger-api/apidom-ns-asyncapi-2": "^0.30.0", "@swagger-api/apidom-ns-openapi-3-1": "^0.30.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.30.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-json": "^0.30.1", diff --git a/packages/apidom-reference/README.md b/packages/apidom-reference/README.md index c49349ad8b..b67915bc81 100644 --- a/packages/apidom-reference/README.md +++ b/packages/apidom-reference/README.md @@ -71,7 +71,7 @@ so providing it is always a better option. ### Parser plugins -Parse component comes with six (6) default parser plugins. +Parse component comes with number of default parser plugins. #### [openapi-json-3-1](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-openapi-json-3-1) @@ -146,6 +146,35 @@ Supported media types are: ] ``` +#### [api-design-systems-json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-json) + +Wraps [@swagger-api/apidom-parser-adapter-api-design-systsems-json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-api-design-systems-json) package +and is uniquely identified by `api-design-systems-json` name. + +Supported media types are: + +```js +[ + 'application/vnd.aai.apidesignsystems;version=2021-05-07', + 'application/vnd.aai.apidesignsystems+json;version=2021-05-07' +] +``` + +#### [api-design-systems-yaml](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-yaml) + +Wraps [@swagger-api/apidom-parser-adapter-api-design-systems-yaml](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-api-design-systems-yaml) package +and is uniquely identified by `api-design-systems-yaml` name. + + +Supported media types are: + +```js +[ + 'application/vnd.aai.apidesignsystems;version=2021-05-07', + 'application/vnd.aai.apidesignsystems+yaml;version=2021-05-07' +] +``` + #### [json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-json) Wraps [@swagger-api/apidom-parser-adapter-json](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-json) package @@ -194,6 +223,8 @@ returns `true` or until entire list of parser plugins is exhausted (throws error OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }), AsyncApiYaml2Parser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }), JsonParser({ allowEmpty: true, sourceMap: false }), YamlParser({ allowEmpty: true, sourceMap: false }), BinaryParser({ allowEmpty: true }), @@ -210,6 +241,8 @@ import { OpenApiYaml3_1Parser, AsyncApiJson2Parser, AsyncApiYaml2Parser, + ApiDesignSystemsJsonParser, + ApiDesignSystemsYamlParser, JsonParser, YamlParser, BinaryParser, @@ -220,6 +253,8 @@ options.parse.parsers = [ OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }), AsyncApiYaml2Parser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }), YamlParser({ allowEmpty: true, sourceMap: false }), JsonParser({ allowEmpty: true, sourceMap: false }), BinaryParser({ allowEmpty: true }), @@ -235,6 +270,8 @@ import { OpenApiYaml3_1Parser, AsyncApiJson2Parser, AsyncApiYaml2Parser, + ApiDesignSystemsJsonParser, + ApiDesignSystemsYamlParser, JsonParser, YamlParser, BinaryParser, @@ -248,6 +285,8 @@ await parse('/home/user/oas.json', { OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }), AsyncApiYaml2Parser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }), YamlParser({ allowEmpty: true, sourceMap: false }), JsonParser({ allowEmpty: true, sourceMap: false }), BinaryParser({ allowEmpty: true }), diff --git a/packages/apidom-reference/package.json b/packages/apidom-reference/package.json index e9c6a0f3a8..5c81ea3e8b 100644 --- a/packages/apidom-reference/package.json +++ b/packages/apidom-reference/package.json @@ -40,6 +40,8 @@ "@swagger-api/apidom-json-pointer": "^0.30.0", "@swagger-api/apidom-ns-asyncapi-2": "^0.30.0", "@swagger-api/apidom-ns-openapi-3-1": "^0.30.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.30.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.30.1", "@swagger-api/apidom-parser-adapter-json": "^0.30.1", diff --git a/packages/apidom-reference/src/index.ts b/packages/apidom-reference/src/index.ts index de639321f6..3063a81e7d 100644 --- a/packages/apidom-reference/src/index.ts +++ b/packages/apidom-reference/src/index.ts @@ -11,6 +11,8 @@ import { readFile as readFileFn } from './resolve/util'; import dereferenceFn, { dereferenceApiDOM as dereferenceApiDOMFn } from './dereference'; export { default as Parser } from './parse/parsers/Parser'; +export { default as ApiDesignSystemsJsonParser } from './parse/parsers/apidom-reference-parser-api-design-systems-json'; +export { default as ApiDesignSystemsYamlParser } from './parse/parsers/apidom-reference-parser-api-design-systems-yaml'; export { default as OpenApiJson3_1Parser } from './parse/parsers/apidom-reference-parser-openapi-json-3-1'; export { default as OpenApiYaml3_1Parser } from './parse/parsers/apidom-reference-parser-openapi-yaml-3-1'; export { default as AsyncApiJson2Parser } from './parse/parsers/apidom-reference-parser-asyncapi-json-2'; diff --git a/packages/apidom-reference/src/options/index.ts b/packages/apidom-reference/src/options/index.ts index 79587ecc6f..1406a6efa5 100644 --- a/packages/apidom-reference/src/options/index.ts +++ b/packages/apidom-reference/src/options/index.ts @@ -2,6 +2,8 @@ import FileResolver from '../resolve/resolvers/FileResolver'; import HttpResolverAxios from '../resolve/resolvers/HttpResolverAxios'; import OpenApi3_1ResolveStrategy from '../resolve/strategies/openapi-3-1'; import AsyncApi2ResolveStrategy from '../resolve/strategies/asyncapi-2'; +import ApiDesignSystemsJsonParser from '../parse/parsers/apidom-reference-parser-api-design-systems-json'; +import ApiDesignSystemsYamlParser from '../parse/parsers/apidom-reference-parser-api-design-systems-yaml'; import OpenApiJson3_1Parser from '../parse/parsers/apidom-reference-parser-openapi-json-3-1'; import OpenApiYaml3_1Parser from '../parse/parsers/apidom-reference-parser-openapi-yaml-3-1'; import AsyncApiJson2Parser from '../parse/parsers/apidom-reference-parser-asyncapi-json-2'; @@ -21,7 +23,7 @@ const defaultOptions: IReferenceOptions = { mediaType: 'text/plain', /** - * Determines how how different types of files will be parsed. + * Determines how different types of files will be parsed. * * You can add additional parsers of your own, replace an existing one with * your own implementation, or remove any resolver by removing it from the list. @@ -32,6 +34,8 @@ const defaultOptions: IReferenceOptions = { OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), AsyncApiJson2Parser({ allowEmpty: true, sourceMap: false }), AsyncApiYaml2Parser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsJsonParser({ allowEmpty: true, sourceMap: false }), + ApiDesignSystemsYamlParser({ allowEmpty: true, sourceMap: false }), JsonParser({ allowEmpty: true, sourceMap: false }), YamlParser({ allowEmpty: true, sourceMap: false }), BinaryParser({ allowEmpty: true }), diff --git a/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts b/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts new file mode 100644 index 0000000000..3bb467ea71 --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts @@ -0,0 +1,31 @@ +import stampit from 'stampit'; +import { pick } from 'ramda'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { parse, mediaTypes } from '@swagger-api/apidom-parser-adapter-api-design-systems-json'; + +import { ParserError } from '../../../util/errors'; +import { File as IFile, Parser as IParser } from '../../../types'; +import Parser from '../Parser'; + +const ApiDesignSystemsJsonParser: stampit.Stamp = stampit(Parser, { + props: { + name: 'api-design-systems-json', + }, + methods: { + canParse(file: IFile): boolean { + return mediaTypes.includes(file.mediaType) && file.extension === '.json'; + }, + async parse(file: IFile): Promise { + const source = Buffer.isBuffer(file.data) ? file.data.toString() : file.data; + + try { + const parserOpts = pick(['sourceMap', 'syntacticAnalysis', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: any) { + throw new ParserError(`Error parsing "${file.uri}"`, error); + } + }, + }, +}); + +export default ApiDesignSystemsJsonParser; diff --git a/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts b/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts new file mode 100644 index 0000000000..de3baa26a3 --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts @@ -0,0 +1,31 @@ +import stampit from 'stampit'; +import { pick } from 'ramda'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { parse, mediaTypes } from '@swagger-api/apidom-parser-adapter-api-design-systems-yaml'; + +import { ParserError } from '../../../util/errors'; +import { File as IFile, Parser as IParser } from '../../../types'; +import Parser from '../Parser'; + +const ApiDesignSystemsYamlParser: stampit.Stamp = stampit(Parser, { + props: { + name: 'api-design-systems-yaml', + }, + methods: { + canParse(file: IFile): boolean { + return mediaTypes.includes(file.mediaType) && ['.yaml', '.yml'].includes(file.extension); + }, + async parse(file: IFile): Promise { + const source = Buffer.isBuffer(file.data) ? file.data.toString() : file.data; + + try { + const parserOpts = pick(['sourceMap', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: any) { + throw new ParserError(`Error parsing "${file.uri}"`, error); + } + }, + }, +}); + +export default ApiDesignSystemsYamlParser; diff --git a/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/fixtures/api-design-systems.json b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/fixtures/api-design-systems.json new file mode 100644 index 0000000000..2df27a23b7 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/fixtures/api-design-systems.json @@ -0,0 +1,356 @@ +{ + "version": "2021-05-07", + "info": { + "title": "SmartBear API Guidelines", + "description": "A machine and human readable version of the SmartBear API Style Guide aimed at promoting living API Style Governance across various tools and teams, leading to improved API quality.\nSee the [SmartBear Standards and Guidelines](https://github.com/SmartBear/api-standards-and-guidelines) repo for a traditional view of the static guidelines.\n" + }, + "principles": [ + { + "iri": "urn:apidesign.systems:principle:robustness", + "level": "must" + }, + { + "iri": "urn:apidesign.systems:principle:rmm:level2", + "level": "must" + }, + { + "iri": "urn:apidesign.systems:principle:rmm:level3", + "level": "should" + } + ], + "standards": [ + { + "iri": "urn:ietf:rfc:6648", + "level": "must" + }, + { + "iri": "urn:ietf:rfc:7807", + "level": "must" + }, + { + "iri": "urn:ietf:rfc:7231", + "level": "should" + }, + { + "iri": "urn:ietf:rfc:6585", + "level": "may" + }, + { + "iri": "urn:ietf:rfc:5788", + "level": "may" + }, + { + "iri": "urn:ietf:draft:http-semantics", + "level": "may" + } + ], + "scenarios": [ + { + "description": "SB-API-010 - Only apply standard HTTP methods", + "when": [ + "http", + "transaction" + ], + "then": [ + { + "subject": [ + "http", + "request", + "method" + ], + "level": "may", + "values": [ + "get", + "post", + "put", + "patch", + "delete" + ] + }, + { + "subject": [ + "http", + "request", + "header" + ], + "level": "may", + "values": [ + "Accept", + "Accept-Charset", + "Authorization", + "Content-Language", + "Content-Type", + "Link", + "Prefer" + ] + }, + { + "subject": [ + "http", + "request", + "header", + "Content-Type" + ], + "level": "may", + "values": [ + "application/json", + "application/hal+json" + ] + }, + { + "subject": [ + "http", + "response", + "header", + "Content-Type" + ], + "level": "should", + "values": [ + "application/hal+json" + ] + }, + { + "subject": [ + "http", + "response", + "header", + "Content-Type" + ], + "level": "may", + "values": [ + "application/json", + "application/hal+json", + "application/problem+json" + ] + } + ] + }, + { + "description": "GET Methods - Allowed status codes", + "when": [ + "http", + "request", + "method", + "get" + ], + "then": [ + { + "subject": [ + "http", + "response", + "status_code" + ], + "level": "may", + "values": [ + "200", + "304", + "400", + "401", + "403", + "404", + "405", + "422", + "429", + "500", + "501", + "503" + ] + } + ] + }, + { + "description": "POST Method - Allowed status codes", + "when": [ + "http", + "request", + "method", + "post" + ], + "then": [ + { + "subject": [ + "http", + "response", + "status_code" + ], + "level": "may", + "values": [ + "200", + "201", + "202", + "400", + "401", + "403", + "404", + "405", + "409", + "415", + "422", + "429", + "500", + "501", + "503" + ] + } + ] + }, + { + "description": "PUT Method - Allowed status codes", + "when": [ + "http", + "request", + "method", + "put" + ], + "then": [ + { + "subject": [ + "http", + "response", + "status_code" + ], + "level": "may", + "values": [ + "200", + "201", + "202", + "204", + "400", + "401", + "403", + "404", + "405", + "409", + "412", + "415", + "422", + "429", + "500", + "501", + "503" + ] + } + ] + }, + { + "description": "PATCH Method - Allowed status codes", + "when": [ + "http", + "request", + "method", + "patch" + ], + "then": [ + { + "subject": [ + "http", + "response", + "status_code" + ], + "level": "may", + "values": [ + "200", + "202", + "204", + "400", + "401", + "403", + "404", + "405", + "409", + "412", + "415", + "422", + "429", + "500", + "501", + "503" + ] + } + ] + }, + { + "description": "DELETE Method - Allowed status codes", + "when": [ + "http", + "request", + "method", + "delete" + ], + "then": [ + { + "subject": [ + "http", + "response", + "status_code" + ], + "level": "may", + "values": [ + "200", + "202", + "204", + "400", + "401", + "403", + "404", + "405", + "409", + "412", + "415", + "422", + "429", + "500", + "501", + "503" + ] + } + ] + }, + { + "description": "Client Errors must return `application/problem+json`", + "when": [ + "http", + "response", + "status_code", + "client_error" + ], + "then": [ + { + "subject": [ + "http", + "response", + "header", + "Content-Type" + ], + "level": "must", + "values": [ + "application/problem+json" + ] + } + ] + }, + { + "description": "Server Errors must return `application/problem+json`", + "when": [ + "http", + "response", + "status_code", + "server_error" + ], + "then": [ + { + "subject": [ + "http", + "response", + "header", + "Content-Type" + ], + "level": "must", + "values": [ + "application/problem+json" + ] + } + ] + } + ] +} diff --git a/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts new file mode 100644 index 0000000000..8d9ea5be3d --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-json/index.ts @@ -0,0 +1,172 @@ +import fs from 'fs'; +import path from 'path'; +import { assert } from 'chai'; +import { isParseResultElement, isSourceMapElement } from '@swagger-api/apidom-core'; +import { mediaTypes } from '@swagger-api/apidom-ns-api-design-systems'; + +import File from '../../../../src/util/File'; +import ApiDesignSystemsJsonParser from '../../../../src/parse/parsers/apidom-reference-parser-api-design-systems-json'; +import { ParserError } from '../../../../src/util/errors'; + +describe('parsers', function () { + context('ApiDesignSystemsJsonParser', function () { + context('canParse', function () { + context('given file with .json extension', function () { + context('and with proper media type', function () { + specify('should return true', function () { + const file1 = File({ + uri: '/path/to/api-design-systems.json', + mediaType: mediaTypes.latest('json'), + }); + const file2 = File({ + uri: '/path/to/api-design-systems.json', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser(); + + assert.isTrue(parser.canParse(file1)); + assert.isTrue(parser.canParse(file2)); + }); + }); + + context('and with improper media type', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems.json', + mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', + }); + const parser = ApiDesignSystemsJsonParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + }); + + context('given file with unknown extension', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems.yaml', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + + context('given file with no extension', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + }); + + context('parse', function () { + context('given API Design Systems JSON data', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = ApiDesignSystemsJsonParser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given API Design Systems JSON data as buffer', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.json'); + const data = fs.readFileSync(url); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = ApiDesignSystemsJsonParser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given data that is not an API Design Systems JSON data', function () { + specify('should throw ParserError', async function () { + try { + const file = File({ + uri: '/path/to/file.json', + data: 1, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser(); + await parser.parse(file); + assert.fail('should throw ParserError'); + } catch (error: any) { + assert.instanceOf(error.cause, TypeError); + assert.instanceOf(error, ParserError); + assert.propertyVal(error, 'message', 'Error parsing "/path/to/file.json"'); + } + }); + }); + + context('given empty file', function () { + specify('should return empty parse result', async function () { + const file = File({ + uri: '/path/to/file.json', + data: '', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + assert.isTrue(parseResult.isEmpty); + }); + }); + + context('sourceMap', function () { + context('given sourceMap enabled', function () { + specify('should decorate ApiDOM with source maps', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser({ sourceMap: true }); + const parseResult = await parser.parse(file); + + assert.isTrue(isSourceMapElement(parseResult.result?.meta.get('sourceMap'))); + }); + }); + + context('given sourceMap disabled', function () { + specify('should not decorate ApiDOM with source maps', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsJsonParser({ sourceMap: false }); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.meta.get('sourceMap')); + }); + }); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/fixtures/api-design-systems.yaml b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/fixtures/api-design-systems.yaml new file mode 100644 index 0000000000..f374926766 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/fixtures/api-design-systems.yaml @@ -0,0 +1,183 @@ +version: "2021-05-07" +info: + title: SmartBear API Guidelines + description: | + A machine and human readable version of the SmartBear API Style Guide aimed at promoting living API Style Governance across various tools and teams, leading to improved API quality. + See the [SmartBear Standards and Guidelines](https://github.com/SmartBear/api-standards-and-guidelines) repo for a traditional view of the static guidelines. +principles: + - iri: urn:apidesign.systems:principle:robustness + level: must + - iri: urn:apidesign.systems:principle:rmm:level2 + level: must + - iri: urn:apidesign.systems:principle:rmm:level3 + level: should +standards: + - iri: urn:ietf:rfc:6648 + level: must + - iri: urn:ietf:rfc:7807 + level: must + - iri: urn:ietf:rfc:7231 + level: should + - iri: urn:ietf:rfc:6585 + level: may + - iri: urn:ietf:rfc:5788 + level: may + - iri: urn:ietf:draft:http-semantics + level: may +scenarios: + - description: SB-API-010 - Only apply standard HTTP methods + when: [http, transaction] + then: + - subject: [http, request, method] + level: may + values: + - get + - post + - put + - patch + - delete + - subject: [http, request, header] + level: may + values: + - Accept + - Accept-Charset + - Authorization + - Content-Language + - Content-Type + - Link + - Prefer + - subject: [http, request, header, Content-Type] + level: may + values: + - application/json + - application/hal+json + - subject: [http, response, header, Content-Type] + level: should + values: + - application/hal+json + - subject: [http, response, header, Content-Type] + level: may + values: + - application/json + - application/hal+json + - application/problem+json + - description: GET Methods - Allowed status codes + when: [http, request, method, get] + then: + - subject: [http, response, status_code] + level: may + values: + - "200" + - "304" + - "400" + - "401" + - "403" + - "404" + - "405" + - "422" + - "429" + - "500" + - "501" + - "503" + - description: POST Method - Allowed status codes + when: [http, request, method, post] + then: + - subject: [http, response, status_code] + level: may + values: + - "200" + - "201" + - "202" + - "400" + - "401" + - "403" + - "404" + - "405" + - "409" + - "415" + - "422" + - "429" + - "500" + - "501" + - "503" + - description: PUT Method - Allowed status codes + when: [http, request, method, put] + then: + - subject: [http, response, status_code] + level: may + values: + - "200" + - "201" + - "202" + - "204" + - "400" + - "401" + - "403" + - "404" + - "405" + - "409" + - "412" + - "415" + - "422" + - "429" + - "500" + - "501" + - "503" + - description: PATCH Method - Allowed status codes + when: [http, request, method, patch] + then: + - subject: [http, response, status_code] + level: may + values: + - "200" + - "202" + - "204" + - "400" + - "401" + - "403" + - "404" + - "405" + - "409" + - "412" + - "415" + - "422" + - "429" + - "500" + - "501" + - "503" + - description: DELETE Method - Allowed status codes + when: [http, request, method, delete] + then: + - subject: [http, response, status_code] + level: may + values: + - "200" + - "202" + - "204" + - "400" + - "401" + - "403" + - "404" + - "405" + - "409" + - "412" + - "415" + - "422" + - "429" + - "500" + - "501" + - "503" + - description: Client Errors must return `application/problem+json` + when: [http, response, status_code, client_error] + then: + - subject: [http, response, header, Content-Type] + level: must + values: + - application/problem+json + - description: Server Errors must return `application/problem+json` + when: [http, response, status_code, server_error] + then: + - subject: [http, response, header, Content-Type] + level: must + values: + - application/problem+json diff --git a/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts new file mode 100644 index 0000000000..b239b020b5 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/apidom-reference-parser-api-design-systems-yaml/index.ts @@ -0,0 +1,195 @@ +import fs from 'fs'; +import path from 'path'; +import { assert } from 'chai'; +import { isParseResultElement, isSourceMapElement } from '@swagger-api/apidom-core'; +import { mediaTypes } from '@swagger-api/apidom-ns-api-design-systems'; + +import File from '../../../../src/util/File'; +import ApiDesignSystemsYamlParser from '../../../../src/parse/parsers/apidom-reference-parser-api-design-systems-yaml'; +import { ParserError } from '../../../../src/util/errors'; + +describe('parsers', function () { + context('ApiDesignSystemsYamlParser', function () { + context('canParse', function () { + context('given file with .yaml extension', function () { + context('and with proper media type', function () { + specify('should return true', function () { + const file1 = File({ + uri: '/path/to/api-design-systems.yaml', + mediaType: mediaTypes.latest('yaml'), + }); + const file2 = File({ + uri: '/path/to/api-design-systems.yaml', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isTrue(parser.canParse(file1)); + assert.isTrue(parser.canParse(file2)); + }); + }); + + context('and with improper media type', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems.yaml', + mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + }); + + context('given file with .yml extension', function () { + context('and with proper media type', function () { + specify('should return true', function () { + const file1 = File({ + uri: '/path/to/api-design-systems.yml', + mediaType: mediaTypes.latest('yaml'), + }); + const file2 = File({ + uri: '/path/to/api-design-systems.yml', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isTrue(parser.canParse(file1)); + assert.isTrue(parser.canParse(file2)); + }); + }); + + context('and with improper media type', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems.yaml', + mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + }); + + context('given file with unknown extension', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems.txt', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + + context('given file with no extension', function () { + specify('should return false', function () { + const file = File({ + uri: '/path/to/api-design-systems', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + + assert.isFalse(parser.canParse(file)); + }); + }); + }); + + context('parse', function () { + context('given API Design Systems YAML data', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ url, data, mediaType: mediaTypes.latest() }); + const parser = ApiDesignSystemsYamlParser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given API Design Systems YAML data as buffer', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.yaml'); + const data = fs.readFileSync(url); + const file = File({ url, data, mediaType: mediaTypes.latest() }); + const parser = ApiDesignSystemsYamlParser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given data that is not an API Design Systems YAML data', function () { + specify('should throw ParserError', async function () { + try { + const file = File({ + uri: '/path/to/file.yaml', + data: 1, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + await parser.parse(file); + assert.fail('should throw ParserError'); + } catch (error: any) { + assert.instanceOf(error.cause, TypeError); + assert.instanceOf(error, ParserError); + assert.propertyVal(error, 'message', 'Error parsing "/path/to/file.yaml"'); + } + }); + }); + + context('given empty file', function () { + specify('should return empty parse result', async function () { + const file = File({ + uri: '/path/to/file.yaml', + data: '', + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser(); + const parsceResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parsceResult)); + assert.isTrue(parsceResult.isEmpty); + }); + }); + + context('sourceMap', function () { + context('given sourceMap enabled', function () { + specify('should decorate ApiDOM with source maps', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser({ sourceMap: true }); + const parseResult = await parser.parse(file); + + assert.isTrue(isSourceMapElement(parseResult.result?.meta.get('sourceMap'))); + }); + }); + + context('given sourceMap disabled', function () { + specify('should not decorate ApiDOM with source maps', async function () { + const url = path.join(__dirname, 'fixtures', 'api-design-systems.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest(), + }); + const parser = ApiDesignSystemsYamlParser({ sourceMap: false }); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.result?.meta.get('sourceMap')); + }); + }); + }); + }); + }); +});