diff --git a/lib/codegen.js b/lib/codegen.js index e0f5d5eb..b9424fcf 100644 --- a/lib/codegen.js +++ b/lib/codegen.js @@ -13,260 +13,260 @@ var splitter = require('./splitter'); var ts = require('./typescript'); var normalizeName = function(id) { - return id.replace(/\.|\-|\{|\}|\s/g, '_'); + return id.replace(/\.|\-|\{|\}|\s/g, '_'); }; var getPathToMethodName = function(opts, m, path){ - if(path === '/' || path === '') { - return m; - } + if(path === '/' || path === '') { + return m; + } - // clean url path for requests ending with '/' - var cleanPath = path.replace(/\/$/, ''); + // clean url path for requests ending with '/' + var cleanPath = path.replace(/\/$/, ''); - var segments = cleanPath.split('/').slice(1); - segments = _.transform(segments, function (result, segment) { - if (segment[0] === '{' && segment[segment.length - 1] === '}') { - segment = 'by' + segment[1].toUpperCase() + segment.substring(2, segment.length - 1); - } - result.push(segment); - }); - var result = _.camelCase(segments.join('-')); - return m.toLowerCase() + result[0].toUpperCase() + result.substring(1); + var segments = cleanPath.split('/').slice(1); + segments = _.transform(segments, function (result, segment) { + if (segment[0] === '{' && segment[segment.length - 1] === '}') { + segment = 'by' + segment[1].toUpperCase() + segment.substring(2, segment.length - 1); + } + result.push(segment); + }); + var result = _.camelCase(segments.join('-')); + return m.toLowerCase() + result[0].toUpperCase() + result.substring(1); }; var getViewForSwagger2 = function(opts, type){ - var swagger = opts.swagger; - var methods = []; - var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND']; - var data = { - isNode: type === 'node' || type === 'react', - isES6: opts.isES6 || type === 'react', - description: swagger.info.description, - isSecure: swagger.securityDefinitions !== undefined, - moduleName: opts.moduleName, - className: opts.className, - imports: opts.imports, - domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath.replace(/\/+$/g,'') : '', - methods: [], - definitions: [] - }; - - _.forEach(swagger.paths, function(api, path) { - var globalParams = []; - /** - * @param {Object} op - meta data for the request - * @param {string} m - HTTP method name - eg: 'get', 'post', 'put', 'delete' - */ - _.forEach(api, function(op, m) { - if (m.toLowerCase() === 'parameters') { - globalParams = op; - } + var swagger = opts.swagger; + var methods = []; + var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND']; + var data = { + isNode: type === 'node' || type === 'react', + isES6: opts.isES6 || type === 'react', + description: swagger.info.description, + isSecure: swagger.securityDefinitions !== undefined, + moduleName: opts.moduleName, + className: opts.className, + imports: opts.imports, + domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath.replace(/\/+$/g,'') : '', + methods: [], + definitions: [] + }; + + _.forEach(swagger.paths, function(api, path) { + var globalParams = []; + /** + * @param {Object} op - meta data for the request + * @param {string} m - HTTP method name - eg: 'get', 'post', 'put', 'delete' + */ + _.forEach(api, function(op, m) { + if (m.toLowerCase() === 'parameters') { + globalParams = op; + } + }); + _.forEach(api, function(op, m) { + var M = m.toUpperCase(); + if (M === '' || authorizedMethods.indexOf(M) === -1) { + return; + } + var secureTypes = []; + if (swagger.securityDefinitions !== undefined || op.security !== undefined) { + var mergedSecurity = _.merge([], swagger.security, op.security).map(function(security) { + return Object.keys(security); }); - _.forEach(api, function(op, m) { - var M = m.toUpperCase(); - if (M === '' || authorizedMethods.indexOf(M) === -1) { - return; - } - var secureTypes = []; - if (swagger.securityDefinitions !== undefined || op.security !== undefined) { - var mergedSecurity = _.merge([], swagger.security, op.security).map(function(security) { - return Object.keys(security); - }); - if (swagger.securityDefinitions) { - for (var sk in swagger.securityDefinitions) { - if (mergedSecurity.join(',').indexOf(sk) !== -1) { - secureTypes.push(swagger.securityDefinitions[sk].type); - } - } - } - } - - var methodName = (op.operationId ? normalizeName(op.operationId) : getPathToMethodName(opts, m, path)); - - // Make sure the method name is unique - if (methods.indexOf(methodName) !== -1) { - var i = 1; - while (true) { - if (methods.indexOf(methodName + '_' + i) !== -1) { - i++; - } else { - methodName = methodName + '_' + i; - break; - } - } - } - methods.push(methodName); - - var method = { - path: path, - className: opts.className, - methodName: methodName, - method: M, - isGET: M === 'GET', - isPOST: M === 'POST', - summary: op.description || op.summary, - externalDocs: op.externalDocs, - isSecure: swagger.security !== undefined || op.security !== undefined, - isSecureToken: secureTypes.indexOf('oauth2') !== -1, - isSecureApiKey: secureTypes.indexOf('apiKey') !== -1, - isSecureBasic: secureTypes.indexOf('basic') !== -1, - parameters: [], - headers: [], - }; - - // add 'destination' field if 'multiple: true' - if (opts.multiple) { - method.destination = op.tags[0].toLowerCase(); - } - - // add 'responses' field, that contains schemas and descriptions - method.responses = op.responses; - - if(method.isSecure && method.isSecureToken) { - data.isSecureToken = method.isSecureToken; - } - if(method.isSecure && method.isSecureApiKey) { - data.isSecureApiKey = method.isSecureApiKey; - } - if(method.isSecure && method.isSecureBasic) { - data.isSecureBasic = method.isSecureBasic; - } - var produces = op.produces || swagger.produces; - if (produces) { - method.headers.push({ - name: 'Accept', - value: `'${produces.map(function(value) { return value; }).join(', ')}'`, - }); - } - - var consumes = op.consumes || swagger.consumes; - if(consumes) { - method.headers.push({name: 'Content-Type', value: '\'' + consumes + '\'' }); + if (swagger.securityDefinitions) { + for (var sk in swagger.securityDefinitions) { + if (mergedSecurity.join(',').indexOf(sk) !== -1) { + secureTypes.push(swagger.securityDefinitions[sk].type); } + } + } + } + + var methodName = (op.operationId ? normalizeName(op.operationId) : getPathToMethodName(opts, m, path)); + + // Make sure the method name is unique + if (methods.indexOf(methodName) !== -1) { + var i = 1; + while (true) { + if (methods.indexOf(methodName + '_' + i) !== -1) { + i++; + } else { + methodName = methodName + '_' + i; + break; + } + } + } + methods.push(methodName); - var params = []; - if(_.isArray(op.parameters)) { - params = op.parameters; - } - params = params.concat(globalParams); - _.forEach(params, function(parameter) { - //Ignore parameters which contain the x-exclude-from-bindings extension - if(parameter['x-exclude-from-bindings'] === true) { - return; - } - - // Ignore headers which are injected by proxies & app servers - // eg: https://cloud.google.com/appengine/docs/go/requests#Go_Request_headers - if (parameter['x-proxy-header'] && !data.isNode) { - return; - } - if (_.isString(parameter.$ref)) { - var segments = parameter.$ref.split('/'); - parameter = swagger.parameters[segments.length === 1 ? segments[0] : segments[2] ]; - } - parameter.camelCaseName = _.camelCase(parameter.name); - if(parameter.enum && parameter.enum.length === 1) { - parameter.isSingleton = true; - parameter.singleton = parameter.enum[0]; - } - if(parameter.in === 'body'){ - parameter.isBodyParameter = true; - } else if(parameter.in === 'path'){ - parameter.isPathParameter = true; - } else if(parameter.in === 'query'){ - if(parameter['x-name-pattern']){ - parameter.isPatternType = true; - parameter.pattern = parameter['x-name-pattern']; - } - parameter.isQueryParameter = true; - } else if(parameter.in === 'header'){ - parameter.isHeaderParameter = true; - } else if(parameter.in === 'formData'){ - parameter.isFormParameter = true; - } - parameter.tsType = ts.convertType(parameter); - parameter.cardinality = parameter.required ? '' : '?'; - method.parameters.push(parameter); - }); - data.methods.push(method); + var method = { + path: path, + className: opts.className, + methodName: methodName, + method: M, + isGET: M === 'GET', + isPOST: M === 'POST', + summary: op.description || op.summary, + externalDocs: op.externalDocs, + isSecure: swagger.security !== undefined || op.security !== undefined, + isSecureToken: secureTypes.indexOf('oauth2') !== -1, + isSecureApiKey: secureTypes.indexOf('apiKey') !== -1, + isSecureBasic: secureTypes.indexOf('basic') !== -1, + parameters: [], + headers: [], + }; + + // add 'destination' field if 'multiple: true' + if (opts.multiple) { + method.destination = op.tags[0].toLowerCase(); + } + + // add 'responses' field, that contains schemas and descriptions + method.responses = op.responses; + + if(method.isSecure && method.isSecureToken) { + data.isSecureToken = method.isSecureToken; + } + if(method.isSecure && method.isSecureApiKey) { + data.isSecureApiKey = method.isSecureApiKey; + } + if(method.isSecure && method.isSecureBasic) { + data.isSecureBasic = method.isSecureBasic; + } + var produces = op.produces || swagger.produces; + if (produces) { + method.headers.push({ + name: 'Accept', + value: `'${produces.map(function(value) { return value; }).join(', ')}'`, }); + } + + var consumes = op.consumes || swagger.consumes; + if(consumes) { + method.headers.push({name: 'Content-Type', value: '\'' + consumes + '\'' }); + } + + var params = []; + if(_.isArray(op.parameters)) { + params = op.parameters; + } + params = params.concat(globalParams); + _.forEach(params, function(parameter) { + //Ignore parameters which contain the x-exclude-from-bindings extension + if(parameter['x-exclude-from-bindings'] === true) { + return; + } + + // Ignore headers which are injected by proxies & app servers + // eg: https://cloud.google.com/appengine/docs/go/requests#Go_Request_headers + if (parameter['x-proxy-header'] && !data.isNode) { + return; + } + if (_.isString(parameter.$ref)) { + var segments = parameter.$ref.split('/'); + parameter = swagger.parameters[segments.length === 1 ? segments[0] : segments[2] ]; + } + parameter.camelCaseName = _.camelCase(parameter.name); + if(parameter.enum && parameter.enum.length === 1) { + parameter.isSingleton = true; + parameter.singleton = parameter.enum[0]; + } + if(parameter.in === 'body'){ + parameter.isBodyParameter = true; + } else if(parameter.in === 'path'){ + parameter.isPathParameter = true; + } else if(parameter.in === 'query'){ + if(parameter['x-name-pattern']){ + parameter.isPatternType = true; + parameter.pattern = parameter['x-name-pattern']; + } + parameter.isQueryParameter = true; + } else if(parameter.in === 'header'){ + parameter.isHeaderParameter = true; + } else if(parameter.in === 'formData'){ + parameter.isFormParameter = true; + } + parameter.tsType = ts.convertType(parameter); + parameter.cardinality = parameter.required ? '' : '?'; + method.parameters.push(parameter); + }); + data.methods.push(method); }); + }); - _.forEach(swagger.definitions, function(definition, name) { - data.definitions.push({ - name: name, - description: definition.description, - tsType: ts.convertType(definition, swagger) - }); + _.forEach(swagger.definitions, function(definition, name) { + data.definitions.push({ + name: name, + description: definition.description, + tsType: ts.convertType(definition, swagger) }); + }); - return data; + return data; }; var getViewForSwagger1 = function(opts, type){ - var swagger = opts.swagger; - var data = { - isNode: type === 'node' || type === 'react', - isES6: opts.isES6 || type === 'react', - description: swagger.description, - moduleName: opts.moduleName, + var swagger = opts.swagger; + var data = { + isNode: type === 'node' || type === 'react', + isES6: opts.isES6 || type === 'react', + description: swagger.description, + moduleName: opts.moduleName, + className: opts.className, + domain: swagger.basePath ? swagger.basePath : '', + methods: [] + }; + swagger.apis.forEach(function(api){ + api.operations.forEach(function(op){ + if (op.method === 'OPTIONS') { + return; + } + var method = { + path: api.path, className: opts.className, - domain: swagger.basePath ? swagger.basePath : '', - methods: [] - }; - swagger.apis.forEach(function(api){ - api.operations.forEach(function(op){ - if (op.method === 'OPTIONS') { - return; - } - var method = { - path: api.path, - className: opts.className, - methodName: op.nickname, - method: op.method, - isGET: op.method === 'GET', - isPOST: op.method.toUpperCase() === 'POST', - summary: op.summary, - parameters: op.parameters, - headers: [] - }; - - if(op.produces) { - var headers = []; - headers.value = []; - headers.name = 'Accept'; - headers.value.push(op.produces.map(function(value) { return '\'' + value + '\''; }).join(', ')); - method.headers.push(headers); - } - - op.parameters = op.parameters ? op.parameters : []; - op.parameters.forEach(function(parameter) { - parameter.camelCaseName = _.camelCase(parameter.name); - if(parameter.enum && parameter.enum.length === 1) { - parameter.isSingleton = true; - parameter.singleton = parameter.enum[0]; - } - if(parameter.paramType === 'body'){ - parameter.isBodyParameter = true; - } else if(parameter.paramType === 'path'){ - parameter.isPathParameter = true; - } else if(parameter.paramType === 'query'){ - if(parameter['x-name-pattern']){ - parameter.isPatternType = true; - parameter.pattern = parameter['x-name-pattern']; - } - parameter.isQueryParameter = true; - } else if(parameter.paramType === 'header'){ - parameter.isHeaderParameter = true; - } else if(parameter.paramType === 'form'){ - parameter.isFormParameter = true; - } - }); - data.methods.push(method); - }); + methodName: op.nickname, + method: op.method, + isGET: op.method === 'GET', + isPOST: op.method.toUpperCase() === 'POST', + summary: op.summary, + parameters: op.parameters, + headers: [] + }; + + if(op.produces) { + var headers = []; + headers.value = []; + headers.name = 'Accept'; + headers.value.push(op.produces.map(function(value) { return '\'' + value + '\''; }).join(', ')); + method.headers.push(headers); + } + + op.parameters = op.parameters ? op.parameters : []; + op.parameters.forEach(function(parameter) { + parameter.camelCaseName = _.camelCase(parameter.name); + if(parameter.enum && parameter.enum.length === 1) { + parameter.isSingleton = true; + parameter.singleton = parameter.enum[0]; + } + if(parameter.paramType === 'body'){ + parameter.isBodyParameter = true; + } else if(parameter.paramType === 'path'){ + parameter.isPathParameter = true; + } else if(parameter.paramType === 'query'){ + if(parameter['x-name-pattern']){ + parameter.isPatternType = true; + parameter.pattern = parameter['x-name-pattern']; + } + parameter.isQueryParameter = true; + } else if(parameter.paramType === 'header'){ + parameter.isHeaderParameter = true; + } else if(parameter.paramType === 'form'){ + parameter.isFormParameter = true; + } + }); + data.methods.push(method); }); - return data; + }); + return data; }; /** @@ -310,89 +310,89 @@ var getCode = function(options, type) { // add all of the necessary query options var data = querier(formatted); - if (type === 'custom') { - if (!_.isObject(opts.template) || !_.isString(opts.template.class) || !_.isString(opts.template.method)) { - throw new Error('Unprovided custom template. Please use the following template: template: { class: "...", method: "...", request: "..." }'); - } - } else { - if (!_.isObject(opts.template)) { - opts.template = {}; - } - var templates = __dirname + '/../templates/'; - - // choose templates based on the 'multiple' option TODO: Typescript support? - if (opts.multiple) { - opts.template.class = fs.readFileSync(templates + 'multi-class.mustache', 'utf-8'); - opts.template.method = fs.readFileSync(templates + 'multi-method.mustache', 'utf-8'); - } else { - opts.template.class = opts.template.class || fs.readFileSync(templates + type + '-class.mustache', 'utf-8'); - opts.template.method = opts.template.method || fs.readFileSync(templates + (type === 'typescript' ? 'typescript-' : '') + 'method.mustache', 'utf-8'); - } - if (type === 'typescript') { - opts.template.type = opts.template.type || fs.readFileSync(templates + 'type.mustache', 'utf-8'); - } - } - - if (opts.mustache) { - _.assign(data, opts.mustache); - } - - var source = Mustache.render(opts.template.class, data, opts.template); - var lintOptions = { - node: type === 'node' || type === 'custom', - browser: type === 'angular' || type === 'custom' || type === 'react', - undef: true, - strict: true, - trailing: true, - smarttabs: true, - maxerr: 999 - }; - if (opts.esnext) { - lintOptions.esnext = true; + if (type === 'custom') { + if (!_.isObject(opts.template) || !_.isString(opts.template.class) || !_.isString(opts.template.method)) { + throw new Error('Unprovided custom template. Please use the following template: template: { class: "...", method: "...", request: "..." }'); } - - if(type === 'typescript') { - opts.lint = false; + } else { + if (!_.isObject(opts.template)) { + opts.template = {}; } + var templates = __dirname + '/../templates/'; - if (opts.lint === undefined || opts.lint === true) { - lint(source, lintOptions); - lint.errors.forEach(function(error) { - if (error.code[0] === 'E') { - throw new Error(error.reason + ' in ' + error.evidence + ' (' + error.code + ')'); - } - }); - } + // choose templates based on the 'multiple' option TODO: Typescript support? if (opts.multiple) { - return splitter.split(beautify(source, { - indent_size: 2, - max_preserve_newlines: 2 - }), opts.className, opts.path, opts.controllersDirName); - } - if (opts.beautify === undefined || opts.beautify === true) { - return beautify(source, { indent_size: 4, max_preserve_newlines: 2 }); + opts.template.class = fs.readFileSync(templates + 'multi-class.mustache', 'utf-8'); + opts.template.method = fs.readFileSync(templates + 'multi-method.mustache', 'utf-8'); } else { - return source; + opts.template.class = opts.template.class || fs.readFileSync(templates + type + '-class.mustache', 'utf-8'); + opts.template.method = opts.template.method || fs.readFileSync(templates + (type === 'typescript' ? 'typescript-' : '') + 'method.mustache', 'utf-8'); } + if (type === 'typescript') { + opts.template.type = opts.template.type || fs.readFileSync(templates + 'type.mustache', 'utf-8'); + } + } + + if (opts.mustache) { + _.assign(data, opts.mustache); + } + + var source = Mustache.render(opts.template.class, data, opts.template); + var lintOptions = { + node: type === 'node' || type === 'custom', + browser: type === 'angular' || type === 'custom' || type === 'react', + undef: true, + strict: true, + trailing: true, + smarttabs: true, + maxerr: 999 + }; + if (opts.esnext) { + lintOptions.esnext = true; + } + + if(type === 'typescript') { + opts.lint = false; + } + + if (opts.lint === undefined || opts.lint === true) { + lint(source, lintOptions); + lint.errors.forEach(function(error) { + if (error.code[0] === 'E') { + throw new Error(error.reason + ' in ' + error.evidence + ' (' + error.code + ')'); + } + }); + } + if (opts.multiple) { + return splitter.split(beautify(source, { + indent_size: 2, + max_preserve_newlines: 2 + }), opts.className, opts.path, opts.controllersDirName); + } + if (opts.beautify === undefined || opts.beautify === true) { + return beautify(source, { indent_size: 4, max_preserve_newlines: 2 }); + } else { + return source; + } }; exports.CodeGen = { - getTypescriptCode: function(opts){ - if (opts.swagger.swagger !== '2.0') { - throw 'Typescript is only supported for Swagger 2.0 specs.'; - } - return getCode(opts, 'typescript'); - }, - getAngularCode: function(opts){ - return getCode(opts, 'angular'); - }, - getNodeCode: function(opts){ - return getCode(opts, 'node'); - }, - getReactCode: function(opts){ - return getCode(opts, 'react'); - }, - getCustomCode: function(opts){ - return getCode(opts, 'custom'); + getTypescriptCode: function(opts){ + if (opts.swagger.swagger !== '2.0') { + throw 'Typescript is only supported for Swagger 2.0 specs.'; } + return getCode(opts, 'typescript'); + }, + getAngularCode: function(opts){ + return getCode(opts, 'angular'); + }, + getNodeCode: function(opts){ + return getCode(opts, 'node'); + }, + getReactCode: function(opts){ + return getCode(opts, 'react'); + }, + getCustomCode: function(opts){ + return getCode(opts, 'custom'); + } }; diff --git a/lib/formatter.js b/lib/formatter.js index 794e4fa2..d462eef2 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -5,9 +5,10 @@ const _ = require('lodash'); * Parse nested schemas to build the proper response structure * @param {object} entry - entry definition object * @param {array} definitions - all of the definitions + * @param {number} status - response status * @returns {object} */ -function responseBuilder(entry, definitions) { +function responseBuilder(entry, definitions, status) { try { const response = {}; @@ -23,6 +24,18 @@ function responseBuilder(entry, definitions) { properties.forEach((property) => { if (property.tsType === 'string') { response[property.name] = property.example || 'string'; + + // this is done for a particular case + // TODO: proper error messages mapping - sligtly change the formatter + if (status >= 400) { + if (property.name === 'description') { + response[property.name] = 'ERROR_DESCRIPTION'; + } + if (property.name === 'reasonPhrase') { + response[property.name] = 'BAD_REQUEST'; + } + } + } if (property.tsType === 'number') { @@ -41,7 +54,7 @@ function responseBuilder(entry, definitions) { definitions.forEach((definition) => { if (definition.name === target) { // recursive call to unwrap all of the refs - response[property.name] = [responseBuilder(definition, definitions)]; + response[property.name] = [responseBuilder(definition, definitions, status)]; } }); } else { @@ -62,7 +75,7 @@ function responseBuilder(entry, definitions) { definitions.forEach((definition) => { if (definition.name === property.target) { // recursive call to unwrap all of the refs - response[property.name] = responseBuilder(definition, definitions); + response[property.name] = responseBuilder(definition, definitions, status); } }); } @@ -152,11 +165,13 @@ function format(data) { let responseObject = {}; definitions.forEach((def) => { if (def.name === ref) { - responseObject = responseBuilder(def, definitions); + responseObject = responseBuilder(def, definitions, formatted[response].status); } }); - formatted[response].code = `return res.code(${formatted[response].status}).send(${inspect(responseObject, { showHidden: false, depth: null })});`; + const isErr = formatted[response].status >= 400 ? 'err' : 'null'; + formatted[response].code = 'return global.response(req, res, ' + + `${formatted[response].status}, ${inspect(responseObject, { showHidden: false, depth: null })}, ${isErr});`; }); // add the code to the resulting object diff --git a/package.json b/package.json index e5b723f0..cc496c87 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/wcandillon/swagger-js-codegen/issues" }, "engines" : { - "node" : "^11.13.0" + "node" : "^11.14.0" }, "repository": { "type": "git", diff --git a/templates/multi-method.mustache b/templates/multi-method.mustache index c5ccb0e8..791e9003 100644 --- a/templates/multi-method.mustache +++ b/templates/multi-method.mustache @@ -54,8 +54,8 @@ {{#200}}{{{code}}}{{/200}} {{/responses}} } catch (err) { - /* - handle errors - */ + {{#responses}} + {{#400}}{{{code}}}{{/400}} + {{/responses}} } };