UNPKG

angular4-swagger-client-generator

Version:

Angular 4/5/6 REST API client generator from Swagger JSON with camel case settings

544 lines (460 loc) 22.4 kB
'use strict'; var fs = require('fs'); var Mustache = require('mustache'); var _ = require('lodash'); var Generator = (function () { function Generator(swaggerfile, outputpath) { this._swaggerfile = swaggerfile; this._outputPath = outputpath; } Generator.prototype.Debug = false; Generator.prototype.initialize = function () { this.LogMessage('Reading Swagger file', this._swaggerfile); var swaggerfilecontent = fs.readFileSync(this._swaggerfile, 'UTF-8'); this.LogMessage('Parsing Swagger JSON'); this.swaggerParsed = JSON.parse(swaggerfilecontent); this.LogMessage('Reading Mustache templates'); this.templates = { 'class': fs.readFileSync(__dirname + '/../templates/angular2-service.mustache', 'utf-8'), 'model': fs.readFileSync(__dirname + '/../templates/angular2-model.mustache', 'utf-8'), 'models_export': fs.readFileSync(__dirname + '/../templates/angular2-models-export.mustache', 'utf-8') }; this.LogMessage('Creating Mustache viewModel'); this.viewModel = this.createMustacheViewModel(); this.initialized = true; }; Generator.prototype.generateAPIClient = function () { if (this.initialized !== true) { this.initialize(); } this.generateClient(); this.generateModels(); this.generateCommonModelsExportDefinition(); this.LogMessage('API client generated successfully'); }; Generator.prototype.generateClient = function () { if (this.initialized !== true) { this.initialize(); } // generate main API client class this.LogMessage('Rendering template for API'); var result = this.renderLintAndBeautify(this.templates.class, this.viewModel, this.templates); var outfile = this._outputPath + '/' + 'index.ts'; this.LogMessage('Creating output file', outfile); fs.writeFileSync(outfile, result, 'utf-8') }; Generator.prototype.generateModels = function () { var that = this; if (this.initialized !== true) { this.initialize(); } var outputdir = this._outputPath + '/models'; if (!fs.existsSync(outputdir)) { fs.mkdirSync(outputdir); } // generate API models _.forEach(this.viewModel.definitions, function (definition) { that.LogMessage('Rendering template for model', definition.name); var result = that.renderLintAndBeautify(that.templates.model, definition, that.templates); var outfile = outputdir + '/' + definition.name.toLowerCase() + '.model.ts'; that.LogMessage('Creating output file', outfile); fs.writeFileSync(outfile, result, 'utf-8') }); }; Generator.prototype.generateCommonModelsExportDefinition = function () { if (this.initialized !== true) { this.initialize(); } var outputdir = this._outputPath; if (!fs.existsSync(outputdir)) { fs.mkdirSync(outputdir); } this.LogMessage('Rendering common models export'); var result = this.renderLintAndBeautify(this.templates.models_export, this.viewModel, this.templates); var outfile = outputdir + '/models.ts'; this.LogMessage('Creating output file', outfile); fs.writeFileSync(outfile, result, 'utf-8') }; Generator.prototype.renderLintAndBeautify = function (template, model) { return Mustache.render(template, model); }; Generator.prototype.createMustacheViewModel = function () { var that = this; var swagger = this.swaggerParsed; var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; var data = { isNode: false, description: swagger.info.description, isSecure: swagger.securityDefinitions !== undefined, swagger: swagger, domain: (swagger.schemes && swagger.schemes.length > 0 ? swagger.schemes[0] : 'http') + '://' + (swagger.host ? swagger.host : 'localhost') + ('/' === swagger.basePath ? '' : swagger.basePath), methods: [], enums: [], definitions: [] }; _.forEach(swagger.paths, function (api, path) { var globalParams = []; debugger; _.forEach(api, function (op, m) { if (m.toLowerCase() === 'parameters') { globalParams = op; } }); _.forEach(api, function (op, m) { if (authorizedMethods.indexOf(m.toUpperCase()) === -1) { return; } // The description line is optional in the spec var summaryLines = []; if (op.description) { summaryLines = op.description.split('\n'); summaryLines.splice(summaryLines.length - 1, 1); } var method = { path: path, backTickPath: path.replace(/(\{.*?\})/g, '$$$1'), methodName: op['x-swagger-js-method-name'] ? op['x-swagger-js-method-name'] : (op.operationId ? op.operationId : that.getPathToMethodName(m, path)), method: m.toUpperCase(), angular2httpMethod: m.toLowerCase(), isGET: m.toUpperCase() === 'GET', hasPayload: !_.includes(['GET', 'DELETE', 'HEAD'], m.toUpperCase()), summaryLines: summaryLines, isSecure: swagger.security !== undefined || op.security !== undefined, parameters: [], hasQueryParameters: false, hasHeaderParameters: false, hasJsonResponse: _.some(_.defaults([], swagger.produces, op.produces), function (response) { // TODO PREROBIT return response.indexOf('/json') != -1; }) }; var params = []; if (_.isArray(op.parameters)) { params = op.parameters; } params = params.concat(globalParams); // Index file! _.forEach(params, function (parameter) { // 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 (_.has(parameter, 'schema') && _.isString(parameter.schema.$ref)) { parameter.type = that.camelCase(that.getRefType(parameter.schema.$ref)); } parameter.camelCaseName = that.camelCase(parameter.name); // lets also check for a bunch of Java objects! if (parameter.type === 'integer' || parameter.type === 'double') { parameter.typescriptType = 'number'; } else if (parameter.type === 'object') { parameter.typescriptType = 'any'; } else if (parameter.type === 'array') { if (parameter.items['type'] === 'integer' || parameter.items['type'] === 'double') { parameter.typescriptType = 'number[]'; } else { parameter.typescriptType = that.camelCase(parameter.items['type']) +'[]'; } parameter.isArray = true; } else { parameter.typescriptType = that.camelCase(parameter.type); } if (parameter.enum) { if (parameter.enum.length === 1) { parameter.isSingleton = true; parameter.singleton = parameter.enum[0]; } else if (parameter.type === 'string') { var enumeration = { name: null, values: [] } // upper keyword to templates enumeration.upper = function() { return function (text, render) { return render(text).toUpperCase(); } }; enumeration.name = parameter.name[0].toUpperCase() + parameter.name.substring(1); var addEnum = true; for (var i = 0; i < data.enums.length; i++) { if (data.enums[i].name === enumeration.name) { addEnum = false; } } if (addEnum) { for (var i = 0; i < parameter.enum.length; i++) { var value = {value: parameter.enum[i], isLast: i === parameter.enum.length - 1}; enumeration.values.push(value); } data.enums.push(enumeration); } parameter.typescriptType = enumeration.name; } } if (parameter.in === 'body') { parameter.isBodyParameter = true; method.hasBodyParameters = true; } else if (parameter.in === 'path') { parameter.isPathParameter = true; } else if (parameter.in === 'query' || parameter.in === 'modelbinding') { parameter.isQueryParameter = true; if (parameter['x-name-pattern']) { parameter.isPatternType = true; } method.hasQueryParameters = true; } else if (parameter.in === 'header') { parameter.isHeaderParameter = true; method.hasHeaderParameters = true; } else if (parameter.in === 'formData') { parameter.isFormParameter = true; } method.parameters.push(parameter); }); if (method.parameters.length > 0) { method.parameters[method.parameters.length - 1].isLast = true; } if (op.responses['200'] != undefined) { var responseSchema = op.responses['200'].schema; if (_.has(responseSchema, 'type')) { if (responseSchema['type'] === 'array') { var items = responseSchema.items; if (_.has(items, '$ref')) { method.response = that.camelCase(items['$ref'].replace('#/definitions/', '')) + '[]'; } else { method.response = that.camelCase(items['type']) + '[]'; } } else { method.response = 'any'; } } else if (_.has(responseSchema, '$ref')) { method.response = that.camelCase(responseSchema['$ref'].replace('#/definitions/', '')); } else { method.response = 'any'; } } else { // check non-200 response codes method.response = 'any'; } data.methods.push(method); }); }); _.forEach(swagger.definitions, function (defin, defVal) { var defName = that.camelCase(defVal); var definition = { name: defName, isLast: false, hasEnums: false, enums: [], properties: [], imports: [] }; // lower keyword to templates definition.lower = function() { return function (text, render) { return render(text).toLowerCase(); } }; _.forEach(defin.properties, function (propin, propVal) { var property = { name: propVal, isRef: _.has(propin, '$ref') || (propin.type === 'array' && _.has(propin.items, '$ref')), isArray: propin.type === 'array', isEnum: propin.enum, type: null, typescriptType: null }; var enumeration = { name: null, values: [] }; // upper keyword to templates enumeration.upper = function() { return function (text, render) { return render(text).toUpperCase(); } }; if (property.isArray) { if (_.has(propin.items, '$ref')) { property.type = that.camelCase(propin.items['$ref'].replace('#/definitions/', '')); } else if (_.has(propin.items, 'type')) { property.type = that.camelCase(propin.items['type']); } else { property.type = propin.type; } } else if (property.isEnum) { property.type = property.name[0].toUpperCase() + property.name.substring(1); enumeration.name = property.type; for (var i = 0; i < propin.enum.length; i++) { var value = {value: propin.enum[i], isLast: i === propin.enum.length - 1}; enumeration.values.push(value); } definition.enums.push(enumeration); definition.hasEnums = true; if (data.enums.length > 0) { for (var i = 0; i < data.enums.length; i++) { if (data.enums[i].name === enumeration.name) { data.enums.splice(i, 1); break; } } } } else { property.type = _.has(propin, '$ref') ? that.camelCase(propin['$ref'].replace('#/definitions/', '')) : propin.type; } if (property.type === 'integer' || property.type === 'double') { property.typescriptType = 'number'; } else if (property.type === 'object') { property.typescriptType = 'any'; } else { property.typescriptType = property.type; } if (property.isRef) { // Don't import hierarchy of same type if (defName !== property.type) { // Don't duplicate import statements var addImport = true; for (var i = 0; i < definition.imports.length; i++) { if (definition.imports[i] === property.type) { addImport = false; } } if (addImport) { definition.imports.push(property.type); } } } definition.properties.push(property); }); _.forEach(defin.allOf, function (oneOf, ofVal) { _.forEach(oneOf.properties, function (propin, propVal) { var property = { name: propVal, isRef: _.has(propin, '$ref') || (propin.type === 'array' && _.has(propin.items, '$ref')), isArray: propin.type === 'array', isEnum: propin.enum, type: null, typescriptType: null }; var enumeration = { name: null, values: [] }; // upper keyword to templates enumeration.upper = function() { return function (text, render) { return render(text).toUpperCase(); } }; if (property.isArray) { if (_.has(propin.items, '$ref')) { property.type = that.camelCase(propin.items['$ref'].replace('#/definitions/', '')); } else if (_.has(propin.items, 'type')) { property.type = that.camelCase(propin.items['type']); } else { property.type = propin.type; } } else if (property.isEnum) { property.type = property.name[0].toUpperCase() + property.name.substring(1); enumeration.name = property.type; for (var i = 0; i < propin.enum.length; i++) { var value = {value: propin.enum[i], isLast: i === propin.enum.length - 1}; enumeration.values.push(value); } definition.enums.push(enumeration); definition.hasEnums = true; if (data.enums.length > 0) { for (var i = 0; i < data.enums.length; i++) { if (data.enums[i].name === enumeration.name) { data.enums.splice(i, 1); break; } } } } else { property.type = _.has(propin, '$ref') ? that.camelCase(propin['$ref'].replace('#/definitions/', '')) : propin.type; } if (property.type === 'integer' || property.type === 'double') { property.typescriptType = 'number'; } else if (property.type === 'object') { property.typescriptType = 'any'; } else { property.typescriptType = property.type; } if (property.isRef) { // Don't duplicate import statements var addImport = true; for (var i = 0; i < definition.imports.length; i++) { if (definition.imports[i] === property.type) { addImport = false; } } if (addImport) { definition.imports.push(property.type); } } definition.properties.push(property); }); }); data.definitions.push(definition); }); if (data.definitions.length > 0) { data.definitions[data.definitions.length - 1].isLast = true; for (var i = 0; i < data.definitions.length; i++) { data.definitions[i].imports.sort(); data.definitions[i].properties.sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); } data.definitions.sort((a,b) => (a.defName > b.defName) ? 1 : ((b.defName > a.defName) ? -1 : 0)); } data.methods.sort((a,b) => (a.methodName > b.methodName) ? 1 : ((b.methodName > a.methodName) ? -1 : 0)); return data; }; Generator.prototype.getRefType = function(refString) { var segments = refString.split('/'); return segments.length === 3 ? segments[2] : segments[0]; }; Generator.prototype.getPathToMethodName = function(m, path) { if (path === '/' || path === '') { return m; } // clean url path for requests ending with '/' var cleanPath = path; if (cleanPath.indexOf('/', cleanPath.length - 1) !== -1) { cleanPath = cleanPath.substring(0, cleanPath.length - 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 = this.camelCase(segments.join('-')); return m.toLowerCase() + result[0].toUpperCase() + result.substring(1); }; Generator.prototype.camelCase = function(text) { if (!text) { return text; } if (text.indexOf('-') === -1 && text.indexOf('.') === -1) { return text; } var tokens = []; text.split('-').forEach(function (token, index) { tokens.push((index > 0 ? token[0].toUpperCase() : token[0]) + token.substring(1)); }); var partialres = tokens.join(''); tokens = []; partialres.split('.').forEach(function (token, index) { tokens.push((index > 0 ? token[0].toUpperCase() : token[0]) + token.substring(1)); }); return tokens.join(''); }; Generator.prototype.LogMessage = function(text, param) { if (this.Debug) { console.log(text, param || ''); } }; return Generator; })(); module.exports.Generator = Generator;