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'); const BROWSER = 'browser'; 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, detailedError = 'Invalid format. Input must be in YAML or JSON format.'; try { oasObject = this.asYaml(openApiSpec); } catch (yamlException) { // Not valid YAML, could be a JSON as well try { oasObject = this.asJson(openApiSpec); } 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 }; }, /** 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 * @return {String} rootFile */ getRootFiles: function (input, inputValidation, options, files = {}) { 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 { file = fs.readFileSync(filePath.fileName, 'utf8'); } obj = this.getOasObject(file); if (obj.result) { oasObject = obj.oasObject; } else { throw new Error(obj.reason); } if (inputValidation.validateSpec(oasObject, options).result) { 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); } }); }); } };