diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55371e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.vscode \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..599b589 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Swagger to fibjs Codegen + +This package generates a fibjs class from a [swagger specification file](https://github.com/wordnik/swagger-spec). The code is generated using [mustache templates](https://github.com/mtennoe/swagger-js-codegen/tree/master/templates) and is quality checked by [jshint](https://github.com/jshint/jshint/) and beautified by [js-beautify](https://github.com/beautify-web/js-beautify). + +The generator is based on [superagent](https://github.com/visionmedia/superagent) and can be used for fibjs. + +This fork was made to simplify some parts, add some more features, and tailor it more to specific use cases. + +## Installation + +```bash +fibjs --install fib-swagger +``` + +## cli + +```bash +fibjs node-modules/fib-swagger/lib/cli -c Test gen test.json test.js +``` + +## Example Code + +```javascript +var fs = require("fs"); +var CodeGen = require("fib-swagger").CodeGen; + +var file = "swagger/spec.json"; +var swagger = JSON.parse(fs.readFileSync(file, "UTF-8")); +var tsSourceCode = CodeGen.getFibjstCode({ + className: "Test", + swagger: swagger +}); +console.log(tsSourceCode); +``` diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..d5d8a22 --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,45 @@ +'use strict'; + +const fs = require('fs'); +const pkg = require('../package.json'); +const cli = require('commander'); +const yaml = require('js-yaml').safeLoad; +const CodeGen = require('./index.js').CodeGen; + +cli + .version(pkg.version) + .command('gen ') + .description('Generate from Swagger file') + .option('-c, --class ', 'Class name [Test]', 'Test') + .option('-l, --lint', 'Whether or not to run jslint on the generated code [false]') + .option('-b, --beautify', 'Whether or not to beautify the generated code [false]') + .action((file, out, options) => { + const fn = CodeGen.getFibjsCode; + options.lint = options.lint || false; + options.beautify = options.beautify || false; + + const content = fs.readFileSync(file, 'utf-8'); + + var swagger; + try { + swagger = JSON.parse(content); + } catch (e) { + swagger = yaml(content); + } + + const result = fn({ + moduleName: options.module, + className: options.class, + swagger: swagger, + lint: options.lint, + beautify: options.beautify + }); + + fs.writeFileSync(out, result); + }); + +cli.parse(process.argv); + +if (!cli.args.length) { + cli.help(); +} diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..a37ea30 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,29 @@ +var fs = require('fs'); +var path = require('path'); +var jsyaml = require('js-yaml'); +var CodeGen = require('swagger-typescript-codegen').CodeGen; + +CodeGen.getFibjsCode = function (opt) { + var _opt = {}; + + if (opt) { + for (var k in opt) + _opt[k] = opt[k]; + opt = _opt; + } + + if (opt.swagger.openapi) { + opt.swagger.swagger = '2.0'; + opt.swagger.securityDefinitions = opt.swagger.components.securitySchemes; + } + + opt.template = { + class: fs.readFileSync(path.join(__dirname, "../templates/class.mustache"), 'utf-8'), + method: fs.readFileSync(path.join(__dirname, "../templates/method.mustache"), 'utf-8'), + type: fs.readFileSync(path.join(__dirname, "../templates/type.mustache"), 'utf-8') + }; + + return CodeGen.getCustomCode(opt); +}; + +exports.CodeGen = CodeGen; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..65ab80e --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "fib-swagger", + "version": "1.0.0", + "description": "Swagger to fibjs Codegen", + "repository": "", + "main": "lib/index.js", + "keywords": "", + "author": "", + "license": "ISC", + "dependencies": { + "swagger-typescript-codegen": "^3.2.2", + "js-yaml": "^3.14.1" + } +} \ No newline at end of file diff --git a/templates/class.mustache b/templates/class.mustache new file mode 100644 index 0000000..492b5f2 --- /dev/null +++ b/templates/class.mustache @@ -0,0 +1,157 @@ +/*jshint -W069 */ +/** + * {{&description}} + * @class {{&className}} + * @param {(string|object)} [domainOrOptions] - The project domain or options object. If object, see the object's optional properties. + * @param {string} [domainOrOptions.domain] - The project domain + * @param {object} [domainOrOptions.token] - auth token - object with value property and optional headerOrQueryName and isQuery properties + */ +{{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} {{&className}} = (function(){ + 'use strict'; + + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} http = require('http'); + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} util = require('util'); + + function {{&className}}(options){ + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} domain = (typeof options === 'object') ? options.domain : options; + this.domain = domain ? domain : '{{&domain}}'; + if(this.domain.length === 0) { + throw new Error('Domain parameter must be specified as a string.'); + } + this.headers = options.headers || {}; + {{#isSecure}} + {{#isSecureToken}} + this.token = (typeof options === 'object') ? (options.token ? options.token : {}) : {}; + {{/isSecureToken}} + {{#isSecureApiKey}} + this.apiKey = (typeof options === 'object') ? (options.apiKey ? options.apiKey : {}) : {}; + {{/isSecureApiKey}} + {{#isSecureBasic}} + this.basic = (typeof options === 'object') ? (options.basic ? options.basic : {}) : {}; + {{/isSecureBasic}} + {{/isSecure}} + } + + function mergeQueryParams(parameters, queryParameters) { + if (parameters.$queryParameters) { + Object.keys(parameters.$queryParameters) + .forEach(function(parameterName) { + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} parameter = parameters.$queryParameters[parameterName]; + queryParameters[parameterName] = parameter; + }); + } + return queryParameters; + } + + function request(method, url, parameters, body, headers, queryParameters, form) { + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} req = {}; + + if(Object.keys(queryParameters).length > 0) { + req.query = queryParameters; + } + if(Object.keys(headers).length > 0) { + req.headers = headers; + } + + if(Object.keys(form).length > 0) { + req.body = form; + }else if(typeof(body) === 'object' && !(body instanceof Buffer)) { + if(Object.keys(body).length > 0) { + req.json = body; + } + } else if(body) { + req.body = body; + } + + var response = http.request(method, url, req); + + if (response.statusCode >= 200 && response.statusCode <= 299) { + return response.data; + } + + throw new Error(response.data); + } + + {{#isSecure}} + {{#isSecureToken}} + /** + * Set Token + * @method + * @name {{&className}}#setToken + * @param {string} value - token's value + * @param {string} headerOrQueryName - the header or query name to send the token at + * @param {boolean} isQuery - true if send the token as query param, otherwise, send as header param + */ + {{&className}}.prototype.setToken = function (value, headerOrQueryName, isQuery) { + this.token.value = value; + this.token.headerOrQueryName = headerOrQueryName; + this.token.isQuery = isQuery; + }; + {{/isSecureToken}} + {{#isSecureApiKey}} + /** + * Set Api Key + * @method + * @name {{&className}}#setApiKey + * @param {string} value - apiKey's value + * @param {string} headerOrQueryName - the header or query name to send the apiKey at + * @param {boolean} isQuery - true if send the apiKey as query param, otherwise, send as header param + */ + {{&className}}.prototype.setApiKey = function (value, headerOrQueryName, isQuery) { + this.apiKey.value = value; + this.apiKey.headerOrQueryName = headerOrQueryName; + this.apiKey.isQuery = isQuery; + }; + {{/isSecureApiKey}} + {{#isSecureBasic}} + /** + * Set Basic Auth + * @method + * @name {{&className}}#setBasicAuth + * @param {string} username + * @param {string} password + */ + {{&className}}.prototype.setBasicAuth = function (username, password) { + this.basic.username = username; + this.basic.password = password; + }; + {{/isSecureBasic}} + /** + * Set Auth headers + * @method + * @name {{&className}}#setAuthHeaders + * @param {object} headerParams - headers object + */ + {{&className}}.prototype.setAuthHeaders = function (headerParams) { + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} headers = headerParams ? headerParams : {}; + {{#isSecureToken}} + if (!this.token.isQuery) { + if (this.token.headerOrQueryName) { + headers[this.token.headerOrQueryName] = this.token.value; + } else if (this.token.value) { + headers['Authorization'] = 'Bearer ' + this.token.value; + } + } + {{/isSecureToken}} + {{#isSecureApiKey}} + if (!this.apiKey.isQuery && this.apiKey.headerOrQueryName) { + headers[this.apiKey.headerOrQueryName] = this.apiKey.value; + } + {{/isSecureApiKey}} + {{#isSecureBasic}} + if (this.basic.username && this.basic.password) { + headers['Authorization'] = 'Basic ' + new Buffer(this.basic.username + ':' + this.basic.password).toString("base64"); + } + {{/isSecureBasic}} + return headers; + }; + {{/isSecure}} + + {{#methods}} + {{> method}} + {{/methods}} + + return {{&className}}; +})(); + +exports.{{&className}} = {{&className}}; diff --git a/templates/method.mustache b/templates/method.mustache new file mode 100644 index 0000000..0888556 --- /dev/null +++ b/templates/method.mustache @@ -0,0 +1,92 @@ +/** + * {{&summary}} + * @method + * @name {{&className}}#{{&methodName}} + * @param {object} parameters - method options and parameters +{{#parameters}} + {{^isSingleton}} * @param {{=<% %>=}}{<%&type%>}<%={{ }}=%> parameters.{{&camelCaseName}} - {{&description}}{{/isSingleton}} +{{/parameters}} + */ + {{&className}}.prototype.{{&methodName}} = function(parameters){ + if(parameters === undefined) { + parameters = {}; + } + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} domain = this.domain, path = '{{&path}}'; + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} body = {}, queryParameters = {}, form = {}; + {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} headers = util.clone(this.headers); + + {{#isSecure}} + headers = this.setAuthHeaders(headers); + {{/isSecure}} + {{#headers}} + headers['{{&name}}'] = [{{&value}}]; + {{/headers}} + + {{#parameters}} + {{#isQueryParameter}} + {{#isSingleton}} + queryParameters['{{&name}}'] = '{{&singleton}}'; + {{/isSingleton}} + {{^isSingleton}} + {{#isPatternType}} + Object.keys(parameters).forEach(function(parameterName) { + if(new RegExp('{{&pattern}}').test(parameterName)){ + queryParameters[parameterName] = parameters[parameterName]; + } + }); + {{/isPatternType}} + {{#default}} + /** set default value **/ + queryParameters['{{&name}}'] = {{&default}}; + {{/default}} + + {{^isPatternType}} + if(parameters['{{&camelCaseName}}'] !== undefined){ + queryParameters['{{&name}}'] = parameters['{{&camelCaseName}}']; + } + {{/isPatternType}} + {{/isSingleton}} + {{/isQueryParameter}} + + {{#isPathParameter}} + path = path.replace('{{=<% %>=}}{<%&name%>}<%={{ }}=%>', parameters['{{&camelCaseName}}']); + {{/isPathParameter}} + + {{#isHeaderParameter}} + {{#isSingleton}} + headers['{{&name}}'] = '{{&singleton}}'; + {{/isSingleton}} + {{^isSingleton}} + if(parameters['{{&camelCaseName}}'] !== undefined){ + headers['{{&name}}'] = parameters['{{&camelCaseName}}']; + } + {{/isSingleton}} + {{/isHeaderParameter}} + + {{#isBodyParameter}} + if(parameters['{{&camelCaseName}}'] !== undefined){ + body = parameters['{{&camelCaseName}}']; + } + {{/isBodyParameter}} + + {{#isFormParameter}} + {{#isSingleton}} + form['{{&name}}'] = '{{&singleton}}'; + {{/isSingleton}} + {{^isSingleton}} + if(parameters['{{&camelCaseName}}'] !== undefined){ + form['{{&name}}'] = parameters['{{&camelCaseName}}']; + } + {{/isSingleton}} + {{/isFormParameter}} + + {{#required}} + if(parameters['{{&camelCaseName}}'] === undefined) + throw new Error('Missing required {{¶mType}} parameter: {{&camelCaseName}}'); + {{/required}} + + {{/parameters}} + queryParameters = mergeQueryParams(parameters, queryParameters); + + return request('{{method}}', domain + path, parameters, body, headers, queryParameters, form); + }; diff --git a/templates/type.mustache b/templates/type.mustache new file mode 100644 index 0000000..fd7b915 --- /dev/null +++ b/templates/type.mustache @@ -0,0 +1,11 @@ +{{#tsType}} +{{! must use different delimiters to avoid ambiguities when delimiters directly follow a literal brace {. }} +{{=<% %>=}} +<%#isRef%><%target%><%/isRef%><%! +%><%#isAtomic%><%&tsType%><%/isAtomic%><%! +%><%#isObject%>{<%#properties%> +'<%name%>'<%#optional%>?<%/optional%>: <%>type%><%/properties%> +}<%/isObject%><%! +%><%#isArray%>Array<<%#elementType%><%>type%><%/elementType%>>|<%#elementType%><%>type%><%/elementType%><%/isArray%> +<%={{ }}=%> +{{/tsType}}