UNPKG

api-spec-converter

Version:

Convert API descriptions between popular formats such as OpenAPI(fka Swagger), RAML, API Blueprint, WADL, etc.

1,490 lines (1,252 loc) 8.12 MB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.APISpecConverter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ 'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _ = require('lodash'); var Promise = require('bluebird'); var SortObject = require('deep-sort-object'); var CompositeError = require('composite-error'); var Formats = require('./formats.js'); var Util = require('./util.js'); var Yaml = require('js-yaml'); var BaseFormat = module.exports = function (spec) { if (spec) this.spec = spec; this.format = "base_format"; this.converters = {}; }; // An array containing the swagger top level attributes we want to enforce order on. // The attributes are listed in reverse order (swagger will be output first). // The sort function will order according to the index of the attributes in this array. // Attributes not found will have index -1 and will be sorted alphabetically // after the elements in this array. var attr = ['externalDocs', 'tags', 'security', 'securityDefinitions', 'responses', 'parameters', 'definitions', 'components', 'paths', 'produces', 'consumes', 'schemes', 'basePath', 'host', 'servers', 'info', 'swagger', 'openapi']; BaseFormat.prototype.stringify = function (options) { var syntax; var order; if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === "object") { syntax = options.syntax; order = options.order; } // set options to default values if not specified syntax = syntax || 'json'; // other value 'yaml' order = order || 'openapi'; // other value 'alpha' for a-z alphabetical order if (order == 'false') { sortedSpecs = this.spec; } else { var sortedSpecs = SortObject(this.spec, function (a, b) { var aIdx = -1; var bIdx = -1; if (order != 'alpha') { // do index lookup only if it will be used; i.e. for OpenApi ordering aIdx = attr.indexOf(a); bIdx = attr.indexOf(b); } // if none of the args to compare are in the pre-sorted list // use the normal alphabetical sort if (aIdx == -1 && bIdx == -1) { //By default sort is done using local aware compare //Instead using order that will never change if (a === b) return 0; return a < b ? -1 : 1; } // sort according to index in attr pre-sorted list if (aIdx === bIdx) return 0; return aIdx < bIdx ? 1 : -1; }); } if (syntax == "yaml") { return Yaml.safeDump(sortedSpecs); } else { return JSON.stringify(sortedSpecs, null, 2); } }; BaseFormat.prototype.parsers = []; BaseFormat.prototype.checkFormat = undefined; BaseFormat.prototype.fixSpec = function () {}; BaseFormat.prototype.fillMissing = undefined; BaseFormat.prototype.validate = function (callback) { return Promise.resolve({ errors: null, warnings: null }).asCallback(callback); }; BaseFormat.prototype.listSubResources = function () { return []; }; BaseFormat.prototype.resolveSubResources = function () { var _this = this; var sources = this.listSubResources(); return Promise.map(_.values(sources), function (url) { return _this.readSpec(url); }, { concurrency: 5 }) //Limit number of parallel downloads to 5 .then(function (resources) { resources = _.map(resources, function (x) { return x[0]; }); var refs = _.keys(sources); _this.subResources = _.zipObject(refs, resources); }); }; BaseFormat.prototype.parse = function (data) { if (!_.isString(data)) return Promise.resolve(data); return Promise.any(_.map(this.parsers, function (parser) { return parser(data); })).catch(Promise.AggregateError, function (err) { throw new CompositeError('Failed to parse spec', _.toArray(err)); }); }; BaseFormat.prototype.readSpec = function (source) { var _this2 = this; var sourceType = Util.getSourceType(source); if (!sourceType) throw Error('Spec source should be object, string, URL or filename.'); return Util.resourceReaders[sourceType](source).then(function (data) { return _this2.parse(data); }).then(function (spec) { return [spec, sourceType]; }); }; BaseFormat.prototype.resolveResources = function (source) { var _this3 = this; return this.readSpec(source).spread(function (spec, sourceType) { if (!_this3.checkFormat(spec)) throw Error(sourceType + ' ' + source + ' is not valid ' + _this3.format); _this3.spec = spec; _this3.sourceType = sourceType; //Only URL and File name provide useful information if (sourceType === 'url' || sourceType === 'file') _this3.source = source; }).then(function () { return _this3.resolveSubResources(); }).then(function () { _this3.fixSpec(); var version = _this3.getFormatVersion(); if (_this3.supportedVersions.indexOf(version) === -1) throw Error('Unsupported version'); }); }; BaseFormat.prototype.convertTo = function (format, passthrough, callback) { var _this4 = this; return Promise.try(function () { if (format === _this4.format) return Promise.resolve(_this4); var convert = _this4.converters[format]; if (!convert) throw Error('Unable to convert from ' + _this4.format + ' to ' + format); return convert(_this4, passthrough).then(function (spec) { spec = new Formats[format](spec); spec.fixSpec(); return spec; }, function (err) { err.message = 'Error during conversion: ' + err.message; throw err; }); }).asCallback(callback); }; BaseFormat.prototype.convertTransitive = function (intermediaries, passthrough) { var prom = Promise.resolve(this); intermediaries.forEach(function (intermediary) { prom = prom.then(function (spec) { return spec.convertTo(intermediary, passthrough); }); }); return prom.then(function (spec) { return spec.spec; }); }; },{"./formats.js":3,"./util.js":12,"bluebird":89,"composite-error":152,"deep-sort-object":214,"js-yaml":425,"lodash":700}],2:[function(require,module,exports){ "use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var cloneDeep = require('lodash.clonedeep'); var HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'], SCHEMA_PROPERTIES = ['format', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'minLength', 'maxLength', 'multipleOf', 'minItems', 'maxItems', 'uniqueItems', 'minProperties', 'maxProperties', 'additionalProperties', 'pattern', 'enum', 'default'], ARRAY_PROPERTIES = ['type', 'items']; var APPLICATION_JSON_REGEX = /^(application\/json|[^;\/ \t]+\/[^;\/ \t]+[+]json)[ \t]*(;.*)?$/; var SUPPORTED_MIME_TYPES = { APPLICATION_X_WWW_URLENCODED: 'application/x-www-form-urlencoded', MULTIPART_FORM_DATA: 'multipart/form-data' }; var npath = require('path'); var fs = require('fs'); var urlParser = require('url'); var YAML = require('js-yaml'); /** * Transforms OpenApi 3.0 to Swagger 2 */ var Converter = module.exports = function (data) { this.spec = JSON.parse(JSON.stringify(data.spec)); if (data.source && !data.source.startsWith('http')) { this.directory = npath.dirname(data.source); } }; Converter.prototype.convert = function () { this.spec.swagger = '2.0'; this.convertInfos(); this.convertOperations(); if (this.spec.components) { this.convertSchemas(); this.convertSecurityDefinitions(); this.spec['x-components'] = this.spec.components; delete this.spec.components; fixRefs(this.spec); } return this.spec; }; function fixRef(ref) { return ref.replace('#/components/schemas/', '#/definitions/').replace('#/components/', '#/x-components/'); } function fixRefs(obj) { if (Array.isArray(obj)) { obj.forEach(fixRefs); } else if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object') { for (var key in obj) { if (key === '$ref') { obj.$ref = fixRef(obj.$ref); } else { fixRefs(obj[key]); } } } } Converter.prototype.resolveReference = function (base, obj, shouldClone) { if (!obj || !obj.$ref) return obj; var ref = obj.$ref; if (ref.startsWith('#')) { var keys = ref.split('/').map(function (k) { return k.replace(/~1/g, '/').replace(/~0/g, '~'); }); keys.shift(); var cur = base; keys.forEach(function (k) { cur = cur[k]; }); return shouldClone ? cloneDeep(cur) : cur; } else if (ref.startsWith('http') || !this.directory) { throw new Error("Remote $ref URLs are not currently supported for openapi_3"); } else { var res = ref.split('#/', 2); var content = fs.readFileSync(npath.join(this.directory, res[0]), 'utf8'); var external = null; try { external = JSON.parse(content); } catch (e) { try { external = YAML.safeLoad(content); } catch (e) { throw new Error("Could not parse path of $ref " + res[0] + " as JSON or YAML"); } } if (res.length > 1) { var keys = res[1].split('/').map(function (k) { return k.replace(/~1/g, '/').replace(/~0/g, '~'); }); keys.forEach(function (k) { external = external[k]; }); } return external; } }; /** * convert main infos and tags */ Converter.prototype.convertInfos = function () { var server = this.spec.servers && this.spec.servers[0]; if (server) { var serverUrl = server.url, variables = server['variables'] || {}; for (var variable in variables) { var variableObject = variables[variable] || {}; if (variableObject['default']) { var re = RegExp('{' + variable + '}', 'g'); serverUrl = serverUrl.replace(re, variableObject['default']); } } var url = urlParser.parse(serverUrl); if (url.host == null) { delete this.spec.host; } else { this.spec.host = url.host; } if (url.protocol == null) { delete this.spec.schemes; } else { this.spec.schemes = [url.protocol.substring(0, url.protocol.length - 1)]; // strip off trailing colon } this.spec.basePath = url.pathname; } delete this.spec.servers; delete this.spec.openapi; }; Converter.prototype.convertOperations = function () { var path, pathObject, method, operation; for (path in this.spec.paths) { pathObject = this.spec.paths[path] = this.resolveReference(this.spec, this.spec.paths[path], true); this.convertParameters(pathObject); // converts common parameters for (method in pathObject) { if (HTTP_METHODS.indexOf(method) >= 0) { operation = pathObject[method] = this.resolveReference(this.spec, pathObject[method], true); this.convertOperationParameters(operation); this.convertResponses(operation); } } } }; Converter.prototype.convertOperationParameters = function (operation) { var content, param, contentKey, mediaRanges, mediaTypes; operation.parameters = operation.parameters || []; if (operation.requestBody) { param = this.resolveReference(this.spec, operation.requestBody, true); // fixing external $ref in body if (operation.requestBody.content) { var type = getSupportedMimeTypes(operation.requestBody.content)[0]; var structuredObj = { 'content': {} }; var data = operation.requestBody.content[type]; if (data && data.schema && data.schema.$ref && !data.schema.$ref.startsWith('#')) { param = this.resolveReference(this.spec, data.schema, true); structuredObj['content']['' + type] = { 'schema': param }; param = structuredObj; } } param.name = 'body'; content = param.content; if (content && Object.keys(content).length) { mediaRanges = Object.keys(content).filter(function (mediaRange) { return mediaRange.indexOf('/') > 0; }); mediaTypes = mediaRanges.filter(function (range) { return range.indexOf('*') < 0; }); contentKey = getSupportedMimeTypes(content)[0]; delete param.content; if (contentKey === SUPPORTED_MIME_TYPES.APPLICATION_X_WWW_URLENCODED || contentKey === SUPPORTED_MIME_TYPES.MULTIPART_FORM_DATA) { operation.consumes = mediaTypes; param.in = 'formData'; param.schema = content[contentKey].schema; param.schema = this.resolveReference(this.spec, param.schema, true); if (param.schema.type === 'object' && param.schema.properties) { var required = param.schema.required || []; for (var name in param.schema.properties) { var schema = param.schema.properties[name]; // readOnly properties should not be sent in requests if (!schema.readOnly) { var formDataParam = { name: name, in: 'formData', schema: schema }; if (required.indexOf(name) >= 0) { formDataParam.required = true; } operation.parameters.push(formDataParam); } } } else { operation.parameters.push(param); } } else if (contentKey) { operation.consumes = mediaTypes; param.in = 'body'; param.schema = content[contentKey].schema; operation.parameters.push(param); } else if (mediaRanges) { operation.consumes = mediaTypes || ['application/octet-stream']; param.in = 'body'; param.name = param.name || 'file'; delete param.type; param.schema = content[mediaRanges[0]].schema || { type: 'string', format: 'binary' }; operation.parameters.push(param); } if (param.schema) { this.convertSchema(param.schema, 'request'); } } delete operation.requestBody; } this.convertParameters(operation); }; Converter.prototype.convertParameters = function (obj) { var _this = this; var param; if (obj.parameters === undefined) { return; } obj.parameters = obj.parameters || []; (obj.parameters || []).forEach(function (param, i) { param = obj.parameters[i] = _this.resolveReference(_this.spec, param, false); if (param.in !== 'body') { _this.copySchemaProperties(param, SCHEMA_PROPERTIES); _this.copySchemaProperties(param, ARRAY_PROPERTIES); _this.copySchemaXProperties(param); if (!param.description) { var schema = _this.resolveReference(_this.spec, param.schema, false); if (!!schema && schema.description) { param.description = schema.description; } } delete param.schema; delete param.allowReserved; if (param.example !== undefined) { param['x-example'] = param.example; } delete param.example; } if (param.type === 'array') { var style = param.style || (param.in === 'query' || param.in === 'cookie' ? 'form' : 'simple'); if (style === 'matrix') { param.collectionFormat = param.explode ? undefined : 'csv'; } else if (style === 'label') { param.collectionFormat = undefined; } else if (style === 'simple') { param.collectionFormat = 'csv'; } else if (style === 'spaceDelimited') { param.collectionFormat = 'ssv'; } else if (style === 'pipeDelimited') { param.collectionFormat = 'pipes'; } else if (style === 'deepOpbject') { param.collectionFormat = 'multi'; } else if (style === 'form') { param.collectionFormat = param.explode === false ? 'csv' : 'multi'; } } delete param.style; delete param.explode; }); }; Converter.prototype.copySchemaProperties = function (obj, props) { var schema = this.resolveReference(this.spec, obj.schema, true); if (!schema) return; props.forEach(function (prop) { var value = schema[prop]; switch (prop) { case 'additionalProperties': if (typeof value === 'boolean') return; } if (value !== undefined) { obj[prop] = value; } }); }; Converter.prototype.copySchemaXProperties = function (obj) { var schema = this.resolveReference(this.spec, obj.schema, true); if (!schema) return; for (var propName in schema) { if (hasOwnProperty.call(schema, propName) && !hasOwnProperty.call(obj, propName) && propName.startsWith('x-')) { obj[propName] = schema[propName]; } } }; Converter.prototype.convertResponses = function (operation) { var anySchema, code, content, jsonSchema, mediaRange, mediaType, response, resolved, headers; for (code in operation.responses) { response = operation.responses[code] = this.resolveReference(this.spec, operation.responses[code], true); if (response.content) { anySchema = jsonSchema = null; for (mediaRange in response.content) { // produces and examples only allow media types, not ranges // use application/octet-stream as a catch-all type mediaType = mediaRange.indexOf('*') < 0 ? mediaRange : 'application/octet-stream'; if (!operation.produces) { operation.produces = [mediaType]; } else if (operation.produces.indexOf(mediaType) < 0) { operation.produces.push(mediaType); } content = response.content[mediaRange]; anySchema = anySchema || content.schema; if (!jsonSchema && isJsonMimeType(mediaType)) { jsonSchema = content.schema; } if (content.example) { response.examples = response.examples || {}; response.examples[mediaType] = content.example; } } if (anySchema) { response.schema = jsonSchema || anySchema; resolved = this.resolveReference(this.spec, response.schema, true); if (resolved && response.schema.$ref && !response.schema.$ref.startsWith('#')) { response.schema = resolved; } this.convertSchema(response.schema, 'response'); } } headers = response.headers; if (headers) { for (var header in headers) { // Always resolve headers when converting to v2. resolved = this.resolveReference(this.spec, headers[header], true); // Headers should be converted like parameters. if (resolved.schema) { resolved.type = resolved.schema.type; resolved.format = resolved.schema.format; delete resolved.schema; } headers[header] = resolved; } } delete response.content; } }; Converter.prototype.convertSchema = function (def, operationDirection) { if (def.oneOf) { delete def.oneOf; if (def.discriminator) { delete def.discriminator; } } if (def.anyOf) { delete def.anyOf; if (def.discriminator) { delete def.discriminator; } } if (def.allOf) { for (var i in def.allOf) { this.convertSchema(def.allOf[i], operationDirection); } } if (def.discriminator) { if (def.discriminator.mapping) { this.convertDiscriminatorMapping(def.discriminator.mapping); } def.discriminator = def.discriminator.propertyName; } switch (def.type) { case 'object': if (def.properties) { for (var propName in def.properties) { if (def.properties[propName].writeOnly === true && operationDirection === 'response') { delete def.properties[propName]; } else { this.convertSchema(def.properties[propName], operationDirection); delete def.properties[propName].writeOnly; } } } case 'array': if (def.items) { this.convertSchema(def.items, operationDirection); } } if (def.nullable) { def['x-nullable'] = true; delete def.nullable; } // OpenAPI 3 has boolean "deprecated" on Schema, OpenAPI 2 does not // Convert to x-deprecated for Autorest (and perhaps others) if (def['deprecated'] !== undefined) { // Move to x-deprecated, unless it is already defined if (def['x-deprecated'] === undefined) { def['x-deprecated'] = def.deprecated; } delete def.deprecated; } }; Converter.prototype.convertSchemas = function () { this.spec.definitions = this.spec.components.schemas; for (var defName in this.spec.definitions) { this.convertSchema(this.spec.definitions[defName]); } delete this.spec.components.schemas; }; Converter.prototype.convertDiscriminatorMapping = function (mapping) { for (var payload in mapping) { var schemaNameOrRef = mapping[payload]; if (typeof schemaNameOrRef !== 'string') { console.warn('Ignoring ' + schemaNameOrRef + ' for ' + payload + ' in discriminator.mapping.'); continue; } // payload may be a schema name or JSON Reference string. // OAS3 spec limits schema names to ^[a-zA-Z0-9._-]+$ // Note: Valid schema name could be JSON file name without extension. // Prefer schema name, with file name as fallback. var schema = void 0; if (/^[a-zA-Z0-9._-]+$/.test(schemaNameOrRef)) { try { schema = this.resolveReference(this.spec, { $ref: '#/components/schemas/' + schemaNameOrRef }, false); } catch (err) { console.debug('Error resolving ' + schemaNameOrRef + ' for ' + payload + ' as schema name in discriminator.mapping: ' + err); } } // schemaNameRef is not a schema name. Try to resolve as JSON Ref. if (!schema) { try { schema = this.resolveReference(this.spec, { $ref: schemaNameOrRef }, false); } catch (err) { console.debug('Error resolving ' + schemaNameOrRef + ' for ' + payload + ' in discriminator.mapping: ' + err); } } if (schema) { // Swagger Codegen + OpenAPI Generator extension // https://github.com/swagger-api/swagger-codegen/pull/4252 schema['x-discriminator-value'] = payload; // AutoRest extension // https://github.com/Azure/autorest/pull/474 schema['x-ms-discriminator-value'] = payload; } else { console.warn('Unable to resolve ' + schemaNameOrRef + ' for ' + payload + ' in discriminator.mapping.'); } } }; Converter.prototype.convertSecurityDefinitions = function () { this.spec.securityDefinitions = this.spec.components.securitySchemes; for (var secKey in this.spec.securityDefinitions) { var security = this.spec.securityDefinitions[secKey]; if (security.type === 'http' && security.scheme === 'basic') { security.type = 'basic'; delete security.scheme; } else if (security.type === 'http' && security.scheme === 'bearer') { security.type = 'apiKey'; security.name = 'Authorization'; security.in = 'header'; delete security.scheme; delete security.bearerFormat; } else if (security.type === 'oauth2') { var flowName = Object.keys(security.flows)[0], flow = security.flows[flowName]; if (flowName === 'clientCredentials') { security.flow = 'application'; } else if (flowName === 'authorizationCode') { security.flow = 'accessCode'; } else { security.flow = flowName; } security.authorizationUrl = flow.authorizationUrl; security.tokenUrl = flow.tokenUrl; security.scopes = flow.scopes; delete security.flows; } } delete this.spec.components.securitySchemes; }; function isJsonMimeType(type) { return new RegExp(APPLICATION_JSON_REGEX, 'i').test(type); } function getSupportedMimeTypes(content) { var MIME_VALUES = Object.keys(SUPPORTED_MIME_TYPES).map(function (key) { return SUPPORTED_MIME_TYPES[key]; }); return Object.keys(content).filter(function (key) { return MIME_VALUES.indexOf(key) > -1 || isJsonMimeType(key); }); } },{"fs":140,"js-yaml":425,"lodash.clonedeep":515,"path":749,"url":959}],3:[function(require,module,exports){ 'use strict'; var Formats = module.exports = {}; function ignoreModuleNodeFound(e) { if (e.code !== 'MODULE_NOT_FOUND') { throw e; } } // some modules do require optionalDependencies, we silently catch loading if // that does not work, and we will warn user when he will try using it try { Formats.swagger_1 = require('./formats/swagger_1.js'); } catch (e) { ignoreModuleNodeFound(e); } try { Formats.swagger_2 = require('./formats/swagger_2.js'); } catch (e) { ignoreModuleNodeFound(e); } try { Formats.openapi_3 = require('./formats/openapi_3.js'); } catch (e) { ignoreModuleNodeFound(e); } try { Formats.api_blueprint = require('./formats/api_blueprint.js'); } catch (e) { ignoreModuleNodeFound(e); } Formats.io_docs = require('./formats/io_docs.js'); try { Formats.google = require('./formats/google.js'); } catch (e) { ignoreModuleNodeFound(e); } try { Formats.raml = require('./formats/raml.js'); } catch (e) { ignoreModuleNodeFound(e); } Formats.wadl = require('./formats/wadl.js'); },{"./formats/api_blueprint.js":4,"./formats/google.js":5,"./formats/io_docs.js":6,"./formats/openapi_3.js":7,"./formats/raml.js":8,"./formats/swagger_1.js":9,"./formats/swagger_2.js":10,"./formats/wadl.js":11}],4:[function(require,module,exports){ 'use strict'; var _ = require('lodash'); var Promise = require('bluebird'); var Drafter = require('drafter.js'); var Inherits = require('util').inherits; var BPToSwagger = require('apib2swagger'); var BaseFormat = require('../base_format.js'); var APIBlueprint = module.exports = function () { var _this = this; APIBlueprint.super_.apply(this, arguments); this.format = 'api_blueprint'; this.converters.swagger_2 = Promise.method(function (apibp) { return BPToSwagger.convertParsed(apibp.spec, {}); }); this.converters.openapi_3 = Promise.method(function (apibp) { return _this.convertTransitive(['swagger_2', 'openapi_3']); }); }; Inherits(APIBlueprint, BaseFormat); APIBlueprint.prototype.formatName = 'apiBlueprint'; APIBlueprint.prototype.supportedVersions = ['1A']; APIBlueprint.prototype.getFormatVersion = function () { return _(this.spec.metadata).filter({ name: 'FORMAT' }).get('[0].value', '1A'); }; APIBlueprint.prototype.parsers = { 'APIB': Promise.method(function (data) { return Drafter.parse(data, { type: 'ast' }).ast; }) }; APIBlueprint.prototype.checkFormat = function (spec) { //TODO: 'spec.ast' isn't working find other criteria. return true; }; },{"../base_format.js":1,"apib2swagger":55,"bluebird":89,"drafter.js":227,"lodash":700,"util":963}],5:[function(require,module,exports){ 'use strict'; var Inherits = require('util').inherits; var Promise = require('bluebird'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var Google2Swagger = require('google-discovery-to-swagger'); var Google = module.exports = function () { var _this = this; Google.super_.apply(this, arguments); this.format = 'google'; this.converters.swagger_2 = Promise.method(function (google) { return Google2Swagger.convert(google.spec); }); this.converters.openapi_3 = Promise.method(function (google) { return _this.convertTransitive(['swagger_2', 'openapi_3']); }); }; Inherits(Google, BaseFormat); Google.prototype.formatName = 'google'; Google.prototype.supportedVersions = ['v1']; Google.prototype.getFormatVersion = function () { return Google2Swagger.getVersion(this.spec); }; Google.prototype.parsers = { 'JSON': Util.parseJSON }; Google.prototype.checkFormat = function (spec) { return Google2Swagger.checkFormat(spec); }; },{"../base_format.js":1,"../util.js":12,"bluebird":89,"google-discovery-to-swagger":268,"util":963}],6:[function(require,module,exports){ 'use strict'; var Inherits = require('util').inherits; var Path = require('path'); var URL = require('url'); var _ = require('lodash'); var Promise = require('bluebird'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var IODocs = module.exports = function () { var _this = this; IODocs.super_.apply(this, arguments); this.type = 'io_docs'; this.converters.swagger_2 = Promise.method(function (iodocs) { return convertToSwagger(iodocs.spec); }); this.converters.openapi_3 = Promise.method(function (iodocs) { return _this.convertTransitive(['swagger_2', 'openapi_3']); }); }; Inherits(IODocs, BaseFormat); IODocs.prototype.formatName = 'ioDocs'; IODocs.prototype.supportedVersions = ['1.0']; IODocs.prototype.getFormatVersion = function () { //It's looks like format doesn't have versions, so treat it as '1.0' return '1.0'; }; IODocs.prototype.parsers = { 'JSON': Util.parseJSON }; IODocs.prototype.checkFormat = function (spec) { return spec.protocol === 'rest'; }; function convertToSwagger(iodocs) { var swagger = { swagger: '2.0' }; swagger.info = { description: iodocs.description, title: iodocs.name }; var baseURL = URL.parse(iodocs.basePath); swagger.schemes = [baseURL.protocol]; swagger.host = [baseURL.hostname]; swagger.basePath = Util.joinPath(baseURL.path || '', iodocs.publicPath || ''); swagger.definitions = iodocs.schemas; swagger.paths = {}; _.forIn(iodocs.resources, function (resource, name) { _.forIn(resource.methods, function (method, name) { swagger.paths[method.path] = swagger.paths[method.path] || {}; var route = swagger.paths[method.path][method.httpMethod.toLowerCase()] = {}; route.responses = { '200': { description: "success" } }; route.parameters = []; _.forIn(method.parameters, function (param, paramName) { var swaggerParam = { name: paramName, in: param.location || 'query', default: param.default, description: param.description, type: param.type, enum: param.enum }; if (swaggerParam.in === 'pathReplace') swaggerParam.in = 'path'; route.parameters.push(swaggerParam); }); }); }); return swagger; } },{"../base_format.js":1,"../util.js":12,"bluebird":89,"lodash":700,"path":749,"url":959,"util":963}],7:[function(require,module,exports){ 'use strict'; var Inherits = require('util').inherits; var Promise = require('bluebird'); var _ = require('lodash'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var validator = require('swagger2openapi/validate.js'); var openapi2swagger = require('../converters/openapi3_to_swagger2.js'); var OpenApi3 = module.exports = function () { OpenApi3.super_.apply(this, arguments); this.format = 'openapi_3'; this.converters.swagger_2 = Promise.method(function (oa) { var converter = new openapi2swagger(oa); return converter.convert(); }); }; Inherits(OpenApi3, BaseFormat); OpenApi3.prototype.formatName = 'openapi'; OpenApi3.prototype.supportedVersions = ['3.0']; OpenApi3.prototype.getFormatVersion = function () { var versionComponents = this.spec.openapi.split('.'); return versionComponents[0] + '.' + versionComponents[1]; }; OpenApi3.prototype.fillMissing = function (dummyData) { dummyData = dummyData || { info: { title: '< An API title here >', version: '< An API version here >' } }; this.spec = _.merge(dummyData, this.spec); }; OpenApi3.prototype.parsers = { 'JSON': Util.parseJSON, 'YAML': Util.parseYAML }; OpenApi3.prototype.checkFormat = function (spec) { return !_.isUndefined(spec.openapi); }; OpenApi3.prototype.validate = function (callback) { var openapi = this.spec; var promise = new Promise(function (resolve, reject) { var result = {}; try { result = validator.validateSync(openapi, result, function (err, options) {}); } catch (ex) { result.errors = { message: ex.message, context: result.context }; } resolve(result); }); return Promise.resolve(promise).asCallback(callback); }; },{"../base_format.js":1,"../converters/openapi3_to_swagger2.js":2,"../util.js":12,"bluebird":89,"lodash":700,"swagger2openapi/validate.js":928,"util":963}],8:[function(require,module,exports){ 'use strict'; var _ = require('lodash'); var RamlParser = require('raml-parser'); var Raml2Swagger = require('raml-to-swagger'); var Inherits = require('util').inherits; var Promise = require('bluebird'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var Raml = module.exports = function () { var _this = this; Raml.super_.apply(this, arguments); this.format = 'raml'; this.converters.swagger_2 = Promise.method(function (raml) { return Raml2Swagger.convert(raml.spec); }); this.converters.openapi_3 = Promise.method(function (raml) { return _this.convertTransitive(['swagger_2', 'openapi_3']); }); }; Inherits(Raml, BaseFormat); Raml.prototype.formatName = 'raml'; Raml.prototype.supportedVersions = ['0.8']; Raml.prototype.getFormatVersion = function () { return '0.8'; }; Raml.prototype.readSpec = function (source) { var sourceType = Util.getSourceType(source); return Promise.try(function () { if (sourceType === 'url' || sourceType === 'file') return RamlParser.loadFile(source);else return RamlParser.load(source); }).then(function (spec) { return [spec, sourceType]; }); }; Raml.prototype.checkFormat = function (spec) { return true; }; },{"../base_format.js":1,"../util.js":12,"bluebird":89,"lodash":700,"raml-parser":792,"raml-to-swagger":804,"util":963}],9:[function(require,module,exports){ 'use strict'; var _ = require('lodash'); var Path = require('path'); var Promise = require('bluebird'); var Inherits = require('util').inherits; var URI = require('urijs'); var SwaggerConverter = require('swagger-converter'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var Swagger1 = module.exports = function () { var _this = this; Swagger1.super_.apply(this, arguments); this.format = 'swagger_1'; this.converters.swagger_2 = Promise.method(function (swagger1, passthrough) { passthrough = passthrough || {}; passthrough.buildTagsFromPaths = passthrough.buildTagsFromPaths !== false; var swagger2 = SwaggerConverter.convert(swagger1.spec, swagger1.subResources, passthrough); if (swagger2.info.title === 'Title was not specified') swagger2.info.title = swagger2.host; return swagger2; }); this.converters.openapi_3 = Promise.method(function (swagger1, passthrough) { return _this.convertTransitive(['swagger_2', 'openapi_3'], passthrough); }); }; Inherits(Swagger1, BaseFormat); Swagger1.prototype.formatName = 'swagger'; Swagger1.prototype.supportedVersions = ['1.0', '1.1', '1.2']; Swagger1.prototype.getFormatVersion = function () { return this.spec.swaggerVersion; }; Swagger1.prototype.parsers = { 'JSON': Util.parseJSON }; Swagger1.prototype.checkFormat = function (spec) { return !_.isUndefined(spec.swaggerVersion); }; Swagger1.prototype.fixSpec = function () { if (this.sourceType == 'url') { var url = URI(this.source); if (!this.spec.basePath) this.spec.basePath = url.filename('').href();else { var basePath = URI(this.spec.basePath); basePath.scheme(basePath.scheme() || url.scheme()); basePath.host(basePath.host() || url.host()); this.spec.basePath = basePath.href(); } } }; Swagger1.prototype.listSubResources = function () { return SwaggerConverter.listApiDeclarations(this.source, this.spec); }; },{"../base_format.js":1,"../util.js":12,"bluebird":89,"lodash":700,"path":749,"swagger-converter":919,"urijs":956,"util":963}],10:[function(require,module,exports){ 'use strict'; var _ = require('lodash'); var Inherits = require('util').inherits; var URI = require('urijs'); var sway = require('sway'); var Promise = require('bluebird'); var swagger2openapi = require('swagger2openapi'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var Swagger2 = module.exports = function () { Swagger2.super_.apply(this, arguments); this.format = 'swagger_2'; this.converters.openapi_3 = Promise.method(function (swagger) { return swagger2openapi.convert(swagger.spec, { direct: true, patch: true }); }); }; Inherits(Swagger2, BaseFormat); Swagger2.prototype.formatName = 'swagger'; Swagger2.prototype.supportedVersions = ['2.0']; Swagger2.prototype.getFormatVersion = function () { return this.spec.swagger; }; Swagger2.prototype.fixSpec = function () { var swagger = this.spec; //Typical mistake is to make version number insted of string var version = _.get(swagger, 'info.version'); if (_.isNumber(version)) swagger.info.version = version % 1 ? version.toString() : version.toFixed(1); if (this.sourceType == 'url') { var url = URI(this.source); swagger.host = swagger.host || url.host(); swagger.schemes = swagger.schemes || [url.scheme()]; //TODO: decide what to do with base path } Util.removeNonValues(swagger); var basePath = swagger.basePath; if (_.isString(basePath)) swagger.basePath = URI().path(basePath).normalize().path(); _.each(swagger.definitions, function (schema) { if (!_.isUndefined(schema.id)) delete schema.id; }); }; Swagger2.prototype.fillMissing = function (dummyData) { dummyData = dummyData || { info: { title: '< An API title here >', version: '< An API version here >' } }; this.spec = _.merge(dummyData, this.spec); }; Swagger2.prototype.parsers = { 'JSON': Util.parseJSON, 'YAML': Util.parseYAML }; Swagger2.prototype.checkFormat = function (spec) { return !_.isUndefined(spec.swagger); }; Swagger2.prototype.validate = function (callback) { var promise = sway.create({ definition: this.spec, jsonRefs: this.jsonRefs }).then(function (swaggerObj) { var result = swaggerObj.validate(); result.remotesResolved = swaggerObj.definitionRemotesResolved; if (_.isEmpty(result.errors)) result.errors = null; if (_.isEmpty(result.warnings)) result.warnings = null; return result; }); return Promise.resolve(promise).asCallback(callback); }; },{"../base_format.js":1,"../util.js":12,"bluebird":89,"lodash":700,"swagger2openapi":923,"sway":929,"urijs":956,"util":963}],11:[function(require,module,exports){ 'use strict'; var _ = require('lodash'); var assert = require('assert'); var Inherits = require('util').inherits; var Path = require('path'); var URI = require('urijs'); var Promise = require('bluebird'); var Xml2Js = require('xml2js'); var BaseFormat = require('../base_format.js'); var Util = require('../util.js'); var WADL = module.exports = function () { var _this = this; WADL.super_.apply(this, arguments); this.format = 'wadl'; this.converters.swagger_2 = Promise.method(function (wadl) { return convertToSwagger(wadl.spec); }); this.converters.openapi_3 = Promise.method(function (swagger1) { return _this.convertTransitive(['swagger_2', 'openapi_3']); }); }; Inherits(WADL, BaseFormat); WADL.prototype.formatName = 'wadl'; WADL.prototype.supportedVersions = ['1.0']; WADL.prototype.getFormatVersion = function () { return '1.0'; }; WADL.prototype.parsers = { 'XML': function XML(data) { return Promise.promisify(Xml2Js.parseString)(data, { //HACK: we just strip namespace. Yes I know, it's ugly. //But handling XML namespaces is even uglier. tagNameProcessors: [Xml2Js.processors.stripPrefix], attrNameProcessors: [Xml2Js.processors.stripPrefix] }); } }; WADL.prototype.checkFormat = function (spec) { return true; }; function convertToSwagger(wadl) { var convertStyle = function convertStyle(style) { switch (style) { case 'query': case 'header': return style; case 'template': return 'path'; default: assert(false); } }; var convertType = function convertType(wadlType) { if (_.isUndefined(wadlType)) return {}; //HACK: we just strip namespace. Yes I know, it's ugly. //But handling XML namespaces is even uglier. var match = wadlType.match(/^(?:[^:]+:)?(.+)$/); assert(match, wadlType); var type = match[1]; switch (type.toLowerCase()) { case 'boolean': case 'string': case 'integer': return { type: type }; case 'double': case 'decimal': return { type: 'number' }; case 'int': return { type: 'integer', format: 'int32' }; case 'long': return { type: 'integer', format: 'int64' }; case 'positiveInteger': return { type: 'integer', minimum: 1 }; case 'anyURI': case 'date': case 'time': case 'date-time': //TODO: add 'format' where possible return { type: 'string' }; default: //HACK: convert unknown types into 'string' these works for everything, //except body and responces but we don't support them yet. return { type: 'string' }; //TODO: add warning //assert(false, 'Unsupported type: ' + wadlType); } }; var convertDoc = function convertDoc(doc) { if (_.isUndefined(doc)) return {}; assert(_.isArray(doc)); var result = {}; _.each(doc, function (docElement) { if (_.isPlainObject(docElement)) { //Handle Apigee extension var externalUrl = docElement.$['url']; if (externalUrl) result.externalDocs = { url: externalUrl }; docElement = docElement._; if (!_.isString(docElement)) return; } assert(_.isString(docElement)); docElement = docElement.trim(); if (result.description) result.description += '\n' + docElement;else result.description = docElement; }); return result; }; var convertDefault = function convertDefault(wadlDefault, type) { if (type === 'string') return wadlDefault; return JSON.parse(wadlDefault); }; var convertParameter = function convertParameter(wadlParam) { var loc = convertStyle(wadlParam.$.style); var ret = { name: wadlParam.$.name, required: loc === 'path' ? true : JSON.parse(wadlParam.$.required || 'false'), in: loc, type: 'string' //default type }; _.assign(ret, convertType(wadlParam.$.type)); var wadlDefault = wadlParam.$.default; if (!_.isUndefined(wadlDefault)) ret.default = convertDefault(wadlDefault, ret.type); var doc = convertDoc(wadlParam.doc); //FIXME: delete doc.externalDocs; _.extend(ret, doc); if (wadlParam.option) { ret.enum = wadlParam.option.map(function (opt) { return opt.$.value; }); } return ret; }; function unwrapArray(array) { if (_.isUndefined(array)) return; assert(_.isArray(array)); assert(_.size(array) === 1); return array[0]; } function convertMethod(wadlMethod) { var method = { operationId: wadlMethod.$.id, responses: { //FIXME: take responces from WADL file 200: { description: 'Successful Response' } } }; var wadlRequest = unwrapArray(wadlMethod.request); if (wadlRequest) method.parameters = _.uniqBy(_.map(wadlRequest.param, convertParameter), function (e) { return e.name + e.in; }); _.extend(method, convertDoc(wadlMethod.doc)); return method; } // Jersey use very strange extension to WADL. See: // https://docs.oracle.com/cd/E19776-01/820-4867/6nga7f5nc/index.html // They add {<name>: <regex>} template parameters which should be converted // to {<name>}. Tricky part is find end of regexp. function convertPath(path) { function indexOf(ch, startPosition) { var pos = path.indexOf(ch, startPosition); if (pos === -1) return pos; var slashNumber = 0; while (path.charAt(pos - 1 - slashNumber) === '\\') { ++slashNumber; }if (slashNumber % 2 === 0) return pos; //Skip escaped symbols return indexOf(ch, pos + 1); } var match; //RegEx should be inside loop to reset iteration while (match = /{([^}:]+):/g.exec(path)) { var deleteBegin = match.index + match[0].length - 1; var deleteEnd = deleteBegin; var unmatched = 1; while (unmatched !== 0) { var open = indexOf('{', deleteEnd + 1); var close = indexOf('}', deleteEnd + 1); if (close === -1) throw Error('Unmatched curly brackets in path: ' + path); if (open !== -1 && open < close) { ++unmatched; deleteEnd = open; } else { --unmatched; deleteEnd = close; } } //For future use: regex itself is //path.substring(deleteBegin + 1, deleteEnd) path = path.slice(0, deleteBegin) + path.slice(deleteEnd); } return path; } function convertResource(wadlResource) { var resourcePath = Util.joinPath('/', convertPath(wadlResource.$.path)); var paths = {}; //Not supported assert(!_.has(wadlResource, 'resource_type')); assert(!_.has(wadlResource, 'resource_type')); var resource = {}; var commonParameters = _.uniqBy(_.map(wadlResource.param, convertParameter), function (e) { return e.name + e.in; }); _.each(wadlResource.method, function (wadlMethod) { var httpMethod = wadlMethod.$.name.toLowerCase(); resource[httpMethod] = convertMethod(wadlMethod); }); if (!_.isEmpty(resource)) { resource.parameters = commonParameters; paths[resourcePath] = resource; } _.each(wadlResource.resource, function (wadlSubResource) { var subPaths = convertResource(wadlSubResource); subPaths = _.mapKeys(subPaths, function (subPath, path) { subPath.parameters = _.uniqBy(commonParameters.concat(subPath.parameters), function (e) { return e.name + e.id; }); return Util.joinPath(resourcePath, convertPath(path)); }); mergePaths(paths, subPaths); }); return paths; } function mergePaths(paths, pathsToAdd) { _.each(pathsToAdd, function (resource, path) { var existingResource = paths[path]; if (!_.isUndefined(existingResource)) { assert(_.isEqual(existingResource.parameters, resource.parameters)); _.extend(existingResource, resource); } else paths[path] = resource; }); } var root = unwrapArray(wadl.application.resources); var title = wadl.application.doc && wadl.application.doc[1] && wadl.application.doc[1].$ ? wadl.application.doc[1].$.title : ""; var baseUrl = URI(root.$.base); var swagger = { swagger: '2.0', info: { title: title, version: "" }, host: baseUrl.host() || undefined, basePath: baseUrl.pathname() || undefined, schemes: baseUrl.protocol() ? [baseUrl.protocol()] : undefined, p