UNPKG

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