diff --git a/package-lock.json b/package-lock.json index 1d69aef136..f94c3dac40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33591,6 +33591,7 @@ "@swagger-api/apidom-error": "^0.81.0", "@swagger-api/apidom-json-pointer": "^0.81.0", "@swagger-api/apidom-ns-asyncapi-2": "^0.81.0", + "@swagger-api/apidom-ns-openapi-2": "^0.81.0", "@swagger-api/apidom-ns-openapi-3-0": "^0.81.0", "@swagger-api/apidom-ns-openapi-3-1": "^0.81.0", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.81.0", @@ -33598,6 +33599,7 @@ "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.81.0", "@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-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", diff --git a/packages/apidom-reference/README.md b/packages/apidom-reference/README.md index 0f0d214762..65abf2638d 100644 --- a/packages/apidom-reference/README.md +++ b/packages/apidom-reference/README.md @@ -24,7 +24,7 @@ This package has two main exports suitable for different use-cases. **Empty** co ```js import { parse } from '@swagger-api/apidom-reference/configuration/empty'; -import { OpenApiJson3_1Parser } from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-1'; +import OpenApiJson3_1Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-3-1'; await parse('/home/user/oas.json', { parse: { @@ -100,6 +100,20 @@ so providing it is always a better option. Parse component comes with number of default parser plugins. +#### [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 +and is uniquely identified by `openapi-json-2` name. + +Supported media types are: + +```js +[ + 'application/vnd.oai.openapi;version=2.0', + 'application/vnd.oai.openapi+json;version=2.0', +] +``` + #### [openapi-json-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/parse/parsers/openapi-json-3-0) Wraps [@swagger-api/apidom-parser-adapter-openapi-json-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-parser-adapter-openapi-json-3-0) package @@ -300,6 +314,7 @@ returns `true` or until entire list of parser plugins is exhausted (throws error ```js [ + OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_1Parser({ allowEmpty: true, sourceMap: false }), @@ -320,20 +335,22 @@ It's possible to **change** the parser plugins **order globally** by mutating gl ```js import { options } from '@swagger-api/apidom-reference'; -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'; -import { OpenApiYaml3_1Parser } from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-1' -import { AsyncApiJson2Parser } from '@swagger-api/apidom-reference/parse/parsers/asyncapi-json-2'; -import { AsyncApiYaml2Parser } from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2'; -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'; -import { YamlParser } from '@swagger-api/apidom-reference/parse/parsers/yaml'; -import { BinaryParser } from '@swagger-api/apidom-reference/parse/parsers/binary'; +import OpenApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-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'; +import OpenApiYaml3_1Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-1' +import AsyncApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-json-2'; +import AsyncApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2'; +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'; +import YamlParser from '@swagger-api/apidom-reference/parse/parsers/yaml'; +import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary'; options.parse.parsers = [ + OpenApiJson2Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiYaml3_0Parser({ allowEmpty: true, sourceMap: false }), OpenApiJson3_1Parser({ allowEmpty: true, sourceMap: false }), @@ -352,22 +369,24 @@ To **change** the parser plugins **order** on ad-hoc basis: ```js import { parse } from '@swagger-api/apidom-reference'; -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'; -import { OpenApiYaml3_1Parser } from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-1' -import { AsyncApiJson2Parser } from '@swagger-api/apidom-reference/parse/parsers/asyncapi-json-2'; -import { AsyncApiYaml2Parser } from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2'; -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'; -import { YamlParser } from '@swagger-api/apidom-reference/parse/parsers/yaml'; -import { BinaryParser } from '@swagger-api/apidom-reference/parse/parsers/binary'; +import OpenApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-json-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'; +import OpenApiYaml3_1Parser from '@swagger-api/apidom-reference/parse/parsers/openapi-yaml-3-1' +import AsyncApiJson2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-json-2'; +import AsyncApiYaml2Parser from '@swagger-api/apidom-reference/parse/parsers/asyncapi-yaml-2'; +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'; +import YamlParser from '@swagger-api/apidom-reference/parse/parsers/yaml'; +import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary'; await parse('/home/user/oas.json', { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0', parsers: [ + OpenApiJson2Parser({ 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 38f522dc40..fac1e1f3c8 100644 --- a/packages/apidom-reference/package.json +++ b/packages/apidom-reference/package.json @@ -105,6 +105,11 @@ "require": "./cjs/parse/parsers/json/index.cjs", "types": "./types/parse/parsers/json/index.d.ts" }, + "./parse/parsers/openapi-json-2": { + "import": "./es/parse/parsers/openapi-json-2/index.mjs", + "require": "./cjs/parse/parsers/openapi-json-2/index.cjs", + "types": "./types/parse/parsers/openapi-json-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", @@ -216,6 +221,7 @@ "@swagger-api/apidom-error": "^0.81.0", "@swagger-api/apidom-json-pointer": "^0.81.0", "@swagger-api/apidom-ns-asyncapi-2": "^0.81.0", + "@swagger-api/apidom-ns-openapi-2": "^0.81.0", "@swagger-api/apidom-ns-openapi-3-0": "^0.81.0", "@swagger-api/apidom-ns-openapi-3-1": "^0.81.0", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.81.0", @@ -223,6 +229,7 @@ "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.81.0", "@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-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", diff --git a/packages/apidom-reference/src/configuration/saturated.ts b/packages/apidom-reference/src/configuration/saturated.ts index b654d809ca..f3572ae15b 100644 --- a/packages/apidom-reference/src/configuration/saturated.ts +++ b/packages/apidom-reference/src/configuration/saturated.ts @@ -5,6 +5,7 @@ import OpenApi3_1ResolveStrategy from '../resolve/strategies/openapi-3-1'; 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 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'; @@ -20,6 +21,7 @@ import AsyncApi2DereferenceStrategy from '../dereference/strategies/asyncapi-2'; import { options } from '../index'; options.parse.parsers = [ + OpenApiJson2Parser({ 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-json-2/index.ts b/packages/apidom-reference/src/parse/parsers/openapi-json-2/index.ts new file mode 100644 index 0000000000..38bd8665da --- /dev/null +++ b/packages/apidom-reference/src/parse/parsers/openapi-json-2/index.ts @@ -0,0 +1,43 @@ +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-json-2'; + +import ParserError from '../../../errors/ParserError'; +import { File as IFile, Parser as IParser } from '../../../types'; +import Parser from '../Parser'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const OpenApiJson2Parser: stampit.Stamp = stampit(Parser, { + props: { + name: 'openapi-json-2', + fileExtensions: ['.json'], + 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', 'syntacticAnalysis', 'refractorOpts'], this); + return await parse(source, parserOpts); + } catch (error: any) { + throw new ParserError(`Error parsing "${file.uri}"`, { cause: error }); + } + }, + }, +}); + +export default OpenApiJson2Parser; diff --git a/packages/apidom-reference/test/parse/parsers/openapi-json-2/fixtures/sample-api.json b/packages/apidom-reference/test/parse/parsers/openapi-json-2/fixtures/sample-api.json new file mode 100644 index 0000000000..254a03b61a --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/openapi-json-2/fixtures/sample-api.json @@ -0,0 +1,1036 @@ +{ + "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-json-2/index.ts b/packages/apidom-reference/test/parse/parsers/openapi-json-2/index.ts new file mode 100644 index 0000000000..9032a24726 --- /dev/null +++ b/packages/apidom-reference/test/parse/parsers/openapi-json-2/index.ts @@ -0,0 +1,196 @@ +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-json-2'; + +import File from '../../../../src/util/File'; +import OpenApiJson2Parser from '../../../../src/parse/parsers/openapi-json-2'; + +describe('parsers', function () { + context('OpenApiJson2Parser', 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/openapi.json', + mediaType: mediaTypes.latest('generic'), + }); + const file2 = File({ + uri: '/path/to/openapi.json', + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + + 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.json', + mediaType: 'application/vnd.aai.asyncapi+json;version=2.6.0', + }); + const parser = OpenApiJson2Parser(); + + 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.yaml', + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + + 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('json'), + }); + const parser = OpenApiJson2Parser(); + + 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.json'); + const file = File({ + uri: '/path/to/open-api.json', + data: fs.readFileSync(url), + }); + const parser = OpenApiJson2Parser(); + + 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.json'); + const file = File({ + uri: '/path/to/open-api.json', + data: fs.readFileSync(url).toString(), + }); + const parser = OpenApiJson2Parser(); + + assert.isTrue(await parser.canParse(file)); + }); + }); + }); + }); + + context('parse', function () { + context('given OpenApi 2.0 JSON data', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given OpenApi 2.0 JSON data as buffer', function () { + specify('should return parse result', async function () { + const url = path.join(__dirname, 'fixtures', 'sample-api.json'); + const data = fs.readFileSync(url); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + const parseResult = await parser.parse(file); + + assert.isTrue(isParseResultElement(parseResult)); + }); + }); + + context('given data that is not an OpenApi 2.0 JSON data', function () { + specify('should coerce to string and parse', async function () { + const file = File({ + uri: '/path/to/file.json', + data: 1, + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + const parseResult = await parser.parse(file); + const numberElement: NumberElement = parseResult.get(0); + + assert.isTrue(isParseResultElement(parseResult)); + 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.json', + data: '', + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + 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.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser({ 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.json'); + const data = fs.readFileSync(url).toString(); + const file = File({ + url, + data, + mediaType: mediaTypes.latest('json'), + }); + const parser = OpenApiJson2Parser(); + const parseResult = await parser.parse(file); + + assert.isUndefined(parseResult.api?.meta.get('sourceMap')); + }); + }); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/parse/parsers/openapi-json-3-0/index.ts b/packages/apidom-reference/test/parse/parsers/openapi-json-3-0/index.ts index 3800eafb67..754f0fddb5 100644 --- a/packages/apidom-reference/test/parse/parsers/openapi-json-3-0/index.ts +++ b/packages/apidom-reference/test/parse/parsers/openapi-json-3-0/index.ts @@ -66,7 +66,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.json'); const file = File({ @@ -79,7 +79,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.json'); const file = File({ @@ -95,7 +95,7 @@ describe('parsers', function () { }); context('parse', function () { - context('given OpenApi 3.1.x JSON data', function () { + context('given OpenApi 3.0.x JSON data', function () { specify('should return parse result', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.json'); const data = fs.readFileSync(url).toString(); @@ -111,7 +111,7 @@ describe('parsers', function () { }); }); - context('given OpenApi 3.1.x JSON data as buffer', function () { + context('given OpenApi 3.0.x JSON data as buffer', function () { specify('should return parse result', async function () { const url = path.join(__dirname, 'fixtures', 'sample-api.json'); const data = fs.readFileSync(url); @@ -127,7 +127,7 @@ describe('parsers', function () { }); }); - context('given data that is not an OpenApi 3.1.x JSON data', function () { + context('given data that is not an OpenApi 3.0.x JSON data', function () { specify('should coerce to string and parse', async function () { const file = File({ uri: '/path/to/file.json',