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
JavaScript
(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