api-spec-converter
Version:
Convert API descriptions between popular formats such as OpenAPI(fka Swagger), RAML, API Blueprint, WADL, etc.
1,516 lines (1,270 loc) • 8.09 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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}return e})()({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
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, 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).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) {
var prom = Promise.resolve(this);
intermediaries.forEach(function (intermediary) {
prom = prom.then(function (spec) {
return spec.convertTo(intermediary);
});
});
return prom.then(function (spec) {
return spec.spec;
});
};
},{"./formats.js":3,"./util.js":12,"bluebird":86,"composite-error":133,"deep-sort-object":195,"js-yaml":455,"lodash":570}],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 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',
MUTIPLART_FORM_DATA: 'multipart/form-data',
APPLICATION_OCTET_STREAM: 'application/octet-stream'
};
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.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.convertSecurityDefinitions();
this.spec.definitions = this.spec.components.schemas;
delete this.spec.components.schemas;
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) {
if (!obj || !obj.$ref) return obj;
var ref = obj.$ref;
if (ref.startsWith('#')) {
var keys = ref.split('/');
keys.shift();
var cur = base;
keys.forEach(function (k) {
cur = cur[k];
});
return cur;
} else if (ref.startsWith('http') || !this.directory) {
throw new Error("Remote $ref URLs are not currently supported for openapi_3");
} else {
var content = fs.readFileSync(npath.join(this.directory, ref), 'utf8');
var external = null;
try {
external = JSON.parse(content);
} catch (e) {
try {
external = YAML.safeLoad(content);
} catch (e) {
throw new Error("Could not parse $ref " + ref + " as JSON or YAML");
}
}
return external;
}
};
/**
* convert main infos and tags
*/
Converter.prototype.convertInfos = function () {
var server = this.spec.servers && this.spec.servers[0];
if (server) {
var match = server.url.match(/(\w+):\/\/([^\/]+)(\/.*)?/);
if (match) {
this.spec.schemes = [match[1]];
this.spec.host = match[2];
this.spec.basePath = match[3] || '/';
}
}
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]);
for (method in pathObject) {
if (HTTP_METHODS.indexOf(method) >= 0) {
operation = pathObject[method] = this.resolveReference(this.spec, pathObject[method]);
this.convertParameters(operation);
this.convertResponses(operation);
}
}
}
};
Converter.prototype.convertParameters = function (operation) {
var _this = this;
var content, param, contentKey;
operation.parameters = operation.parameters || [];
if (operation.requestBody) {
param = this.resolveReference(this.spec, operation.requestBody);
param.name = 'body';
content = param.content;
if (content) {
contentKey = getSupportedMimeTypes(content)[0];
delete param.content;
if (contentKey === SUPPORTED_MIME_TYPES.APPLICATION_X_WWW_URLENCODED) {
operation.consumes = [contentKey];
param.in = 'formData';
param.schema = content[contentKey].schema;
param.schema = this.resolveReference(this.spec, param.schema);
if (param.schema.type === 'object' && param.schema.properties) {
for (var name in param.schema.properties) {
var p = param.schema.properties[name];
p.name = name;
p.in = 'formData';
operation.parameters.push(p);
}
} else {
operation.parameters.push(param);
}
} else if (contentKey === SUPPORTED_MIME_TYPES.MUTIPLART_FORM_DATA) {
operation.consumes = [contentKey];
param.in = 'formData';
param.schema = content[contentKey].schema;
operation.parameters.push(param);
} else if (contentKey === SUPPORTED_MIME_TYPES.APPLICATION_OCTET_STREAM) {
operation.consumes = [contentKey];
param.in = 'formData';
param.type = 'file';
param.name = param.name || 'file';
delete param.schema;
operation.parameters.push(param);
} else if (isJsonMimeType(contentKey)) {
operation.consumes = [contentKey];
param.in = 'body';
param.schema = content[contentKey].schema;
operation.parameters.push(param);
} else {
console.warn('unsupported request body media type', operation.operationId, content);
}
}
delete operation.requestBody;
}
(operation.parameters || []).forEach(function (param, i) {
param = operation.parameters[i] = _this.resolveReference(_this.spec, param);
_this.copySchemaProperties(param, SCHEMA_PROPERTIES);
if (param.in !== 'body') {
_this.copySchemaProperties(param, ARRAY_PROPERTIES);
delete param.schema;
delete param.allowReserved;
if (param.example) {
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);
if (!schema) return;
props.forEach(function (prop) {
if (schema[prop] !== undefined) {
obj[prop] = schema[prop];
}
});
};
Converter.prototype.convertResponses = function (operation) {
var code, content, contentType, response, resolved, headers;
for (code in operation.responses) {
content = false;
contentType = 'application/json';
response = operation.responses[code] = this.resolveReference(this.spec, operation.responses[code]);
if (response.content) {
if (response.content[contentType]) {
content = response.content[contentType];
}
if (!content) {
contentType = Object.keys(response.content)[0];
content = response.content[contentType];
}
}
if (content) {
operation.produces = operation.produces || [];
if (!operation.produces.includes(contentType)) {
operation.produces.push(contentType);
}
response.schema = content.schema;
resolved = this.resolveReference(this.spec, response.schema);
if (resolved && response.schema.$ref && !response.schema.$ref.startsWith('#')) {
response.schema = resolved;
}
if (content.example) {
response.examples = {};
response.examples[contentType] = content.example;
}
this.copySchemaProperties(response, SCHEMA_PROPERTIES);
}
headers = response.headers;
if (headers) {
for (var header in headers) {
// Always resolve headers when converting to v2.
resolved = this.resolveReference(this.spec, headers[header]);
// 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.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":119,"js-yaml":455,"path":604,"url":964}],3:[function(require,module,exports){
'use strict';
var Formats = module.exports = {};
Formats.swagger_1 = require('./formats/swagger_1.js');
Formats.swagger_2 = require('./formats/swagger_2.js');
Formats.openapi_3 = require('./formats/openapi_3.js');
Formats.api_blueprint = require('./formats/api_blueprint.js');
Formats.io_docs = require('./formats/io_docs.js');
Formats.google = require('./formats/google.js');
Formats.raml = require('./formats/raml.js');
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":86,"drafter.js":213,"lodash":570,"util":968}],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":86,"google-discovery-to-swagger":380,"util":968}],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":86,"lodash":570,"path":604,"url":964,"util":968}],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":86,"lodash":570,"swagger2openapi/validate.js":848,"util":968}],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":86,"lodash":570,"raml-parser":638,"raml-to-swagger":650,"util":968}],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) {
var swagger2 = SwaggerConverter.convert(swagger1.spec, swagger1.subResources, { buildTagsFromPaths: true });
if (swagger2.info.title === 'Title was not specified') swagger2.info.title = swagger2.host;
return swagger2;
});
this.converters.openapi_3 = Promise.method(function (swagger1) {
return _this.convertTransitive(['swagger_2', 'openapi_3']);
});
};
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":86,"lodash":570,"path":604,"swagger-converter":839,"urijs":961,"util":968}],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":86,"lodash":570,"swagger2openapi":843,"sway":849,"urijs":961,"util":968}],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 = _.map(wadlRequest.param, convertParameter);
_.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 = _.map(wadlResource.param, convertParameter);
_.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 = commonParameters.concat(subPath.parameters);
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 baseUrl = URI(root.$.base);
var swagger = {
swagger: '2.0',
host: baseUrl.host() || undefined,
basePath: baseUrl.pathname() || undefined,
schemes: baseUrl.protocol() ? [baseUrl.protocol()] : undefined,
paths: {}
};
_.each(root.resource, function (wadlResource) {
mergePaths(swagger.paths, convertResource(wadlResource));
});
return swagger;
}
},{"../base_format.js":1,"../util.js":12,"assert":79,"bluebird":86,"lodash":570,"path":604,"urijs":961,"util":968,"xml2js":976}],12:[function(require,module,exports){
'use strict';
var FS = require('fs');
var Path = require('path');
var _ = require('lodash');
var URI = require('urijs');
var YAML = require('js-yaml');
var Promise = require('bluebird');
var traverse = require('traverse');
var Request = Promise.promisify(require('request'), { multiArgs: true });
module.exports.joinPath = function () {
var argArray = Array.prototype.slice.call(arguments);
return argArray.join('/').replace(/\/\/+/g, '/');
};
module.exports.parseJSON = Promise.method(JSON.parse);
module.exports.parseYAML = Promise.method(YAML.safeLoad);
function readUrl(url, callback) {
var options = {
uri: url,
headers: {
//TODO: allow different fomrats to define different MIME types.
'Accept': 'application/json,*/*'
}
};
return Request(options).spread(function (response, data) {
if (response.statusCode !== 200) throw Error('Can not GET ' + url + ': ' + response.statusMessage);
return data;
});
}
function readFile(filename) {
return Promise.fromCallback(function (callback) {
FS.readFile(filename, 'utf8', callback);
});
}
function readDummy(data) {
return Promise.resolve(data);
}
module.exports.resourceReaders = {
url: readUrl,
file: readFile,
object: readDummy,
string: readDummy
};
module.exports.getSourceType = function (source) {
if (_.isObject(source)) return 'object';
if (!_.isString(source)) return undefined;
// windows resolved paths look like absolute URLs,
// so check for file existence.
try {
// FIXME: existsSync fails in browser
if (FS.existsSync(source)) return 'file';
} catch (e) {}
var uri = new URI(source);
if (uri.is('absolute')) return 'url';else if (uri.is('relative')) return 'file';else return 'string';
};
module.exports.removeNonValues = function (obj) {
traverse(obj).forEach(function (value) {
if (value === undefined || value === null) this.remove();
});
};
},{"bluebird":86,"fs":119,"js-yaml":455,"lodash":570,"path":604,"request":667,"traverse":952,"urijs":961}],13:[function(require,module,exports){
'use strict';
var KEYWORDS = [
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'additionalProperties',
'enum',
'format',
'const'
];
module.exports = function (metaSchema, keywordsJsonPointers) {
for (var i=0; i<keywordsJsonPointers.length; i++) {
metaSchema = JSON.parse(JSON.stringify(metaSchema));
var segments = keywordsJsonPointers[i].split('/');
var keywords = metaSchema;
var j;
for (j=1; j<segments.length; j++)
keywords = keywords[segments[j]];
for (j=0; j<KEYWORDS.length; j++) {
var key = KEYWORDS[j];
var schema = keywords[key];
if (schema) {
keywords[key] = {
anyOf: [
schema,
{ $ref: 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#' }
]
};
}
}
}
return metaSchema;
};
},{}],14:[function(require,module,exports){
'use strict';
var compileSchema = require('./compile')
, resolve = require('./compile/resolve')
, Cache = require('./cache')
, SchemaObject = require('./compile/schema_obj')
, stableStringify = require('fast-json-stable-stringify')
, formats = require('./compile/formats')
, rules = require('./compile/rules')
, $dataMetaSchema = require('./$data')
, patternGroups = require('./patternGroups')
, util = require('./compile/util')
, co = require('co');
module.exports = Ajv;
Ajv.prototype.validate = validate;
Ajv.prototype.compile = compile;
Ajv.prototype.addSchema = addSchema;
Ajv.prototype.addMetaSchema = addMetaSchema;
Ajv.prototype.validateSchema = validateSchema;
Ajv.prototype.getSchema = getSchema;
Ajv.prototype.removeSchema = removeSchema;
Ajv.prototype.addFormat = addFormat;
Ajv.prototype.errorsText = errorsText;
Ajv.prototype._addSchema = _addSchema;
Ajv.prototype._compile = _compile;
Ajv.prototype.compileAsync = require('./compile/async');
var customKeyword = require('./keyword');
Ajv.prototype.addKeyword = customKeyword.add;
Ajv.prototype.getKeyword = customKeyword.get;
Ajv.prototype.removeKeyword = customKeyword.remove;
var errorClasses = require('./compile/error_classes');
Ajv.ValidationError = errorClasses.Validation;
Ajv.MissingRefError = errorClasses.MissingRef;
Ajv.$dataMetaSchema = $dataMetaSchema;
var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema';
var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ];
var META_SUPPORT_DATA = ['/properties'];
/**
* Creates validator instance.
* Usage: `Ajv(opts)`
* @param {Object} opts optional options
* @return {Object} ajv instance
*/
function Ajv(opts) {
if (!(this instanceof Ajv)) return new Ajv(opts);
opts = this._opts = util.copy(opts) || {};
setLogger(this);
this._schemas = {};
this._refs = {};
this._fragments = {};
this._formats = formats(opts.format);
var schemaUriFormat = this._schemaUriFormat = this._formats['uri-reference'];
this._schemaUriFormatFunc = function (str) { return schemaUriFormat.test(str); };
this._cache = opts.cache || new Cache;
this._loadingSchemas = {};
this._compilations = [];
this.RULES = rules();
this._getId = chooseGetId(opts);
opts.loopRequired = opts.loopRequired || Infinity;
if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true;
if (opts.serialize === undefined) opts.serialize = stableStringify;
this._metaOpts = getMetaSchemaOptions(this);
if (opts.formats) addInitialFormats(this);
addDraft6MetaSchema(this);
if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta);
addInitialSchemas(this);
if (opts.patternGroups) patternGroups(this);
}
/**
* Validate data using schema
* Schema will be compiled and cached (using serialized JSON as key. [fast-json-stable-stringify](https://github.com/epoberezkin/fast-json-stable-stringify) is used to serialize.
* @this Ajv
* @param {String|Object} schemaKeyRef key, ref or schema object
* @param {Any} data to be validated
* @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`).
*/
function validate(schemaKeyRef, data) {
var v;
if (typeof schemaKeyRef == 'string') {
v = this.getSchema(schemaKeyRef);
if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"');
} else {
var schemaObj = this._addSchema(schemaKeyRef);
v = schemaObj.validate || this._compile(schemaObj);
}
var valid = v(data);
if (v.$async === true)
return this._opts.async == '*' ? co(valid) : valid;
this.errors = v.errors;
return valid;
}
/**
* Create validating function for passed schema.
* @this Ajv
* @param {Object} schema schema object
* @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords.
* @return {Function} validating function
*/
function compile(schema, _meta) {
var schemaObj = this._addSchema(schema, undefined, _meta);
return schemaObj.validate || this._compile(schemaObj);
}
/**
* Adds schema to the instance.
* @this Ajv
* @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored.
* @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
* @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead.
* @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
* @return {Ajv} this for method chaining
*/
function addSchema(schema, key, _skipValidation, _meta) {
if (Array.isArray(schema)){
for (var i=0; i<schema.length; i++) this.addSchema(schema[i], undefined, _skipValidation, _meta);
return this;
}
var id = this._getId(schema);
if (id !== undefined && typeof id != 'string')
throw new Error('schema id must be string');
key = resolve.normalizeId(key || id);
checkUnique(this, key);
this._schemas[key] = this._addSchema(schema, _skipValidation, _meta, true);
return this;
}
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @this Ajv
* @param {Object} schema schema object
* @param {String} key optional schema key
* @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
* @return {Ajv} this for method chaining
*/
function addMetaSchema(schema, key, skipValidation) {
this.addSchema(schema, key, skipValidation, true);
return this;
}
/**
* V