From 1a1de727e7053247c05ca8c7424c24baa9bb2ace Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Tue, 31 Oct 2023 13:21:32 +0100 Subject: [PATCH] feat(reference): add OpenAPI 2.0 YAML parser plugin Refs #3100 --- package-lock.json | 5 + packages/apidom-reference/README.md | 19 + packages/apidom-reference/package.json | 10 + .../src/configuration/saturated.ts | 2 + .../src/parse/parsers/openapi-yaml-2/index.ts | 42 ++ .../openapi-yaml-2/fixtures/sample-api.yaml | 710 ++++++++++++++++++ .../parse/parsers/openapi-yaml-2/index.ts | 223 ++++++ .../parse/parsers/openapi-yaml-3-0/index.ts | 10 +- 8 files changed, 1016 insertions(+), 5 deletions(-) create mode 100644 packages/apidom-reference/src/parse/parsers/openapi-yaml-2/index.ts create mode 100644 packages/apidom-reference/test/parse/parsers/openapi-yaml-2/fixtures/sample-api.yaml create mode 100644 packages/apidom-reference/test/parse/parsers/openapi-yaml-2/index.ts diff --git a/package-lock.json b/package-lock.json index f94c3dac40..b154683ecd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33571,8 +33571,10 @@ "stampit": "^4.3.2" }, "devDependencies": { + "@swagger-api/apidom-error": "*", "@swagger-api/apidom-json-pointer": "*", "@swagger-api/apidom-ns-asyncapi-2": "*", + "@swagger-api/apidom-ns-openapi-2": "*", "@swagger-api/apidom-ns-openapi-3-0": "*", "@swagger-api/apidom-ns-openapi-3-1": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "*", @@ -33580,8 +33582,10 @@ "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "*", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "*", "@swagger-api/apidom-parser-adapter-json": "*", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "*", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "*", "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "*", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "*", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "*", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "*", "@swagger-api/apidom-parser-adapter-yaml-1-2": "*", @@ -33602,6 +33606,7 @@ "@swagger-api/apidom-parser-adapter-openapi-json-2": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.81.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.81.0", "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.81.0" diff --git a/packages/apidom-reference/README.md b/packages/apidom-reference/README.md index 65abf2638d..50916a6ab8 100644 --- a/packages/apidom-reference/README.md +++ b/packages/apidom-reference/README.md @@ -134,6 +134,20 @@ Supported media types are: ] ``` +#### [openapi-yaml-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/openapi-yaml-2) + +Wraps [@swagger-api/apidom-parser-adapter-openapi-yaml-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-openapi-yaml-2) package +and is uniquely identified by `openapi-yaml-2` name. + +Supported media types are: + +```js +[ + 'application/vnd.oai.openapi;version=2.0', + 'application/vnd.oai.openapi+yaml;version=2.0', +] +``` + #### [openapi-yaml-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/openapi-yaml-3-0) Wraps [@swagger-api/apidom-parser-adapter-openapi-yaml-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-openapi-yaml-3-0) package @@ -315,6 +329,7 @@ returns `true` or until entire list of parser plugins is exhausted (throws error ```js [ OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }), + OpenApiYaml2Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), @@ -336,6 +351,7 @@ It's possible to **change** the parser plugins **order globally** by mutating gl ```js import { options } from '@swagger-api/apidom-reference'; import OpenApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-2'; +import OpenApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-2'; import OpenApiJson3_0Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-0'; import OpenApiYaml3_0Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-0' import OpenApiJson3_1Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-1'; @@ -351,6 +367,7 @@ import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary'; options.parse.parsers = [ OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }), + OpenApiYaml2Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }), @@ -370,6 +387,7 @@ To **change** the parser plugins **order** on ad-hoc basis: ```js import { parse } from '@swagger-api/apidom-reference'; import OpenApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-2'; +import OpenApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-2'; import OpenApiJson3_0Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-0'; import OpenApiYaml3_0Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-0' import OpenApiJson3_1Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-1'; @@ -387,6 +405,7 @@ await parse('/home/user/oas.json', { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', 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 }), diff --git a/packages/apidom-reference/package.json b/packages/apidom-reference/package.json index fac1e1f3c8..bf0e9b66e2 100644 --- a/packages/apidom-reference/package.json +++ b/packages/apidom-reference/package.json @@ -110,6 +110,11 @@ "require": "./cjs/parse/parsers/openapi-json-2/index.cjs", "types": "./types/parse/parsers/openapi-json-2/index.d.ts" }, + "./parse/parsers/openapi-yaml-2": { + "import": "./es/parse/parsers/openapi-yaml-2/index.mjs", + "require": "./cjs/parse/parsers/openapi-yaml-2/index.cjs", + "types": "./types/parse/parsers/openapi-yaml-2/index.d.ts" + }, "./parse/parsers/openapi-json-3-0": { "import": "./es/parse/parsers/openapi-json-3-0/index.mjs", "require": "./cjs/parse/parsers/openapi-json-3-0/index.cjs", @@ -230,6 +235,7 @@ "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.81.0", "@swagger-api/apidom-parser-adapter-json": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-json-2": "^0.81.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.81.0", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.81.0", @@ -237,8 +243,10 @@ "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.81.0" }, "devDependencies": { + "@swagger-api/apidom-error": "*", "@swagger-api/apidom-json-pointer": "*", "@swagger-api/apidom-ns-asyncapi-2": "*", + "@swagger-api/apidom-ns-openapi-2": "*", "@swagger-api/apidom-ns-openapi-3-0": "*", "@swagger-api/apidom-ns-openapi-3-1": "*", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "*", @@ -246,6 +254,8 @@ "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "*", "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "*", "@swagger-api/apidom-parser-adapter-json": "*", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "*", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "*", "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "*", "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "*", "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "*", diff --git a/packages/apidom-reference/src/configuration/saturated.ts b/packages/apidom-reference/src/configuration/saturated.ts index f3572ae15b..c5f934bd42 100644 --- a/packages/apidom-reference/src/configuration/saturated.ts +++ b/packages/apidom-reference/src/configuration/saturated.ts @@ -6,6 +6,7 @@ import AsyncApi2ResolveStrategy from '../resolve/strategies/asyncapi-2'; import ApiDesignSystemsJsonParser from '../parse/parsers/api-design-systems-json'; import ApiDesignSystemsYamlParser from '../parse/parsers/api-design-systems-yaml'; import OpenApiJson2Parser from '../parse/parsers/openapi-json-2'; +import OpenApiYaml2Parser from '../parse/parsers/openapi-yaml-2'; import OpenApiJson3_0Parser from '../parse/parsers/openapi-json-3-0'; import OpenApiYaml3_0Parser from '../parse/parsers/openapi-yaml-3-0'; import OpenApiJson3_1Parser from '../parse/parsers/openapi-json-3-1'; @@ -22,6 +23,7 @@ import { options } from '../index'; options.parse.parsers = [ OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }), + OpenApiYaml2Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }), diff --git a/packages/apidom-reference/src/parse/parsers/openapi-yaml-2/index.ts b/packages/apidom-reference/src/parse/parsers/openapi-yaml-2/index.ts new file mode 100644 index 0000000000..97a7bcae78 --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/openapi-yaml-2/index.ts @@ -0,0 +1,42 @@ +import stampit from 'stampit'; +import { pick } from 'ramda'; +import { ParseResultElement } from '@swagger-api/apidom-core'; +import { parse, mediaTypes, detect } from '@swagger-api/apidom-parser-adapter-openapi-yaml-2'; + +import ParserError from '../../../errors/ParserError'; +import { File as IFile, Parser as IParser } from '../../../types'; +import Parser from '../Parser'; + +const OpenApiYaml2Parser: stampit.Stamp = stampit(Parser, { + props: { + name: 'openapi-yaml-2', + fileExtensions: ['.yaml', '.yml'], + mediaTypes, + }, + methods: { + async canParse(file: IFile): Promise { + 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) { + return detect(file.toString()); + } + return false; + }, + async parse(file: IFile): Promise { + const source = file.toString(); + + try { + const parserOpts = pick(['sourceMap', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: any) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + }, + }, +}); + +export default OpenApiYaml2Parser; diff --git a/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/fixtures/sample-api.yaml b/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/fixtures/sample-api.yaml new file mode 100644 index 0000000000..c4a2ec92db --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/fixtures/sample-api.yaml @@ -0,0 +1,710 @@ +swagger: '2.0' +info: + description: >- + This is a sample server Petstore server. You can find out more about + Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, + #swagger](http://swagger.io/irc/). For this sample, you can use the api + key `special-key` to test the authorization filters. + version: 1.0.0 + title: Swagger Petstore 2.0 + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +host: petstore.swagger.io +basePath: /v2 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +schemes: + - https + - http +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + produces: + - application/xml + - application/json + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + collectionFormat: multi + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Muliple tags can be provided with comma separated strings. Use + tag1, tag2, tag3 for testing. + operationId: findPetsByTags + produces: + - application/xml + - application/json + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + type: array + items: + type: string + collectionFormat: multi + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + deprecated: true + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + type: integer + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + consumes: + - application/x-www-form-urlencoded + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: integer + format: int64 + - name: name + in: formData + description: Updated name of the pet + required: false + type: string + - name: status + in: formData + description: Updated status of the pet + required: false + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + produces: + - application/xml + - application/json + parameters: + - name: api_key + in: header + required: false + type: string + - name: petId + in: path + description: Pet id to delete + required: true + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + consumes: + - multipart/form-data + produces: + - application/json + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + type: integer + format: int64 + - name: additionalMetadata + in: formData + description: Additional data to pass to server + required: false + type: string + - name: file + in: formData + description: file to upload + required: false + type: file + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + produces: + - application/json + parameters: [] + responses: + '200': + description: successful operation + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: order placed for purchasing the pet + required: true + schema: + $ref: '#/definitions/Order' + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid Order + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value >= 1 and <= 10. + Other values will generated exceptions + operationId: getOrderById + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + type: integer + maximum: 10 + minimum: 1 + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with positive integer value. + Negative or non-integer values will generate API errors + operationId: deleteOrder + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + type: integer + minimum: 1 + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Created user object + required: true + schema: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: query + description: The user name for login + required: true + type: string + - name: password + in: query + description: The password for login in clear text + required: true + type: string + responses: + '200': + description: successful operation + schema: + type: string + headers: + X-Rate-Limit: + type: integer + format: int32 + description: calls per hour allowed by the user + X-Expires-After: + type: string + format: date-time + description: date in UTC when token expires + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + produces: + - application/xml + - application/json + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + type: string + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: name that need to be updated + required: true + type: string + - in: body + name: body + description: Updated user object + required: true + schema: + $ref: '#/definitions/User' + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: http://petstore.swagger.io/oauth/dialog + flow: implicit + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header +definitions: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string +externalDocs: + description: Find out more about Swagger + url: http://swagger.io diff --git a/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/index.ts b/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/index.ts new file mode 100644 index 0000000000..0b4fc3e7e8 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/openapi-yaml-2/index.ts @@ -0,0 +1,223 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { assert } from 'chai'; +import { NumberElement, isParseResultElement, isSourceMapElement } from '@swagger-api/apidom-core'; +import { mediaTypes } from '@swagger-api/apidom-parser-adapter-openapi-yaml-2'; + +import File from '../../../../src/util/File'; +import OpenApiYaml2Parser from '../../../../src/parse/parsers/openapi-yaml-2'; + +describe('parsers', function () { + context('OpenApiYaml2Parser', function () { + context('canParse', function () { + context('given file with .yaml extension', function () { + context('and with proper media type', function () { + specify('should return true', async function () { + const file1 = File({ + uri: '/path/to/openapi.yaml', + mediaType: mediaTypes.latest('yaml'), + }); + const file2 = File({ + uri: '/path/to/openapi.yaml', + mediaType: mediaTypes.latest('generic'), + }); + const parser = OpenApiYaml2Parser(); + + 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/openapi.yaml', + mediaType: 'application/vnd.aai.asyncapi;version=2.6.0', + }); + const parser = OpenApiYaml2Parser(); + + assert.isFalse(await parser.canParse(file)); + }); + }); + }); + + context('given file with .yml extension', function () { + context('and with proper media type', function () { + specify('should return true', async function () { + const file1 = File({ + uri: '/path/to/openapi.yml', + mediaType: mediaTypes.latest('yaml'), + }); + const file2 = File({ + uri: '/path/to/openapi.yml', + mediaType: mediaTypes.latest('generic'), + }); + const parser = OpenApiYaml2Parser(); + + 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/openapi.yaml', + mediaType: 'application/vnd.aai.asyncapi;version=2.6.0', + }); + const parser = OpenApiYaml2Parser(); + + 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/openapi.txt', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser(); + + 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/openapi', + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser(); + + assert.isFalse(await parser.canParse(file)); + }); + }); + + context('given file with supported extension', function () { + context('and file data is buffer and can be detected as OpenAPI 2.0', function () { + specify('should return true', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); + const file = File({ + uri: '/path/to/open-api.yaml', + data: fs.readFileSync(url), + }); + const parser = OpenApiYaml2Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + + context('and file data is string and can be detected as OpenAPI 2.0', function () { + specify('should return true', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); + const file = File({ + uri: '/path/to/open-api.yaml', + data: fs.readFileSync(url).toString(), + }); + const parser = OpenApiYaml2Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + }); + }); + + context('parse', function () { + context('given OpenApi 2.0 YAML data', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given OpenApi 2.0 YAML data as buffer', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); + const data = fs.readFileSync(url); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given data that is not an OpenApi 2.0 YAML data', function () { + specify('should coerce to string and parse', async function () { + const file = File({ + uri: '/path/to/file.yaml', + data: 1, + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser(); + const result = await parser.parse(file); + const numberElement: NumberElement = result.get(0); + + assert.isTrue(isParseResultElement(result)); + assert.isTrue(numberElement.equals(1)); + }); + }); + + 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('yaml'), + }); + const parser = OpenApiYaml2Parser(); + 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', 'sample-api.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('yaml'), + }); + const parser = OpenApiYaml2Parser({ sourceMap: true }); + const parseResult = await parser.parse(file); + + assert.isTrue(isSourceMapElement(parseResult.api?.meta.get('sourceMap'))); + }); + }); + + context('given sourceMap disabled', function () { + specify('should not decorate ApiDOM with source maps', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); + const data = fs.readFileSync(url).toString(); + const file = File({ url, data }); + const parser = OpenApiYaml2Parser({ sourceMap: false }); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.api?.meta.get('sourceMap')); + }); + }); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/parse/parsers/openapi-yaml-3-0/index.ts b/packages/apidom-reference/test/parse/parsers/openapi-yaml-3-0/index.ts index afa9ae99ad..0add286464 100644 --- a/packages/apidom-reference/test/parse/parsers/openapi-yaml-3-0/index.ts +++ b/packages/apidom-reference/test/parse/parsers/openapi-yaml-3-0/index.ts @@ -97,7 +97,7 @@ describe('parsers', function () { }); context('given file with supported extension', function () { - context('and file data is buffer and can be detected as OpenAPI 3.1.0', function () { + context('and file data is buffer and can be detected as OpenAPI 3.0.x', function () { specify('should return true', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); const file = File({ @@ -110,7 +110,7 @@ describe('parsers', function () { }); }); - context('and file data is string and can be detected as OpenAPI 3.1.0', function () { + context('and file data is string and can be detected as OpenAPI 3.0.x', function () { specify('should return true', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); const file = File({ @@ -126,7 +126,7 @@ describe('parsers', function () { }); context('parse', function () { - context('given OpenApi 3.1.x YAML data', function () { + context('given OpenApi 3.0.1 YAML data', function () { specify('should return parse result', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); const data = fs.readFileSync(url).toString(); @@ -142,7 +142,7 @@ describe('parsers', function () { }); }); - context('given OpenApi 3.1.x YAML data as buffer', function () { + context('given OpenApi 3.0.x YAML data as buffer', function () { specify('should return parse result', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.yaml'); const data = fs.readFileSync(url); @@ -158,7 +158,7 @@ describe('parsers', function () { }); }); - context('given data that is not an OpenApi 3.1.x YAML data', function () { + context('given data that is not an OpenApi 3.0.x YAML data', function () { specify('should coerce to string and parse', async function () { const file = File({ uri: '/path/to/file.yaml',