mirror of
https://github.com/postmanlabs/openapi-to-postman.git
synced 2022-11-29 22:05:00 +03:00
Support circularRefs inline
Support circularRefs inline
This commit is contained in:
@@ -22,7 +22,8 @@ let path = require('path'),
|
||||
{ DFS } = require('./dfs'),
|
||||
deref = require('./deref.js'),
|
||||
{ isSwagger, getBundleRulesDataByVersion } = require('./common/versionUtils'),
|
||||
CIRCULAR_REF_EXT_PROP = 'x-circularRef';
|
||||
CIRCULAR_REF_EXT_PROP = 'x-circularRef',
|
||||
CIRCULAR_Or_REF_EXT_PROP = 'x-orRef';
|
||||
|
||||
|
||||
/**
|
||||
@@ -481,6 +482,33 @@ function findReferenceByMainKeyInTraceFromContext(documentContext, mainKeyInTrac
|
||||
return relatedRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if a node has same content than 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.
|
||||
* @param {function} traverseContext - The context of the traverse function
|
||||
* @param {object} documentContext The document context from root
|
||||
* @returns {undefined} nothing
|
||||
*/
|
||||
function handleCircularReference(traverseContext, documentContext) {
|
||||
let relatedRef = '';
|
||||
if (traverseContext.circular) {
|
||||
relatedRef = findReferenceByMainKeyInTraceFromContext(documentContext, traverseContext.circular.key);
|
||||
traverseContext.update({ $ref: relatedRef, [CIRCULAR_REF_EXT_PROP]: true });
|
||||
}
|
||||
if (traverseContext.keys && traverseContext.keys.includes(CIRCULAR_Or_REF_EXT_PROP)) {
|
||||
traverseContext.update({ $ref: traverseContext.node[CIRCULAR_Or_REF_EXT_PROP], [CIRCULAR_REF_EXT_PROP]: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the components object from the documentContext data
|
||||
* @param {object} documentContext The document context from root
|
||||
@@ -541,6 +569,16 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver
|
||||
if (!contentFromTrace) {
|
||||
refData.nodeContent = { $ref: `${localPointer + local}` };
|
||||
}
|
||||
else if (isCircularReference(this, contentFromTrace)) {
|
||||
if (refData.inline) {
|
||||
refData.nodeContent = { [CIRCULAR_Or_REF_EXT_PROP]: tempRef, [CIRCULAR_REF_EXT_PROP]: true };
|
||||
}
|
||||
else {
|
||||
refData.node = { [CIRCULAR_Or_REF_EXT_PROP]: refData.reference,
|
||||
[CIRCULAR_REF_EXT_PROP]: true };
|
||||
refData.nodeContent = contentFromTrace;
|
||||
}
|
||||
}
|
||||
else {
|
||||
refData.nodeContent = contentFromTrace;
|
||||
}
|
||||
@@ -551,38 +589,28 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver
|
||||
refData.node = hasSiblings ?
|
||||
_.merge(referenceSiblings, refData.nodeContent) :
|
||||
refData.nodeContent;
|
||||
documentContext.globalReferences[property.$ref].reference =
|
||||
documentContext.globalReferences[tempRef].reference =
|
||||
resolveJsonPointerInlineNodes(this.parents, this.key);
|
||||
}
|
||||
this.update(refData.node);
|
||||
if (!refData.inline) {
|
||||
if (documentContext.globalReferences[tempRef].refHasContent) {
|
||||
setValueInComponents(
|
||||
refData.keyInComponents,
|
||||
components,
|
||||
refData.nodeContent,
|
||||
COMPONENTS_KEYS
|
||||
);
|
||||
}
|
||||
else if (refData.refHasContent) {
|
||||
setValueInComponents(
|
||||
refData.keyInComponents,
|
||||
components,
|
||||
refData.nodeContent,
|
||||
COMPONENTS_KEYS
|
||||
);
|
||||
}
|
||||
this.update(refData.node);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
resRoot: traverseUtility(rootContent).map(function () {
|
||||
let relatedRef = '';
|
||||
if (this.circular) {
|
||||
relatedRef = findReferenceByMainKeyInTraceFromContext(documentContext, this.circular.key);
|
||||
this.update({ $ref: relatedRef, [CIRCULAR_REF_EXT_PROP]: true });
|
||||
}
|
||||
handleCircularReference(this, documentContext);
|
||||
}),
|
||||
newComponents: traverseUtility(components).map(function () {
|
||||
let relatedRef = '';
|
||||
if (this.circular) {
|
||||
relatedRef = findReferenceByMainKeyInTraceFromContext(documentContext, this.circular.key);
|
||||
this.update({ $ref: relatedRef, [CIRCULAR_REF_EXT_PROP]: true });
|
||||
}
|
||||
handleCircularReference(this, documentContext);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/_schemas_schemas.yaml-components_schemas_ErrorDetail"
|
||||
"$ref": "#/components/schemas/_schemas_schemas.yaml-_components_schemas_ErrorDetail"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"_schemas_schemas.yaml-components_schemas_ErrorDetail": {
|
||||
"_schemas_schemas.yaml-_components_schemas_ErrorDetail": {
|
||||
"type": "object",
|
||||
"description": "The error detail.",
|
||||
"properties": {
|
||||
@@ -63,7 +63,7 @@
|
||||
"readOnly": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/_schemas_schemas.yaml-components_schemas_ErrorDetail",
|
||||
"$ref": "#/components/schemas/_schemas_schemas.yaml-_components_schemas_ErrorDetail",
|
||||
"x-circularRef": true
|
||||
},
|
||||
"description": "The error details."
|
||||
|
||||
@@ -25,4 +25,4 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./schemas/schemas.yaml#components/schemas/ErrorDetail"
|
||||
$ref: "./schemas/schemas.yaml#/components/schemas/ErrorDetail"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Swagger Petstore",
|
||||
"description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "Swagger API Team",
|
||||
"email": "apiteam@swagger.io",
|
||||
"url": "http://swagger.io"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/pets": {
|
||||
"get": {
|
||||
"description": "Returns all pets alesuada ac...",
|
||||
"operationId": "findPets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An paged array of pets",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/_schemas_schemas.yaml-_components_schemas_ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"_schemas_schemas.yaml-_components_schemas_ErrorResponse": {
|
||||
"title": "Error response",
|
||||
"description": "Common error response for all Azure Resource Manager APIs to return error details for failed operations. (This also follows the OData error response format.).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"description": "The error detail.",
|
||||
"properties": {
|
||||
"code": {
|
||||
"readOnly": true,
|
||||
"type": "string",
|
||||
"description": "The error code."
|
||||
},
|
||||
"message": {
|
||||
"readOnly": true,
|
||||
"type": "string",
|
||||
"description": "The error message."
|
||||
},
|
||||
"target": {
|
||||
"readOnly": true,
|
||||
"type": "string",
|
||||
"description": "The error target."
|
||||
},
|
||||
"details": {
|
||||
"readOnly": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "/schemas/schemas.yaml#components/schemas/ErrorDetail",
|
||||
"x-circularRef": true
|
||||
},
|
||||
"description": "The error details."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
openapi: "3.0.2"
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
|
||||
termsOfService: http://swagger.io/terms/
|
||||
contact:
|
||||
name: Swagger API Team
|
||||
email: apiteam@swagger.io
|
||||
url: http://swagger.io
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
description: Returns all pets alesuada ac...
|
||||
operationId: findPets
|
||||
responses:
|
||||
'200':
|
||||
description: An paged array of pets
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./schemas/schemas.yaml#/components/schemas/ErrorResponse"
|
||||
@@ -0,0 +1,32 @@
|
||||
components:
|
||||
schemas:
|
||||
ErrorDetail:
|
||||
type: object
|
||||
description: The error detail.
|
||||
properties:
|
||||
code:
|
||||
readOnly: true
|
||||
type: string
|
||||
description: The error code.
|
||||
message:
|
||||
readOnly: true
|
||||
type: string
|
||||
description: The error message.
|
||||
target:
|
||||
readOnly: true
|
||||
type: string
|
||||
description: The error target.
|
||||
details:
|
||||
readOnly: true
|
||||
type: array
|
||||
items:
|
||||
$ref: "#components/schemas/ErrorDetail"
|
||||
description: The error details.
|
||||
ErrorResponse:
|
||||
title: "Error response"
|
||||
description: "Common error response for all Azure Resource Manager APIs to return error details for failed operations. (This also follows the OData error response format.)."
|
||||
type: "object"
|
||||
properties:
|
||||
error:
|
||||
description: "The error object."
|
||||
$ref: "#components/schemas/ErrorDetail"
|
||||
@@ -48,7 +48,8 @@ let expect = require('chai').expect,
|
||||
referencedPathSchema = path.join(__dirname, BUNDLES_FOLDER + '/paths_schema'),
|
||||
exampleValue = path.join(__dirname, BUNDLES_FOLDER + '/example_value'),
|
||||
example2 = path.join(__dirname, BUNDLES_FOLDER + '/example2'),
|
||||
schemaCircularRef = path.join(__dirname, BUNDLES_FOLDER + '/circular_reference');
|
||||
schemaCircularRef = path.join(__dirname, BUNDLES_FOLDER + '/circular_reference'),
|
||||
schemaCircularRefInline = path.join(__dirname, BUNDLES_FOLDER + '/circular_reference_inline');
|
||||
|
||||
describe('bundle files method - 3.0', function () {
|
||||
it('Should return bundled file as json - schema_from_response', async function () {
|
||||
@@ -2646,6 +2647,38 @@ describe('bundle files method - 3.0', function () {
|
||||
expect(res.output.specification.version).to.equal('3.0');
|
||||
expect(JSON.stringify(JSON.parse(res.output.data[0].bundledContent), null, 2)).to.be.equal(expected);
|
||||
});
|
||||
|
||||
it('Should resolve circular reference in schema correctly resolved inline', async function () {
|
||||
let contentRootFile = fs.readFileSync(schemaCircularRefInline + '/root.yaml', 'utf8'),
|
||||
schema = fs.readFileSync(schemaCircularRefInline + '/schemas/schemas.yaml', 'utf8'),
|
||||
expected = fs.readFileSync(schemaCircularRefInline + '/expected.json', 'utf8'),
|
||||
input = {
|
||||
type: 'multiFile',
|
||||
specificationVersion: '3.0',
|
||||
rootFiles: [
|
||||
{
|
||||
path: '/root.yaml'
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
path: '/root.yaml',
|
||||
content: contentRootFile
|
||||
},
|
||||
{
|
||||
path: '/schemas/schemas.yaml',
|
||||
content: schema
|
||||
}
|
||||
],
|
||||
options: {},
|
||||
bundleFormat: 'JSON'
|
||||
};
|
||||
const res = await Converter.bundle(input);
|
||||
expect(res).to.not.be.empty;
|
||||
expect(res.result).to.be.true;
|
||||
expect(res.output.specification.version).to.equal('3.0');
|
||||
expect(JSON.stringify(JSON.parse(res.output.data[0].bundledContent), null, 2)).to.be.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getReferences method when node does not have any reference', function() {
|
||||
|
||||
Reference in New Issue
Block a user