diff --git a/README.md b/README.md index 525e5b8f..b8430bec 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,27 @@ methods: type: boolean isFormParameter: type: boolean + response: + type: object + description: Description of the response +definitions: + type: array + description: Array with all Types defined in defintions + items: + type: object + properties: + tsType: + type: string + isRef: + type: boolean + isObject: + type: boolean + isArray: + type: boolean + isAtomic: + type: boolean + name: + type: string ``` ####Custom Mustache Variables diff --git a/lib/codegen.js b/lib/codegen.js index 3298fa6a..9c1f33ae 100644 --- a/lib/codegen.js +++ b/lib/codegen.js @@ -65,7 +65,8 @@ var getViewForSwagger2 = function(opts, type){ className: opts.className, imports: opts.imports, domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath : '', - methods: [] + methods: [], + definitions: [] }; _.forEach(swagger.paths, function(api, path){ @@ -92,7 +93,8 @@ var getViewForSwagger2 = function(opts, type){ summary: op.description, isSecure: swagger.security !== undefined || op.security !== undefined, parameters: [], - headers: [] + headers: [], + responseType: 'any' }; if(op.produces) { @@ -101,7 +103,7 @@ var getViewForSwagger2 = function(opts, type){ headers.name = 'Accept'; headers.value.push(op.produces.map(function(value) { return '\'' + value + '\''; }).join(', ')); - + method.headers.push(headers); } @@ -143,9 +145,23 @@ var getViewForSwagger2 = function(opts, type){ parameter.cardinality = parameter.required ? '' : '?'; method.parameters.push(parameter); }); + + // Parse responses information + var responseDesc = op.responses['200'] || op.responses.default; + method.responseType = ts.convertType(responseDesc); + data.methods.push(method); }); }); + + _.forEach(swagger.definitions, function(typeInfo, typeName) { + typeInfo.type = typeInfo.type || 'object'; + + var tsType = ts.convertType(typeInfo); + tsType.name = typeName; + data.definitions.push(tsType); + }); + return data; }; @@ -178,7 +194,7 @@ var getViewForSwagger1 = function(opts, type){ headers.name = 'Accept'; headers.value.push(op.produces.map(function(value) { return '\'' + value + '\''; }).join(', ')); - + method.headers.push(headers); } diff --git a/lib/typescript.js b/lib/typescript.js index 15e1c9ca..b76d1151 100644 --- a/lib/typescript.js +++ b/lib/typescript.js @@ -14,6 +14,9 @@ var _ = require('lodash'); function convertType(swaggerType) { var typespec = {}; + + // sanitize input + swaggerType = swaggerType || {}; if (swaggerType.hasOwnProperty('schema')) { return convertType(swaggerType.schema); @@ -50,7 +53,6 @@ function convertType(swaggerType) { typespec.isAtomic = _.contains(['string', 'number', 'boolean', 'any'], typespec.tsType); return typespec; - } module.exports.convertType = convertType; diff --git a/templates/typescript-class-with-definitions.mustache b/templates/typescript-class-with-definitions.mustache new file mode 100644 index 00000000..97c5c56a --- /dev/null +++ b/templates/typescript-class-with-definitions.mustache @@ -0,0 +1,28 @@ +{{#imports}} +/// +{{/imports}} + +import * as request from "superagent"; + +{{#definitions}} +interface {{name}} {{>type}} +{{/definitions}} + +/** + * {{&description}} + * @class {{&className}} + * @param {(string)} [domainOrOptions] - The project domain. + */ +export default class {{&className}} { + + private domain: string; + + constructor(domain: string) { + this.domain = domain; + } + +{{#methods}} + {{> method}} + +{{/methods}} +} diff --git a/templates/typescript-method-typed.mustache b/templates/typescript-method-typed.mustache new file mode 100644 index 00000000..6fb8996c --- /dev/null +++ b/templates/typescript-method-typed.mustache @@ -0,0 +1,111 @@ +/** +* {{&summary}} +* @method +* @name {{&className}}#{{&methodName}} +{{#parameters}} + {{^isSingleton}} * @param {{=<% %>=}}{<%&type%>}<%={{ }}=%> {{&camelCaseName}} - {{&description}}{{/isSingleton}} +{{/parameters}} +* +*/ +{{&methodName}}(parameters: { +{{#parameters}}{{^isSingleton}}'{{&camelCaseName}}'{{&cardinality}}: {{> type}}, +{{/isSingleton}}{{/parameters}} + $queryParameters?: {} +}): Promise< +{{#responseType}} +{{! must use different delimiters to avoid ambiguities when delimiters directly follow a literal brace {. }} + {{=<% %>=}} + <%#isRef%><%target%><%/isRef%><%! + %><%#isAtomic%><%tsType%><%/isAtomic%><%! + %><%#isObject%>{<%#properties%> + '<%name%>': <%>type%><%/properties%> + }<%/isObject%><%! + %><%#isArray%>Array<<%#elementType%><%>type%><%/elementType%>>|<%#elementType%><%>type%><%/elementType%><%/isArray%><%#isBoolean%>boolean<%/isBoolean%> + <%={{ }}=%> +{{/responseType}} +> { + let domain = this.domain; + let path = '{{&path}}'; + let body; + let queryParameters = {}; + let headers = {}; + let form = {}; + return new Promise(function(resolve, reject) { +{{#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}} + {{^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){ + reject(new Error('Missing required {{¶mType}} parameter: {{&camelCaseName}}')); + return; + } + {{/required}} + +{{/parameters}} + +if(parameters.$queryParameters) { + Object.keys(parameters.$queryParameters).forEach(function(parameterName){ + var parameter = parameters.$queryParameters[parameterName]; + queryParameters[parameterName] = parameter; + }); +} + +{{> request}} + + }); +}; diff --git a/templates/typescript-request-typed.mustache b/templates/typescript-request-typed.mustache new file mode 100644 index 00000000..c6754faf --- /dev/null +++ b/templates/typescript-request-typed.mustache @@ -0,0 +1,28 @@ +let req = request('{{method}}', domain + path) +.query(queryParameters); +Object.keys(headers).forEach(key => { + req.set(key, headers[key]); +}); + +if(body) { + req.send(body); +} + +if(typeof(body) === 'object' && !(body.constructor.name === 'Buffer')) { + req.set('Content-Type', 'application/json'); +} + +if(Object.keys(form).length > 0) { + req.type('form'); + req.send(form); +} + +req.end((error, response) => { + if(error) { + reject(error); + } else if(response.status >= 200 && response.status <= 299) { + resolve(response.body); + } else { + reject(response); + } +}); diff --git a/test.js b/test.js new file mode 100644 index 00000000..cbe9e4d1 --- /dev/null +++ b/test.js @@ -0,0 +1,15 @@ +var fs = require('fs'); +var CodeGen = require('./lib/codegen.js').CodeGen; + +var uberAPISwagger = JSON.parse(fs.readFileSync('./tests/apis/uber.json')); + +var opts = { swagger:uberAPISwagger, template: {} }; + +// var templateDirecotry = './templates/'; + +opts.template.class = fs.readFileSync('./templates/typescript-class-with-definitions.mustache', 'utf-8'); +opts.template.method = fs.readFileSync('./templates/typescript-method-typed.mustache', 'utf-8'); +opts.template.request = fs.readFileSync('./templates/typescript-request-typed.mustache', 'utf-8'); + +var result = CodeGen.getTypescriptCode(opts); +console.log(result); \ No newline at end of file