diff --git a/lib/bundle.js b/lib/bundle.js index 1a2a1e699..73de6da2a 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -14,7 +14,8 @@ const _ = require('lodash'), parse = require('./parse.js'), { ParseError } = require('./common/ParseError'), Utils = require('./utils'), - crypto = require('crypto'); + crypto = require('crypto'), + { isCircularReference } = require('./utils'); let path = require('path'), pathBrowserify = require('path-browserify'), @@ -481,16 +482,6 @@ function findReferenceByMainKeyInTraceFromContext(documentContext, mainKeyInTrac return relatedRef; } -/** - * Verifies if a node has same content as one of the parents so it is a circular ref - * @param {function} traverseContext - The context of the traverse function - * @param {object} contentFromTrace - The resolved content of the node to deref - * @returns {boolean} whether is circular reference or not. - */ -function isCircularReference(traverseContext, contentFromTrace) { - return traverseContext.parents.find((parent) => { return parent.node === contentFromTrace; }) !== undefined; -} - /** * Modifies content of a node if it is circular reference. * diff --git a/lib/deref.js b/lib/deref.js index c09ba9bec..fe4175bff 100644 --- a/lib/deref.js +++ b/lib/deref.js @@ -1,6 +1,7 @@ const _ = require('lodash'), mergeAllOf = require('json-schema-merge-allof'), { typesMap } = require('./common/schemaUtilsCommon'), + { isLocalRef } = require('./jsonPointer'), PARAMETER_SOURCE = { REQUEST: 'REQUEST', RESPONSE: 'RESPONSE' @@ -29,7 +30,9 @@ const _ = require('lodash'), ], DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'), traverseUtility = require('traverse'), - PROPERTIES_TO_ASSIGN_ON_CASCADE = ['type', 'nullable']; + PROPERTIES_TO_ASSIGN_ON_CASCADE = ['type', 'nullable'], + CIRCULAR_REF_KEY = '$circularRef', + { isCircularReference } = require('./utils'); /** * @param {*} currentNode - the object from which you're trying to find references @@ -201,6 +204,9 @@ module.exports = { return this.resolveAllOf(schema.allOf, parameterSourceOption, components, schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef), stackLimit); } + if (schema.hasOwnProperty(CIRCULAR_REF_KEY)) { + return schema; + } if (schema.$ref && _.isFunction(schema.$ref.split)) { let refKey = schema.$ref, outerProperties = concreteUtils.getOuterPropsIfIsSupported(schema); @@ -388,5 +394,67 @@ module.exports = { } return schema; + }, + + /** + * Take a $ref and a spec and return the referenced value + * @param {object} spec the parsed spec object + * @param {string} reference the $ref value to dereference + * @returns {object} the dereferenced $ref value + */ + dereferenceElement: function (spec, reference) { + let splitRef = reference.split('/'), + resolvedContent; + + splitRef = splitRef.slice(1).map((elem) => { + // https://swagger.io/docs/specification/using-ref#escape + // since / is the default delimiter, slashes are escaped with ~1 + return decodeURIComponent( + elem + .replace(/~1/g, '/') + .replace(/~0/g, '~') + ); + }); + + resolvedContent = this._getEscaped(spec, splitRef); + return resolvedContent; + }, + + /** + * Dereferences the values referenced from out of components/schemas element + * @param {object} spec The parsed specification + * @param {object} constraintRegexp The regexp to match against the $ref element's value + * @returns {object} The specification with the values referenced from other place than components/schemas + */ + dereferenceByConstraint: function(spec, constraintRegexp) { + let dereferencedSpec = _.cloneDeep(spec), + that = this, + seenContents = {}; + + traverseUtility(dereferencedSpec).forEach(function (property) { + if ( + typeof property === 'object' && + _.size(property) === 1 && + property.hasOwnProperty('$ref') && + isLocalRef(property, '$ref') && + ( + property.$ref.match(constraintRegexp) + ) + ) { + const contentFromRef = seenContents[property.$ref]; + if (isCircularReference(this, contentFromRef)) { + this.update({ [CIRCULAR_REF_KEY]: '' }); + } + else { + const dereferencedContent = that.dereferenceElement( + dereferencedSpec, + property.$ref + ); + seenContents[property.$ref] = dereferencedContent; + this.update(dereferencedContent); + } + } + }); + return dereferencedSpec; } }; diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 232ca8662..1a223daf9 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -558,6 +558,8 @@ module.exports = { */ generateTrieFromPaths: function (spec, options, fromWebhooks = false) { options = _.merge({}, defaultOptions, options); + // We only dereference the elements(if exist) that references to components/pathItems + spec = deref.dereferenceByConstraint(spec, /#\/components\/pathItems/); let concreteUtils = getConcreteSchemaUtils({ type: 'json', data: spec }), specComponentsAndUtils = { concreteUtils @@ -595,7 +597,6 @@ module.exports = { return methods; }; Object.assign(specComponentsAndUtils, concreteUtils.getRequiredData(spec)); - for (path in paths) { if (paths.hasOwnProperty(path)) { currentPathObject = paths[path]; diff --git a/lib/schemapack.js b/lib/schemapack.js index 0921d81fb..9a1980abd 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -1,5 +1,8 @@ 'use strict'; +const deref = require('./deref.js'), + { getNegativeRegexp } = require('./utils.js'); + // This is the default collection name if one can't be inferred from the OpenAPI spec const COLLECTION_NAME = 'Imported from OpenAPI 3.0', { getConcreteSchemaUtils } = require('./common/versionUtils.js'), @@ -272,8 +275,9 @@ class SchemaPack { if (error) { return callback(error); } - - this.openapi = newOpenapi; + // Here we resolve only the elements to reference to aou of components. + // All the dereferences to components are resolved in their own process + this.openapi = deref.dereferenceByConstraint(newOpenapi, getNegativeRegexp('#/components/')); // this cannot be attempted before validation specComponentsAndUtils = { concreteUtils }; Object.assign(specComponentsAndUtils, concreteUtils.getRequiredData(this.openapi)); diff --git a/lib/utils.js b/lib/utils.js index 1d1bcc90a..a38df981e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -142,5 +142,26 @@ module.exports = { res.push(segment); } return res.join('/'); + }, + + /** + * Generate a regexp to match with strings that does not contain the provided string + * @param {string} negatedWord The string we want to appear in the string + * @returns {object} The regexp that matches with strings that does not contain the provided string + */ + getNegativeRegexp: function (negatedWord) { + const regexpValue = `^(?!.*?${negatedWord}).*$`, + regexp = new RegExp(regexpValue); + return regexp; + }, + + /** + * Verifies if a node has same content as one of the parents in traverseUtil context so it is a circular ref + * @param {function} traverseContext - The context of the traverse function + * @param {object} contentFromTrace - The resolved content of the node to deref + * @returns {boolean} whether is circular reference or not. + */ + isCircularReference: function (traverseContext, contentFromTrace) { + return traverseContext.parents.find((parent) => { return parent.node === contentFromTrace; }) !== undefined; } }; diff --git a/test/data/valid_openapi/petstore-detailed-referenced-path.yaml b/test/data/valid_openapi/petstore-detailed-referenced-path.yaml new file mode 100644 index 000000000..47a654faf --- /dev/null +++ b/test/data/valid_openapi/petstore-detailed-referenced-path.yaml @@ -0,0 +1,1216 @@ +openapi: 3.0.0 +servers: + - url: http://petstore.swagger.io + description: 'Adding the variables' + variables: + temp1: + default: value1 + description: variable1 + enum: [value1, value2] + temp2: + default: value3 + description: variable2 + enum: [value3, value4] + - url: //petstore.swagger.io/sandbox + description: Sandbox server +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. + + # Introduction + This API is documented in **OpenAPI format** and is based on + [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. + It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard + OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md). + + # OpenAPI Specification + This API is documented in **OpenAPI format** and is based on + [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. + It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard + OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md). + + # Cross-Origin Resource Sharing + This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). + And that allows cross-domain communication from the browser. + All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. + + # Authentication + + Petstore offers two forms of authentication: + - API Key + - OAuth2 + OAuth2 - an open protocol to allow secure authorization in a simple + and standard method from web, mobile and desktop applications. + + + + version: 1.0.0 + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + name: API Support + email: apiteam@swagger.io + url: https://github.com/Redocly/redoc + x-logo: + url: 'https://redocly.github.io/redoc/petstore-logo.png' + altText: Petstore logo + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +externalDocs: + description: Find out how to create Github repo for your OpenAPI spec. + url: 'https://github.com/Rebilly/generator-openapi-repo' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + - name: pet_model + x-displayName: The Pet Model + description: | + + - name: store_model + x-displayName: The Order Model + description: | + +x-tagGroups: + - name: General + tags: + - pet + - store + - name: User Management + tags: + - user + - name: Models + tags: + - pet_model + - store_model +paths: + /pet: + parameters: + - name: Accept-Language + in: header + description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US" + example: en-US + required: false + schema: + type: string + default: en-AU + - name: cookieParam + in: cookie + description: Some cookie + required: true + schema: + type: integer + format: int64 + post: + tags: + - pet + summary: Add a new pet to the store + description: Add new pet to the store inventory. + operationId: addPet + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + x-code-samples: + - lang: 'C#' + source: | + PetStore.v1.Pet pet = new PetStore.v1.Pet(); + pet.setApiKey("your api key"); + pet.petType = PetStore.v1.Pet.TYPE_DOG; + pet.name = "Rex"; + // set other fields + PetStoreResponse response = pet.create(); + if (response.statusCode == HttpStatusCode.Created) + { + // Successfully created + } + else + { + // Something wrong -- check response for errors + Console.WriteLine(response.getRawResponse()); + } + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->create($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + x-code-samples: + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetId(1); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->update($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' + servers: + - url: https://{username}.gigantic-server.com:{port}/{basePath} + description: Common url for all operations in this path + variables: + username: + default: demo + description: Assigned by the service provider + port: + enum: + - '8843' + - '443' + + default: '8843' + basePath: + default: v2 + + '/pet/{petId}': + $ref: "#/x-operations/getAPet" + + '/pet/{petId}/uploadImage': + post: + tags: + - pet + - cat + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + /pet/findByStatus: + get: + tags: + - pet + - dog + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + schema: + type: array + minItems: 1 + maxItems: 3 + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + deprecated: true + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + 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 + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + prop123: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + content: + application/json: + example: + status: 400 + message: "Invalid Order" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/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 value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + minimum: 1 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /store/subscribe: + post: + summary: Subscribe to the Store events + description: Add subscription for a store events + requestBody: + content: + application/json: + schema: + type: object + properties: + callbackUrl: + type: string + format: uri + description: This URL will be called by the server when the desired event will occur + example: https://myserver.com/send/callback/here + eventName: + type: string + description: Event name for the subscription + enum: + - orderInProgress + - orderShipped + - orderDelivered + example: orderInProgress + required: + - callbackUrl + - eventName + responses: + '201': + description: Subscription added + content: + application/json: + schema: + type: object + properties: + subscriptionId: + type: string + example: AAA-123-BBB-456 + callbacks: + orderInProgress: + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': + servers: + - url: //callback-url.path-level/v1 + description: Path level server 1 + - url: //callback-url.path-level/v2 + description: Path level server 2 + post: + summary: Order in Progress (Summary) + description: A callback triggered every time an Order is updated status to "inProgress" (Description) + externalDocs: + description: Find out more + url: 'https://more-details.com/demo' + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + status: + type: string + example: 'inProgress' + application/xml: + schema: + type: object + properties: + orderId: + type: string + example: '123' + example: | + + + 123 + inProgress + 2018-10-19T16:46:45Z + + responses: + '200': + description: Callback successfully processed and no retries will be performed + content: + application/json: + schema: + type: object + properties: + someProp: + type: string + example: '123' + '299': + description: Response for cancelling subscription + '500': + description: Callback processing failed and retries will be performed + x-code-samples: + - lang: 'C#' + source: | + PetStore.v1.Pet pet = new PetStore.v1.Pet(); + pet.setApiKey("your api key"); + pet.petType = PetStore.v1.Pet.TYPE_DOG; + pet.name = "Rex"; + // set other fields + PetStoreResponse response = pet.create(); + if (response.statusCode == HttpStatusCode.Created) + { + // Successfully created + } + else + { + // Something wrong -- check response for errors + Console.WriteLine(response.getRawResponse()); + } + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->create($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + put: + description: Order in Progress (Only Description) + servers: + - url: //callback-url.operation-level/v1 + description: Operation level server 1 (Operation override) + - url: //callback-url.operation-level/v2 + description: Operation level server 2 (Operation override) + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + status: + type: string + example: 'inProgress' + application/xml: + schema: + type: object + properties: + orderId: + type: string + example: '123' + example: | + + + 123 + inProgress + 2018-10-19T16:46:45Z + + responses: + '200': + description: Callback successfully processed and no retries will be performed + content: + application/json: + schema: + type: object + properties: + someProp: + type: string + example: '123' + orderShipped: + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': + post: + description: | + Very long description + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + culpa qui officia deserunt mollit anim id est laborum. + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + estimatedDeliveryDate: + type: string + format: date-time + example: '2018-11-11T16:00:00Z' + responses: + '200': + description: Callback successfully processed and no retries will be performed + orderDelivered: + 'http://notificationServer.com?url={$request.body#/callbackUrl}&event={$request.body#/eventName}': + post: + deprecated: true + summary: Order delivered + description: A callback triggered every time an Order is delivered to the recipient + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + responses: + '200': + description: Callback successfully processed and no retries will be performed + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/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 + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/json: + schema: + type: string + examples: + response: + value: OK + application/xml: + schema: + type: string + examples: + response: + value: OK + text/plain: + examples: + response: + value: OK + '400': + description: Invalid username/password supplied + /user/logout: + get: + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation +components: + schemas: + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + Cat: + description: A representation of a cat + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + huntingSkill: + type: string + description: The measured skill for hunting + default: lazy + example: adventurous + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Category: + type: object + properties: + id: + description: Category ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Category name + type: string + minLength: 1 + sub: + description: Test Sub Category + type: object + properties: + prop1: + type: string + description: Dumb Property + xml: + name: Category + Dog: + description: A representation of a dog + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + packSize: + type: integer + format: int32 + description: The size of the pack the dog is from + default: 1 + minimum: 1 + required: + - packSize + HoneyBee: + description: A representation of a honey bee + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + honeyPerDay: + type: number + description: Average amount of honey produced per day in ounces + example: 3.14 + multipleOf: .01 + required: + - honeyPerDay + Id: + type: integer + format: int64 + readOnly: true + Order: + type: object + properties: + id: + description: Order ID + allOf: + - $ref: '#/components/schemas/Id' + petId: + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + quantity: + type: integer + format: int32 + minimum: 1 + default: 1 + shipDate: + description: Estimated ship date + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + description: Indicates whenever order was completed or not + type: boolean + default: false + readOnly: true + requestId: + description: Unique Request Id + type: string + writeOnly: true + xml: + name: Order + Pet: + type: object + required: + - name + - photoUrls + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + bee: '#/components/schemas/HoneyBee' + properties: + id: + externalDocs: + description: "Find more info here" + url: "https://example.com" + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + category: + description: Categories this pet belongs to + allOf: + - $ref: '#/components/schemas/Category' + name: + description: The name given to a pet + type: string + example: Guru + photoUrls: + description: The list of URL to a cute photos featuring pet + type: array + maxItems: 20 + xml: + name: photoUrl + wrapped: true + items: + type: string + format: url + friend: + allOf: + - $ref: '#/components/schemas/Pet' + tags: + description: Tags attached to the pet + type: array + minItems: 1 + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: Pet status in the store + enum: + - available + - pending + - sold + petType: + description: Type of a pet + type: string + xml: + name: Pet + Tag: + type: object + properties: + id: + description: Tag ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Tag name + type: string + minLength: 1 + xml: + name: Tag + User: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + pet: + oneOf: + - $ref: '#/components/schemas/Pet' + - $ref: '#/components/schemas/Tag' + username: + description: User supplied username + type: string + minLength: 4 + example: John78 + firstName: + description: User first name + type: string + minLength: 1 + example: John + lastName: + description: User last name + type: string + minLength: 1 + example: Smith + email: + description: User email address + type: string + format: email + example: john.smith@example.com + password: + type: string + description: >- + User password, MUST contain a mix of upper and lower case letters, + as well as digits + format: password + minLength: 8 + pattern: '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/' + example: drowssaP123 + phone: + description: User phone number in international format + type: string + pattern: '/^\+(?:[0-9]-?){6,14}[0-9]$/' + example: +1-202-555-0192 + userStatus: + description: User status + type: integer + format: int32 + xml: + name: User + requestBodies: + Pet: + content: + application/json: + schema: + allOf: + - description: My Pet + title: Pettie + - $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + securitySchemes: + petstore_auth: + description: | + Get access to data while protecting your account credentials. + OAuth2 is also a safer and more secure way to give you access. + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + description: > + For this sample, you can use the api key `special-key` to test the + authorization filters. + type: apiKey + name: api_key + in: header + examples: + Order: + value: + quantity: 1 + shipDate: '2018-10-19T16:46:45Z' + status: placed + complete: false + +x-operations: + getAPet: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + deprecated: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/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 + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + example: "Bearer " + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' diff --git a/test/data/valid_openapi/referencedAllFromComponents.yaml b/test/data/valid_openapi/referencedAllFromComponents.yaml new file mode 100644 index 000000000..779748316 --- /dev/null +++ b/test/data/valid_openapi/referencedAllFromComponents.yaml @@ -0,0 +1,206 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/components/links/UserRepositories' + parameters: + - "$ref": "#/components/parameters/theParam" + responses: + "200": + "$ref": "#/components/responses/200" + default: + "$ref": "#/components/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/components/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + /pets/{petId}: + $ref: "#/x-operations/getAPet" +components: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + examples: + petExample: + "$ref": "#/components/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/components/headers/theHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + examples: + petsExample: + "$ref": "#/components/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" +x-operations: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi/referencedAllFromComponentsAndFromOut.yaml b/test/data/valid_openapi/referencedAllFromComponentsAndFromOut.yaml new file mode 100644 index 000000000..ceb7be578 --- /dev/null +++ b/test/data/valid_openapi/referencedAllFromComponentsAndFromOut.yaml @@ -0,0 +1,172 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/components/links/UserRepositories' + parameters: + - "$ref": "#/components/parameters/theParam" + responses: + "$ref": "#/components/responses" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + /pets/{petId}: + $ref: "#/x-operations/getAPet" +components: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + Pet: + content: + application/json: + schema: + allOf: + - description: My Pet + title: Pettie + - $ref: '#/components/schemas/Pet' + examples: + response: + "$ref": "#/components/examples/response" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + "$ref": "#/components/headers/theHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + response: + value: { + description: My Pet, + title: Pettie, + id: 1, + name: bob + } + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" +x-operations: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi/referencedAllFromOutOfComponents.yaml b/test/data/valid_openapi/referencedAllFromOutOfComponents.yaml new file mode 100644 index 000000000..213c138aa --- /dev/null +++ b/test/data/valid_openapi/referencedAllFromOutOfComponents.yaml @@ -0,0 +1,206 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/componentsFromOut/links/UserRepositories' + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" + default: + "$ref": "#/componentsFromOut/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/componentsFromOut/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + /pets/{petId}: + $ref: "#/x-operations/getAPet" +componentsFromOut: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/componentsFromOut/schemas/Pet" + examples: + petExample: + "$ref": "#/componentsFromOut/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/componentsFromOut/headers/theHeader" + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + examples: + petsExample: + "$ref": "#/componentsFromOut/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/componentsFromOut/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" +x-operations: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi/referencedAllFromOutOfComponentsCircular.yaml b/test/data/valid_openapi/referencedAllFromOutOfComponentsCircular.yaml new file mode 100644 index 000000000..def0e88fd --- /dev/null +++ b/test/data/valid_openapi/referencedAllFromOutOfComponentsCircular.yaml @@ -0,0 +1,76 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" +componentsFromOut: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + petData: + $ref: "#/componentsFromOut/schemas/other" + Pets: + type: array + items: + $ref: "#/componentsFromOut/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" +placeOut: + theSchema: + type: object + properties: + petData: + $ref: "#/componentsFromOut/schemas/Pet" diff --git a/test/data/valid_openapi/referencedAllPathsFromOutOfComponents.yaml b/test/data/valid_openapi/referencedAllPathsFromOutOfComponents.yaml new file mode 100644 index 000000000..8bf25beb5 --- /dev/null +++ b/test/data/valid_openapi/referencedAllPathsFromOutOfComponents.yaml @@ -0,0 +1,208 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + "$ref": "#/componentsFromOut/allPaths" +componentsFromOut: + allPaths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/componentsFromOut/links/UserRepositories' + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" + default: + "$ref": "#/componentsFromOut/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/componentsFromOut/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + /pets/{petId}: + $ref: "#/x-operations/getAPet" + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/componentsFromOut/schemas/Pet" + examples: + petExample: + "$ref": "#/componentsFromOut/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/componentsFromOut/headers/theHeader" + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + examples: + petsExample: + "$ref": "#/componentsFromOut/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/componentsFromOut/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" +x-operations: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi/referencedSchemas2DependOnSameOutOfComponents.yaml b/test/data/valid_openapi/referencedSchemas2DependOnSameOutOfComponents.yaml new file mode 100644 index 000000000..90b4400d1 --- /dev/null +++ b/test/data/valid_openapi/referencedSchemas2DependOnSameOutOfComponents.yaml @@ -0,0 +1,93 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" +componentsFromOut: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + schemas: + Dog: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + petData: + $ref: "#/componentsFromOut/schemas/other" + Cat: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + petData: + $ref: "#/componentsFromOut/schemas/other" + Pets: + type: object + properties: + dog: + $ref: "#/componentsFromOut/schemas/Dog" + cat: + $ref: "#/componentsFromOut/schemas/Cat" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + type: object + properties: + child: + $ref: "#/componentsFromOut/schemas/childOfOther" + childOfOther: + type: string diff --git a/test/data/valid_openapi31X/json/multiple_refs_and_paths.json b/test/data/valid_openapi31X/json/multiple_refs_and_paths.json new file mode 100644 index 000000000..40eb4c6df --- /dev/null +++ b/test/data/valid_openapi31X/json/multiple_refs_and_paths.json @@ -0,0 +1,101 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1" + } + ], + "paths": { + "/pets": { + "$ref": "#/components/pathItems/pets" + } + + }, + "components": { + "schemas": { + "RequestBody": { + "required": [ + "requestId", + "requestName" + ], + "properties": { + "requestId": { + "type": "integer", + "format": "int64", + "examples": [123456] + }, + "requestName": { + "type": "string" + } + } + }, + "ResponseBody": { + "required": [ + "responseId", + "responseName" + ], + "properties": { + "responseId": { + "type": "integer", + "format": "int64", + "examples": [234] + }, + "responseName": { + "type":"string", + "examples": ["200 OK Response"] + } + } + } + }, + "pathItems": { + "pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "key1": { + "$ref":"#/components/schemas/RequestBody" + }, + "key2": { + "$ref":"#/components/schemas/RequestBody" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "An paged array of pets", + "content": { + "application/json": { + "schema": { + "properties":{ + "key1": { + "$ref": "#/components/schemas/ResponseBody" + }, + "key2": { + "$ref": "#/components/schemas/ResponseBody" + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/test/data/valid_openapi31X/yaml/referencedAllFromComponents.yaml b/test/data/valid_openapi31X/yaml/referencedAllFromComponents.yaml new file mode 100644 index 000000000..62c450b9c --- /dev/null +++ b/test/data/valid_openapi31X/yaml/referencedAllFromComponents.yaml @@ -0,0 +1,206 @@ +openapi: "3.1" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/components/links/UserRepositories' + parameters: + - "$ref": "#/components/parameters/theParam" + responses: + "200": + "$ref": "#/components/responses/200" + default: + "$ref": "#/components/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/components/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + /pets/{petId}: + "$ref": "#/components/pathItems/getAPet" +components: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + examples: + petExample: + "$ref": "#/components/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/components/headers/theHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + examples: + petsExample: + "$ref": "#/components/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" + pathItems: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi31X/yaml/referencedAllFromOutOfComponents.yaml b/test/data/valid_openapi31X/yaml/referencedAllFromOutOfComponents.yaml new file mode 100644 index 000000000..6edd69660 --- /dev/null +++ b/test/data/valid_openapi31X/yaml/referencedAllFromOutOfComponents.yaml @@ -0,0 +1,221 @@ +openapi: "3.1" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/componentsFromOut/links/UserRepositories' + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" + default: + "$ref": "#/componentsFromOut/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + "$ref": "#/componentsFromOut/schemas/Pet" + examples: + petExample: + "$ref": "#/componentsFromOut/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + /pets/{petId}: + "$ref": "#/componentsFromOut/pathItems/getAPet" +componentsFromOut: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/componentsFromOut/schemas/Pet" + examples: + petExample: + "$ref": "#/componentsFromOut/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/componentsFromOut/headers/theHeader" + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + examples: + petsExample: + "$ref": "#/componentsFromOut/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/componentsFromOut/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + other: + $ref: "#/placeOut/theSchema" + pathItems: + getAPet: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + +placeOut: + theSchema: + type: string diff --git a/test/data/valid_openapi31X/yaml/referencedAllPathsFromComponents.yaml b/test/data/valid_openapi31X/yaml/referencedAllPathsFromComponents.yaml new file mode 100644 index 000000000..dd4fa8bb7 --- /dev/null +++ b/test/data/valid_openapi31X/yaml/referencedAllPathsFromComponents.yaml @@ -0,0 +1,199 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + "$ref": "#/components/pathItems" +components: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + examples: + petExample: + "$ref": "#/components/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/components/headers/theHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + examples: + petsExample: + "$ref": "#/components/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + pathItems: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/components/links/UserRepositories' + parameters: + - "$ref": "#/components/parameters/theParam" + responses: + "200": + "$ref": "#/components/responses/200" + default: + "$ref": "#/components/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/components/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/components/responses/default" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" diff --git a/test/data/valid_openapi31X/yaml/referencedAllPathsFromOutOfComponents.yaml b/test/data/valid_openapi31X/yaml/referencedAllPathsFromOutOfComponents.yaml new file mode 100644 index 000000000..7fb0c82c7 --- /dev/null +++ b/test/data/valid_openapi31X/yaml/referencedAllPathsFromOutOfComponents.yaml @@ -0,0 +1,199 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + "$ref": "#/componentsFromOut/pathItems" +componentsFromOut: + parameters: + theParam: + name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + requestBodies: + PetRequest: + content: + application/json: + schema: + "$ref": "#/componentsFromOut/schemas/Pet" + examples: + petExample: + "$ref": "#/componentsFromOut/examples/petExample" + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + responses: + '200': + description: A paged array of pets + headers: + theHeader: + "$ref": "#/componentsFromOut/headers/theHeader" + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pets" + examples: + petsExample: + "$ref": "#/componentsFromOut/examples/petsExample" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" + headers: + theHeader: + description: the header + schema: + type: string + links: + UserRepositories: + operationId: getRepositoriesByOwner + parameters: + username: $response.body#/username + examples: + petExample: + value: { + id: 1, + name: bob + } + petsExample: + value: [{ + id: 1, + name: bob + }] + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/componentsFromOut/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + pathItems: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + links: + userRepositories: + $ref: '#/componentsFromOut/links/UserRepositories' + parameters: + - "$ref": "#/componentsFromOut/parameters/theParam" + responses: + "200": + "$ref": "#/componentsFromOut/responses/200" + default: + "$ref": "#/componentsFromOut/responses/default" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + callbacks: + # the name `onData` is a convenience locator + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates + put: + summary: Create a pet put + operationId: createPets + tags: + - pets + requestBody: + $ref: '#/componentsFromOut/requestBodies/PetRequest' + responses: + '201': + description: Null response + default: + "$ref": "#/componentsFromOut/responses/default" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/componentsFromOut/schemas/Error" diff --git a/test/unit/base.test.js b/test/unit/base.test.js index fc4acc77f..bc77a2a06 100644 --- a/test/unit/base.test.js +++ b/test/unit/base.test.js @@ -74,6 +74,18 @@ describe('CONVERT FUNCTION TESTS ', function() { specWithAuthDigest = path.join(__dirname, VALID_OPENAPI_PATH + '/specWithAuthDigest.yaml'), specWithAuthOauth1 = path.join(__dirname, VALID_OPENAPI_PATH + '/specWithAuthOauth1.yaml'), specWithAuthBasic = path.join(__dirname, VALID_OPENAPI_PATH + '/specWithAuthBasic.yaml'), + referencedAllFromOutOfComponents = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedAllFromOutOfComponents.yaml'), + referencedAllPathsFromOutOfComponents = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedAllPathsFromOutOfComponents.yaml'), + referencedPathFromOutOfComponentsTagsStrategy = + path.join(__dirname, VALID_OPENAPI_PATH + '/petstore-detailed-referenced-path.yaml'), + referencedAllFromComponents = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedAllFromComponents.yaml'), + referencedAllFromOutOfComponentsCircular = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedAllFromOutOfComponentsCircular.yaml'), + referencedSchemas2DependOnSameOutOfComponents = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedSchemas2DependOnSameOutOfComponents.yaml'), schemaWithArrayTypeAndAdditionalProperties = path.join(__dirname, VALID_OPENAPI_PATH + '/schemaWithArrayTypeAndAdditionalProperties.yaml'), xmlRequestAndResponseBody = path.join(__dirname, VALID_OPENAPI_PATH, '/xmlRequestAndResponseBody.json'), @@ -1570,6 +1582,287 @@ describe('CONVERT FUNCTION TESTS ', function() { }); }); + describe('[Github #309 - Should convert a path when is referenced ' + + 'from a different place than components]', function() { + + describe('Validate that all references from out of components are resolved correctly', function() { + it('Should convert and include the referenced responses by referencing individual', function(done) { + Converter.convert({ type: 'file', data: + referencedAllFromOutOfComponents }, {}, (err, conversionResult) => { + const individualResponseReferencedContainer = conversionResult.output[0] + .data.item[0].item[0].response; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(individualResponseReferencedContainer.length) + .to.equal(2); + done(); + }); + }); + + it('Should convert and include the referenced parameters by referencing' + + ' individual', function(done) { + Converter.convert({ type: 'file', data: + referencedAllFromOutOfComponents }, {}, (err, conversionResult) => { + const referencedParameterContainer = conversionResult.output[0] + .data.item[0].item[0].request.url.query; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedParameterContainer.length) + .to.equal(1); + expect(referencedParameterContainer[0].key) + .to.equal('limit'); + done(); + }); + }); + + it('Should convert and include the referenced examples by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromOutOfComponents }, + { requestParametersResolution: 'Example' }, + (err, conversionResult) => { + const referencedRequestExample = conversionResult.output[0] + .data.item[0].item[2].request.body.raw, + referencedResponseExample = conversionResult.output[0] + .data.item[0].item[0].response[0].body; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestExample) + .to.equal('{\n \"id\": 1,\n \"name\": \"bob\"\n}'); + expect(referencedResponseExample) + .to.equal('[\n {\n \"id\": 1,\n \"name\": \"bob\"\n }\n]'); + done(); + } + ); + }); + + it('Should convert and include the referenced requestBody by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromOutOfComponents }, + {}, + (err, conversionResult) => { + const referencedRequestBody = conversionResult.output[0] + .data.item[0].item[2].request.body.raw; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestBody) + .to.equal('{\n \"id\": \"\",\n \"name\": \"\",\n \"tag\": \"\"\n}'); + done(); + } + ); + }); + + it('Should convert and include the referenced headers by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromOutOfComponents }, + {}, + (err, conversionResult) => { + const referencedHeader = conversionResult.output[0] + .data.item[0].item[0].response[0].header[0]; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedHeader.description) + .to.equal('the header'); + expect(referencedHeader.key) + .to.equal('theHeader'); + done(); + } + ); + }); + + it('Should convert and include the paths by referencing all from out of components', function(done) { + Converter.convert( + { type: 'file', data: referencedAllPathsFromOutOfComponents }, + {}, + (err, conversionResult) => { + const referencedHeader = conversionResult.output[0] + .data.item[0].item[0].response[0].header[0]; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedHeader.description) + .to.equal('the header'); + expect(referencedHeader.key) + .to.equal('theHeader'); + done(); + } + ); + }); + + it('Should convert and include the referenced paths using tags as folder strategy', function(done) { + Converter.convert( + { + type: 'file', + data: referencedPathFromOutOfComponentsTagsStrategy + }, + { + folderStrategy: 'Tags' + }, + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(7); + done(); + } + ); + }); + + it('Should avoid circular references', function(done) { + Converter.convert( + { + type: 'file', + data: referencedAllFromOutOfComponentsCircular + }, + {}, + (err, conversionResult) => { + const responseBody = JSON.parse(conversionResult.output[0].data.item[0].response[0].body); + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + expect(responseBody[0]).to.include.all.keys('id', 'name', 'tag', 'petData'); + expect(responseBody[0].petData.petData.$circularRef) + .to.equal(''); + done(); + } + ); + }); + + it('Should resolve two schemas that depend on the same branch from out of components', function(done) { + Converter.convert( + { + type: 'file', + data: referencedSchemas2DependOnSameOutOfComponents + }, + {}, + (err, conversionResult) => { + const responseBody = conversionResult.output[0].data.item[0].response[0].body; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + expect(responseBody.includes('$circularRef')).to.false; + expect(JSON.parse(responseBody)).to.include.all.keys('dog', 'cat'); + done(); + } + ); + }); + }); + + describe('Validate that all references from components are resolved correctly', function () { + it('Should convert and include the referenced responses by referencing individual', function(done) { + Converter.convert({ type: 'file', data: + referencedAllFromComponents }, {}, (err, conversionResult) => { + const individualResponseReferencedContainer = conversionResult.output[0] + .data.item[0].item[0].response; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(individualResponseReferencedContainer.length) + .to.equal(2); + done(); + }); + }); + + it('Should convert and include the referenced parameters by referencing individual', function(done) { + Converter.convert({ type: 'file', data: + referencedAllFromComponents }, {}, (err, conversionResult) => { + const referencedParameterContainer = conversionResult.output[0] + .data.item[0].item[0].request.url.query; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedParameterContainer.length) + .to.equal(1); + expect(referencedParameterContainer[0].key) + .to.equal('limit'); + done(); + }); + }); + + it('Should convert and include the referenced examples by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromComponents }, + { requestParametersResolution: 'Example' }, + (err, conversionResult) => { + const referencedRequestExample = conversionResult.output[0] + .data.item[0].item[2].request.body.raw, + referencedResponseExample = conversionResult.output[0] + .data.item[0].item[0].response[0].body; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestExample) + .to.equal('{\n \"id\": 1,\n \"name\": \"bob\"\n}'); + expect(referencedResponseExample) + .to.equal('[\n {\n \"id\": 1,\n \"name\": \"bob\"\n }\n]'); + done(); + } + ); + }); + + it('Should convert and include the referenced requestBody by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromComponents }, + {}, + (err, conversionResult) => { + const referencedRequestBody = conversionResult.output[0] + .data.item[0].item[2].request.body.raw; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestBody) + .to.equal('{\n \"id\": \"\",\n \"name\": \"\",\n \"tag\": \"\"\n}'); + done(); + } + ); + }); + + it('Should convert and include the referenced headers by referencing individual', function(done) { + Converter.convert( + { type: 'file', data: referencedAllFromComponents }, + {}, + (err, conversionResult) => { + const referencedHeader = conversionResult.output[0] + .data.item[0].item[0].response[0].header[0]; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedHeader.description) + .to.equal('the header'); + expect(referencedHeader.key) + .to.equal('theHeader'); + done(); + } + ); + }); + }); + }); + it('Should convert and resolve xml bodies correctly when prefix is provided', function(done) { var openapi = fs.readFileSync(xmlRequestAndResponseBody, 'utf8'); Converter.convert( diff --git a/test/unit/deref.test.js b/test/unit/deref.test.js index 8fb314f66..dcd521c08 100644 --- a/test/unit/deref.test.js +++ b/test/unit/deref.test.js @@ -1,7 +1,12 @@ var expect = require('chai').expect, _ = require('lodash'), deref = require('../../lib/deref.js'), - schemaUtils30X = require('./../../lib/30XUtils/schemaUtils30X'); + schemaUtils30X = require('./../../lib/30XUtils/schemaUtils30X'), + { getNegativeRegexp } = require('../../lib/utils'), + fs = require('fs'), + path = require('path'), + VALID_OPENAPI_PATH = '../data/valid_openapi', + { getConcreteSchemaUtils } = require('./../../lib/common/versionUtils'); describe('DEREF FUNCTION TESTS ', function() { describe('resolveRefs Function', function () { @@ -414,3 +419,23 @@ describe('DEREF FUNCTION TESTS ', function() { }); }); }); + +describe('dereferenceByConstraint method', function() { + + it('Should dereference ONLY all out of components in root file', function() { + let referencedAllFromComponentsAndOut = + path.join(__dirname, VALID_OPENAPI_PATH + '/referencedAllFromComponentsAndFromOut.yaml'), + fileData = fs.readFileSync(referencedAllFromComponentsAndOut, 'utf-8'), + concreteUtils = getConcreteSchemaUtils(fileData), + parsedContent = concreteUtils.parseSpec(fileData, {}); + const dereferencedSpec = deref.dereferenceByConstraint( + parsedContent.openapi, + getNegativeRegexp('#/components/') + ), + referencedPathContent = JSON.stringify(parsedContent.openapi['x-operations'].getAPet), + referencedSchemaContent = JSON.stringify(parsedContent.openapi.placeOut.theSchema); + + expect(JSON.stringify(dereferencedSpec.paths['/pets/{petId}'])).to.equal(referencedPathContent); + expect(JSON.stringify(dereferencedSpec.components.schemas.other)).to.equal(referencedSchemaContent); + }); +}); diff --git a/test/unit/x31schemapack.test.js b/test/unit/x31schemapack.test.js index 290d4d27b..c976d46f3 100644 --- a/test/unit/x31schemapack.test.js +++ b/test/unit/x31schemapack.test.js @@ -23,6 +23,21 @@ describe('Testing openapi 3.1 schema pack convert', function() { }); }); + it('Should convert from openapi 3.1 spec to postman collection -- multiple refs_and_paths', function() { + const fileSource = path.join(__dirname, OPENAPI_31_FOLDER + '/json/multiple_refs_and_paths.json'), + fileData = fs.readFileSync(fileSource, 'utf8'), + input = { + type: 'string', + data: fileData + }, + converter = new SchemaPack(input); + + converter.convert((err, result) => { + expect(err).to.be.null; + expect(result.result).to.be.true; + }); + }); + it('Should interpret binary types correctly and set mode as file in body -- petstore', function() { const fileSource = path.join(__dirname, OPENAPI_31_FOLDER + '/json/petstore.json'), fileData = fs.readFileSync(fileSource, 'utf8'), @@ -121,6 +136,294 @@ describe('Testing openapi 3.1 schema pack convert', function() { }); }); + describe('[Github #309 - Should convert a path when is referenced ' + + 'from a different place than components]', function() { + const referencedAllFromOutOfComponents = + path.join(__dirname, OPENAPI_31_FOLDER + '/yaml/referencedAllFromOutOfComponents.yaml'), + referencedAllPathsFromOutOfComponents = + path.join(__dirname, OPENAPI_31_FOLDER + '/yaml/referencedAllPathsFromOutOfComponents.yaml'), + referencedAllFromComponents = + path.join(__dirname, OPENAPI_31_FOLDER + '/yaml/referencedAllFromComponents.yaml'), + referencedAllPathsFromComponents = + path.join(__dirname, OPENAPI_31_FOLDER + '/yaml/referencedAllPathsFromComponents.yaml'); + + describe('Validate that all references from out of components are resolved correctly', function() { + it('Should convert and include the referenced responses by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + {} + ); + Converter.convert((err, conversionResult) => { + const individualResponseReferencedContainer = conversionResult.output[0] + .data.item[0].item[0].response; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(individualResponseReferencedContainer.length) + .to.equal(2); + done(); + }); + }); + + it('Should convert and include the referenced parameters by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + {} + ); + Converter.convert((err, conversionResult) => { + const referencedParameterContainer = conversionResult.output[0] + .data.item[0].item[0].request.url.query; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedParameterContainer.length) + .to.equal(1); + expect(referencedParameterContainer[0].key) + .to.equal('limit'); + done(); + }); + }); + + it('Should convert and include the referenced examples by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + { requestParametersResolution: 'Example' } + ); + Converter.convert( + (err, conversionResult) => { + const referencedRequestExample = conversionResult.output[0] + .data.item[0].item[2].request.body.raw, + referencedResponseExample = conversionResult.output[0] + .data.item[0].item[0].response[0].body; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestExample) + .to.equal('{\n \"id\": 1,\n \"name\": \"bob\"\n}'); + expect(referencedResponseExample) + .to.equal('[\n {\n \"id\": 1,\n \"name\": \"bob\"\n }\n]'); + done(); + } + ); + }); + + it('Should convert and include the referenced requestBody by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + const referencedRequestBody = JSON.parse(conversionResult.output[0] + .data.item[0].item[2].request.body.raw); + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestBody) + .to.have.all.keys('id', 'name', 'tag'); + done(); + } + ); + }); + + it('Should convert and include the referenced headers by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + const referencedHeader = conversionResult.output[0] + .data.item[0].item[0].response[0].header[0]; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedHeader.description) + .to.equal('the header'); + expect(referencedHeader.key) + .to.equal('theHeader'); + done(); + } + ); + }); + + it('Should convert and include the referenced paths by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromOutOfComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + done(); + } + ); + }); + + it('Should convert and include the referenced paths by referencing all from out', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllPathsFromOutOfComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + done(); + } + ); + }); + }); + + describe('Validate that all references from components are resolved correctly', function() { + it('Should convert and include the referenced responses by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + {} + ); + Converter.convert((err, conversionResult) => { + const individualResponseReferencedContainer = conversionResult.output[0] + .data.item[0].item[0].response; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(individualResponseReferencedContainer.length) + .to.equal(2); + done(); + }); + }); + + it('Should convert and include the referenced parameters by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + {} + ); + Converter.convert((err, conversionResult) => { + const referencedParameterContainer = conversionResult.output[0] + .data.item[0].item[0].request.url.query; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedParameterContainer.length) + .to.equal(1); + expect(referencedParameterContainer[0].key) + .to.equal('limit'); + done(); + }); + }); + + it('Should convert and include the referenced examples by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + { requestParametersResolution: 'Example' } + ); + Converter.convert( + (err, conversionResult) => { + const referencedRequestExample = conversionResult.output[0] + .data.item[0].item[2].request.body.raw, + referencedResponseExample = conversionResult.output[0] + .data.item[0].item[0].response[0].body; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestExample) + .to.equal('{\n \"id\": 1,\n \"name\": \"bob\"\n}'); + expect(referencedResponseExample) + .to.equal('[\n {\n \"id\": 1,\n \"name\": \"bob\"\n }\n]'); + done(); + } + ); + }); + + it('Should convert and include the referenced requestBody by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + const referencedRequestBody = JSON.parse(conversionResult.output[0] + .data.item[0].item[2].request.body.raw); + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedRequestBody) + .to.have.all.keys('id', 'name', 'tag'); + done(); + } + ); + }); + + it('Should convert and include the referenced headers by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + const referencedHeader = conversionResult.output[0] + .data.item[0].item[0].response[0].header[0]; + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + expect(referencedHeader.description) + .to.equal('the header'); + expect(referencedHeader.key) + .to.equal('theHeader'); + done(); + } + ); + }); + + it('Should convert and include the referenced paths by referencing individually', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllFromComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + done(); + } + ); + }); + + it('Should convert and include the referenced paths by referencing all from pathItems', function(done) { + const Converter = new SchemaPack( + { type: 'file', data: referencedAllPathsFromComponents }, + {} + ); + Converter.convert( + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output[0].data.item[0].item.length) + .to.equal(4); + done(); + } + ); + }); + }); + }); + it('Should convert from openapi 3.1, exclude deprecated operations - has only one op and is deprecated', function() { const fileSource = path.join(__dirname, OPENAPI_31_FOLDER + '/json/has_one_op_dep.json'), fileData = fs.readFileSync(fileSource, 'utf8'),