Files
fastapi-openapi-to-postman/lib/parse.js
Luis Tejeda 83e6819c66 Fix circular references in bundle
Fix circular references in bundle
2022-07-08 17:26:27 -05:00

284 lines
8.2 KiB
JavaScript

var yaml = require('js-yaml'),
fs = require('fs'),
_ = require('lodash'),
// use path based on platform it's running on (web or node)
path = require('path'),
pathBrowserify = require('path-browserify'),
resolver = require('oas-resolver-browser'),
yamlParse = require('yaml'),
{ compareVersion, getVersionFromSpec } = require('./common/versionUtils.js');
const BROWSER = 'browser',
YAML_FORMAT = 'yaml',
JSON_FORMAT = 'json',
UNSUPPORTED_FORMAT = 'unsupported';
/** Converts OpenAPI input to OpenAPI Object
* @param {String} spec OpenAPI input in string
* @returns {boolean} wheter the input looks like a json
*/
function looksLikeJson(spec) {
return typeof spec === 'string' && spec.startsWith('{');
}
module.exports = {
asJson: function (spec) {
try {
return JSON.parse(spec);
}
catch (jsonException) {
throw new SyntaxError(`Specification is not a valid JSON. ${jsonException}`);
}
},
asYaml: function (spec) {
try {
let obj = yaml.safeLoad(spec);
// yaml.safeLoad does not throw errors for most of the cases in invalid yaml
// hence check if it returned an object
if (typeof obj !== 'object') {
throw new Error('');
}
return obj;
}
catch (yamlException) {
throw new SyntaxError(`Specification is not a valid YAML. ${yamlException}`);
}
},
/** Converts OpenAPI input to OpenAPI Object
* @param {String} openApiSpec OpenAPI input in string
* @returns {Object} oasObject
*/
getOasObject: function (openApiSpec) {
let oasObject = openApiSpec,
inputFormat = UNSUPPORTED_FORMAT,
detailedError = 'Invalid format. Input must be in YAML or JSON format.';
try {
oasObject = this.asYaml(openApiSpec);
inputFormat = looksLikeJson(openApiSpec) ? JSON_FORMAT : YAML_FORMAT;
}
catch (yamlException) {
// Not valid YAML, could be a JSON as well
try {
oasObject = this.asJson(openApiSpec);
inputFormat = JSON_FORMAT;
}
catch (jsonException) {
// It's neither JSON nor YAML
// try and determine json-ness or yaml-ness
if (openApiSpec && openApiSpec[0] === '{') {
// probably JSON
detailedError += ' ' + jsonException.message;
}
else if (openApiSpec && openApiSpec.indexOf('openapi:') === 0) {
// probably YAML
detailedError += ' ' + yamlException.message;
}
return {
result: false,
reason: detailedError
};
}
}
return {
result: true,
oasObject,
inputFormat
};
},
/** Given an array of files returns the root OAS file if present
*
* @param {Array} input input object that contains files array
* @param {Object} inputValidation Validator according to version
* @param {Object} options computed process options
* @param {Object} files Files map
* @param {string} specificationVersion the string of the desired version
* @param {boolean} allowReadingFS wheter to allow reading content from file system
* @return {String} rootFile
*/
getRootFiles: function (input, inputValidation, options, files = {}, specificationVersion,
allowReadingFS = true) {
let rootFilesArray = [],
filesPathArray = input.data,
origin = input.origin || '';
filesPathArray.forEach((filePath) => {
let obj,
file,
oasObject;
try {
if (origin === BROWSER) {
path = pathBrowserify;
}
// Use files map if present to read files.
if (!_.isEmpty(files)) {
file = files[path.resolve(filePath.fileName)];
}
else if (allowReadingFS) {
file = fs.readFileSync(filePath.fileName, 'utf8');
}
obj = this.getOasObject(file);
if (obj.result) {
oasObject = obj.oasObject;
}
else {
// Ignore this file.
return;
}
if (inputValidation.validateSpec(oasObject, options).result) {
if (specificationVersion) {
if (compareVersion(specificationVersion, getVersionFromSpec(oasObject))) {
rootFilesArray.push(filePath.fileName);
}
}
else {
rootFilesArray.push(filePath.fileName);
}
}
}
catch (e) {
throw new Error(e.message);
}
});
return rootFilesArray;
},
/**
* Resolve file references and generate single OAS Object
*
* @param {Object} openapi OpenAPI
* @param {Object} options options
* @param {Object} files Map of files path and content
* @return {Object} Resolved content
*/
resolveContent: function (openapi, options, files) {
return resolver.resolve(openapi, options.source, {
options: Object.assign({}, options),
resolve: true,
cache: [],
externals: [],
externalRefs: {},
rewriteRefs: true,
openapi: openapi,
files: files,
browser: options.browser || '',
resolveInternal: true
});
},
/** Resolves all OpenAPI file references and returns a single OAS Object
*
* @param {Object} source Root file path
* @param {Object} options Configurable options as per oas-resolver module.
* @param {Object} files Map of files path and content
* @return {Object} Resolved OpenAPI Schema
*/
mergeFiles: function(source, options = {}, files = {}) {
options.source = source;
options.origin = source;
// Use files map if present instead of reading files.
if (!_.isEmpty(files)) {
// Use pathBrowserify if the origin of input is browser.
if (options.browser) {
path = pathBrowserify;
}
let content = files[path.resolve(source)],
unresolved;
try {
unresolved = yamlParse.parse(content, { prettyErrors: true });
}
// Used to indicate to users where is the issue with their API
catch (err) {
throw new Error('\nLine: ' + err.linePos.start.line + ', col: ' +
err.linePos.start.col + ' ' + err.message);
}
return new Promise((resolve, reject) => {
return this.resolveContent(unresolved, options, files)
.then((result) => {
return resolve(result.openapi);
}, (err) => {
return reject(err);
});
});
}
return this.readFileAsync(source, 'utf8')
.then((content) => {
try {
return yamlParse.parse(content, { prettyErrors: true });
}
catch (err) {
throw new Error('\nLine: ' + err.linePos.start.line + ', col: ' +
err.linePos.start.col + ' ' + err.message);
}
}, (err) => {
throw new Error(err.message);
})
.then((unresolved) => {
if (options.resolve === true) {
return this.resolveContent(unresolved, options);
}
}, (err) => {
throw err;
})
.then((result) => {
return result.openapi;
}, (err) => {
throw err;
});
},
/** Read File asynchronously
*
* @param {String} filePath Path of the file.
* @param {String} encoding encoding
* @return {String} Contents of the file
*/
readFileAsync: function(filePath, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, encoding, (err, data) => {
if (err) { reject(err); }
else { resolve(data); }
});
});
},
/**
* Serializes the given API as JSON, using the given spaces for formatting.
*
* @param {object} api specification to parse
* @param {string|number} spaces number of spaces to use for tabulation
*
* @return {String} Contents of the file in json format
*/
toJSON: function (api, spaces) {
return JSON.stringify(api, null, spaces);
},
/**
* Serializes the given API as YAML, using the given spaces for formatting.
*
* @param {object} api API to convert to YAML
* @param {string|number} spaces number of spaces to ident
* @param {number} lineWidth set max line width. (default: 80)
*
* @return {String} Contents of the file in yaml format
*/
toYAML: function (api, spaces, lineWidth) {
return yaml.dump(api, {
indent: spaces,
lineWidth: lineWidth,
noRefs: true
});
},
YAML_FORMAT,
JSON_FORMAT
};