├── .gitignore ├── .jshintrc ├── .npmignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bin └── swagger2js.js ├── circle.yml ├── lib ├── cli.js ├── codegen.js ├── flow.js └── typescript.js ├── package-lock.json ├── package.json ├── templates ├── angular-class.mustache ├── flow-class.mustache ├── flow-method.mustache ├── flow-type.mustache ├── method.mustache ├── node-class.mustache ├── react-class.mustache ├── type.mustache ├── typescript-class.mustache └── typescript-method.mustache ├── tests ├── apis │ ├── _batch.json │ ├── _datasources.json │ ├── _jobs.json │ ├── _modules.json │ ├── _queries.json │ ├── account.json │ ├── auth.json │ ├── package.json │ ├── ping.json │ ├── project.json │ ├── protected.json │ ├── queries.json │ ├── ref.json │ ├── test.json │ ├── uber.json │ └── users.json └── generation.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.iml 4 | wks.* 5 | .DS_Store 6 | *.swp 7 | 8 | tmp-* 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": false, 4 | "esnext": true, 5 | "bitwise": false, 6 | "camelcase": false, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": false, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | test/ 3 | .DS_Store 4 | *.swp 5 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 'use strict'; 3 | 4 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 5 | // Load local tasks. 6 | //grunt.loadTasks('tasks'); 7 | 8 | // Project configuration. 9 | grunt.initConfig({ 10 | jshint: { 11 | all: ['Gruntfile.js', 'lib/**/*.js', 'tests/**/*.js'], 12 | options: { 13 | jshintrc: '.jshintrc' 14 | } 15 | }, 16 | vows: { 17 | all: { 18 | options: { 19 | verbose: true, 20 | colors: true, 21 | coverage: 'json' 22 | }, 23 | // String or array of strings 24 | // determining which files to include. 25 | // This option is grunt's "full" file format. 26 | src: ['tests/*.js'] 27 | } 28 | }, 29 | jsonlint: { 30 | all: { 31 | src: ['package.json', 'tests/apis/*.json', '.jshintrc'] 32 | } 33 | } 34 | }); 35 | 36 | // Default task. 37 | grunt.registerTask('default', ['jsonlint', 'jshint', 'vows']); 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger to JS & Typescript Codegen 2 | [![Circle CI](https://circleci.com/gh/wcandillon/swagger-js-codegen.svg?style=svg)](https://circleci.com/gh/wcandillon/swagger-js-codegen) [![NPM version](http://img.shields.io/npm/v/swagger-js-codegen.svg?style=flat)](http://badge.fury.io/js/swagger-js-codegen) 3 | 4 | ## We are looking for a new maintainer 5 | 6 | This project is no longer actively maintained by its creator. Please let us know if you would like to become a maintainer. 7 | At the time we wrote this package, the swagger didn't have generators for JavaScript nor TypeScript. Now there are [great alternatives of this package available](https://github.com/swagger-api/swagger-codegen). 8 | 9 | This package generates a nodejs, reactjs or angularjs class from a [swagger specification file](https://github.com/wordnik/swagger-spec). The code is generated using [mustache templates](https://github.com/wcandillon/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). 10 | 11 | The typescript generator is based on [superagent](https://github.com/visionmedia/superagent) and can be used for both nodejs and the browser via browserify/webpack. 12 | 13 | ## Installation 14 | ```bash 15 | npm install swagger-js-codegen 16 | ``` 17 | 18 | ## Example 19 | ```javascript 20 | var fs = require('fs'); 21 | var CodeGen = require('swagger-js-codegen').CodeGen; 22 | 23 | var file = 'swagger/spec.json'; 24 | var swagger = JSON.parse(fs.readFileSync(file, 'UTF-8')); 25 | var nodejsSourceCode = CodeGen.getNodeCode({ className: 'Test', swagger: swagger }); 26 | var angularjsSourceCode = CodeGen.getAngularCode({ className: 'Test', swagger: swagger }); 27 | var reactjsSourceCode = CodeGen.getReactCode({ className: 'Test', swagger: swagger }); 28 | var tsSourceCode = CodeGen.getTypescriptCode({ className: 'Test', swagger: swagger, imports: ['../../typings/tsd.d.ts'] }); 29 | console.log(nodejsSourceCode); 30 | console.log(angularjsSourceCode); 31 | console.log(reactjsSourceCode); 32 | console.log(tsSourceCode); 33 | ``` 34 | 35 | ## Custom template 36 | ```javascript 37 | var source = CodeGen.getCustomCode({ 38 | moduleName: 'Test', 39 | className: 'Test', 40 | swagger: swaggerSpec, 41 | template: { 42 | class: fs.readFileSync('my-class.mustache', 'utf-8'), 43 | method: fs.readFileSync('my-method.mustache', 'utf-8'), 44 | type: fs.readFileSync('my-type.mustache', 'utf-8') 45 | } 46 | }); 47 | ``` 48 | 49 | ## Options 50 | In addition to the common options listed below, `getCustomCode()` *requires* a `template` field: 51 | 52 | template: { class: "...", method: "..." } 53 | 54 | `getAngularCode()`, `getNodeCode()`, and `getCustomCode()` each support the following options: 55 | 56 | ```yaml 57 | moduleName: 58 | type: string 59 | description: Your AngularJS module name 60 | className: 61 | type: string 62 | lint: 63 | type: boolean 64 | description: whether or not to run jslint on the generated code 65 | esnext: 66 | type: boolean 67 | description: passed through to jslint 68 | beautify: 69 | type: boolean 70 | description: whether or not to beautify the generated code 71 | mustache: 72 | type: object 73 | description: See the 'Custom Mustache Variables' section below 74 | imports: 75 | type: array 76 | description: Typescript definition files to be imported. 77 | swagger: 78 | type: object 79 | required: true 80 | description: swagger object 81 | ``` 82 | 83 | ### Template Variables 84 | The following data are passed to the [mustache templates](https://github.com/janl/mustache.js): 85 | 86 | ```yaml 87 | isNode: 88 | type: boolean 89 | isES6: 90 | type: boolean 91 | description: 92 | type: string 93 | description: Provided by your options field: 'swagger.info.description' 94 | isSecure: 95 | type: boolean 96 | description: false unless 'swagger.securityDefinitions' is defined 97 | moduleName: 98 | type: string 99 | description: Your AngularJS module name - provided by your options field 100 | className: 101 | type: string 102 | description: Provided by your options field 103 | domain: 104 | type: string 105 | description: If all options defined: swagger.schemes[0] + '://' + swagger.host + swagger.basePath 106 | methods: 107 | type: array 108 | items: 109 | type: object 110 | properties: 111 | path: 112 | type: string 113 | className: 114 | type: string 115 | description: Provided by your options field 116 | methodName: 117 | type: string 118 | description: Generated from the HTTP method and path elements or 'x-swagger-js-method-name' field 119 | method: 120 | type: string 121 | description: 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND' 122 | enum: 123 | - GET 124 | - POST 125 | - PUT 126 | - DELETE 127 | - PATCH 128 | - COPY 129 | - HEAD 130 | - OPTIONS 131 | - LINK 132 | - UNLIK 133 | - PURGE 134 | - LOCK 135 | - UNLOCK 136 | - PROPFIND 137 | isGET: 138 | type: string 139 | description: true if method === 'GET' 140 | summary: 141 | type: string 142 | description: Provided by the 'description' or 'summary' field in the schema 143 | externalDocs: 144 | type: object 145 | properties: 146 | url: 147 | type: string 148 | description: The URL for the target documentation. Value MUST be in the format of a URL. 149 | required: true 150 | description: 151 | type: string 152 | description: A short description of the target documentation. GitHub-Markdown syntax can be used for rich text representation. 153 | isSecure: 154 | type: boolean 155 | description: true if the 'security' is defined for the method in the schema 156 | parameters: 157 | type: array 158 | description: Includes all of the properties defined for the parameter in the schema plus: 159 | items: 160 | camelCaseName: 161 | type: string 162 | isSingleton: 163 | type: boolean 164 | description: true if there was only one 'enum' defined for the parameter 165 | singleton: 166 | type: string 167 | description: the one and only 'enum' defined for the parameter (if there is only one) 168 | isBodyParameter: 169 | type: boolean 170 | isPathParameter: 171 | type: boolean 172 | isQueryParameter: 173 | type: boolean 174 | isPatternType: 175 | type: boolean 176 | description: true if *in* is 'query', and 'pattern' is defined 177 | isHeaderParameter: 178 | type: boolean 179 | isFormParameter: 180 | type: boolean 181 | ``` 182 | 183 | #### Custom Mustache Variables 184 | You can also pass in your own variables for the mustache templates by adding a `mustache` object: 185 | 186 | ```javascript 187 | var source = CodeGen.getCustomCode({ 188 | ... 189 | mustache: { 190 | foo: 'bar', 191 | app_build_id: env.BUILD_ID, 192 | app_version: pkg.version 193 | } 194 | }); 195 | ``` 196 | 197 | ## Swagger Extensions 198 | 199 | ### x-proxy-header 200 | Some proxies and application servers inject HTTP headers into the requests. Server-side code 201 | may use these fields, but they are not required in the client API. 202 | 203 | eg: https://cloud.google.com/appengine/docs/go/requests#Go_Request_headers 204 | 205 | ```yaml 206 | /locations: 207 | get: 208 | parameters: 209 | - name: X-AppEngine-Country 210 | in: header 211 | x-proxy-header: true 212 | type: string 213 | description: Provided by AppEngine eg - US, AU, GB 214 | - name: country 215 | in: query 216 | type: string 217 | description: | 218 | 2 character country code. 219 | If not specified, will default to the country provided in the X-AppEngine-Country header 220 | ... 221 | ``` 222 | 223 | 224 | ## Grunt task 225 | [There is a grunt task](https://github.com/wcandillon/grunt-swagger-js-codegen) that enables you to integrate the code generation in your development pipeline. This is extremely convenient if your application is using APIs which are documented/specified in the swagger format. 226 | 227 | ## Who is using it? 228 | [28.io](http://28.io) is using this project to generate their [nodejs](https://github.com/28msec/28.io-nodejs) and [angularjs language bindings](https://github.com/28msec/28.io-angularjs). 229 | -------------------------------------------------------------------------------- /bin/swagger2js.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); 5 | var updateNotifier = require('update-notifier'); 6 | 7 | //1. Update Notifier 8 | var pkg = require('../package.json'); 9 | updateNotifier({packageName: pkg.name, packageVersion: pkg.version}).notify(); 10 | 11 | //4. CLI Script 12 | require(lib + '/cli.js'); -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 5.7.0 4 | dependencies: 5 | post: 6 | - npm install grunt-cli -g 7 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const pkg = require('../package.json'); 5 | const cli = require('commander'); 6 | const yaml = require('js-yaml').safeLoad; 7 | const CodeGen = require('./codegen').CodeGen; 8 | 9 | cli 10 | .version(pkg.version) 11 | .command('generate [imports...]') 12 | .alias('gen') 13 | .description('Generate from Swagger file') 14 | .option('-t, --type ', 'Code type [typescript]', /^(typescript|angular|node|react|flow)$/i, 'typescript') 15 | .option('-m, --module ', 'Your AngularJS module name [Test]', 'Test') 16 | .option('-c, --class ', 'Class name [Test]', 'Test') 17 | .option('-l, --lint', 'Whether or not to run jslint on the generated code [false]') 18 | .option('-b, --beautify', 'Whether or not to beautify the generated code [false]') 19 | .action((file, imports, options) => { 20 | const fnName = 'get' + options.type.charAt(0).toUpperCase() + options.type.substr(1) + 'Code'; 21 | const fn = CodeGen[fnName]; 22 | options.lint = options.lint || false; 23 | options.beautify = options.beautify || false; 24 | 25 | const content = fs.readFileSync(file, 'utf-8'); 26 | 27 | var swagger; 28 | try { 29 | swagger = JSON.parse(content); 30 | } catch (e) { 31 | swagger = yaml(content); 32 | } 33 | 34 | const result = fn({ 35 | moduleName: options.module, 36 | className: options.class, 37 | swagger: swagger, 38 | lint: options.lint, 39 | beautify: options.beautify 40 | }); 41 | 42 | console.log(result); 43 | }); 44 | 45 | cli.parse(process.argv); 46 | 47 | if (!cli.args.length) { 48 | cli.help(); 49 | } 50 | -------------------------------------------------------------------------------- /lib/codegen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var Mustache = require('mustache'); 5 | var beautify = require('js-beautify').js_beautify; 6 | var lint = require('jshint').JSHINT; 7 | var _ = require('lodash'); 8 | var ts = require('./typescript'); 9 | var flow = require('./flow'); 10 | 11 | var normalizeName = function(id) { 12 | return id.replace(/\.|\-|\{|\}|\s/g, '_'); 13 | }; 14 | 15 | var getPathToMethodName = function(opts, m, path){ 16 | if(path === '/' || path === '') { 17 | return m; 18 | } 19 | 20 | // clean url path for requests ending with '/' 21 | var cleanPath = path.replace(/\/$/, ''); 22 | 23 | var segments = cleanPath.split('/').slice(1); 24 | segments = _.transform(segments, function (result, segment) { 25 | if (segment[0] === '{' && segment[segment.length - 1] === '}') { 26 | segment = 'by' + segment[1].toUpperCase() + segment.substring(2, segment.length - 1); 27 | } 28 | result.push(segment); 29 | }); 30 | var result = _.camelCase(segments.join('-')); 31 | return m.toLowerCase() + result[0].toUpperCase() + result.substring(1); 32 | }; 33 | 34 | var getViewForSwagger2 = function(opts, type){ 35 | var swagger = opts.swagger; 36 | var methods = []; 37 | var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND']; 38 | var data = { 39 | isNode: type === 'node' || type === 'react', 40 | isES6: opts.isES6 || type === 'react', 41 | description: swagger.info.description, 42 | isSecure: swagger.securityDefinitions !== undefined, 43 | moduleName: opts.moduleName, 44 | className: opts.className, 45 | imports: opts.imports, 46 | domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath.replace(/\/+$/g,'') : '', 47 | methods: [], 48 | definitions: [] 49 | }; 50 | 51 | _.forEach(swagger.paths, function(api, path){ 52 | var globalParams = []; 53 | /** 54 | * @param {Object} op - meta data for the request 55 | * @param {string} m - HTTP method name - eg: 'get', 'post', 'put', 'delete' 56 | */ 57 | _.forEach(api, function(op, m){ 58 | if(m.toLowerCase() === 'parameters') { 59 | globalParams = op; 60 | } 61 | }); 62 | _.forEach(api, function(op, m){ 63 | var M = m.toUpperCase(); 64 | if(M === '' || authorizedMethods.indexOf(M) === -1) { 65 | return; 66 | } 67 | var secureTypes = []; 68 | if(swagger.securityDefinitions !== undefined || op.security !== undefined) { 69 | var mergedSecurity = _.merge([], swagger.security, op.security).map(function(security){ 70 | return Object.keys(security); 71 | }); 72 | if(swagger.securityDefinitions) { 73 | for(var sk in swagger.securityDefinitions) { 74 | if(mergedSecurity.join(',').indexOf(sk) !== -1){ 75 | secureTypes.push(swagger.securityDefinitions[sk].type); 76 | } 77 | } 78 | } 79 | } 80 | var methodName = (op.operationId ? normalizeName(op.operationId) : getPathToMethodName(opts, m, path)); 81 | // Make sure the method name is unique 82 | if(methods.indexOf(methodName) !== -1) { 83 | var i = 1; 84 | while(true) { 85 | if(methods.indexOf(methodName + '_' + i) !== -1) { 86 | i++; 87 | } else { 88 | methodName = methodName + '_' + i; 89 | break; 90 | } 91 | } 92 | } 93 | methods.push(methodName); 94 | 95 | var method = { 96 | path: path, 97 | className: opts.className, 98 | methodName: methodName, 99 | method: M, 100 | isGET: M === 'GET', 101 | isPOST: M === 'POST', 102 | summary: op.description || op.summary, 103 | externalDocs: op.externalDocs, 104 | isSecure: swagger.security !== undefined || op.security !== undefined, 105 | isSecureToken: secureTypes.indexOf('oauth2') !== -1, 106 | isSecureApiKey: secureTypes.indexOf('apiKey') !== -1, 107 | isSecureBasic: secureTypes.indexOf('basic') !== -1, 108 | parameters: [], 109 | hasParameters: false, 110 | headers: [] 111 | }; 112 | if(method.isSecure && method.isSecureToken) { 113 | data.isSecureToken = method.isSecureToken; 114 | } 115 | if(method.isSecure && method.isSecureApiKey) { 116 | data.isSecureApiKey = method.isSecureApiKey; 117 | } 118 | if(method.isSecure && method.isSecureBasic) { 119 | data.isSecureBasic = method.isSecureBasic; 120 | } 121 | var produces = op.produces || swagger.produces; 122 | if(produces) { 123 | method.headers.push({ 124 | name: 'Accept', 125 | value: `'${produces.map(function(value) { return value; }).join(', ')}'`, 126 | }); 127 | } 128 | 129 | var consumes = op.consumes || swagger.consumes; 130 | if(consumes) { 131 | method.headers.push({name: 'Content-Type', value: '\'' + consumes + '\'' }); 132 | } 133 | 134 | var params = []; 135 | if(_.isArray(op.parameters)) { 136 | params = op.parameters; 137 | } 138 | params = params.concat(globalParams); 139 | _.forEach(params, function(parameter) { 140 | //Ignore parameters which contain the x-exclude-from-bindings extension 141 | if(parameter['x-exclude-from-bindings'] === true) { 142 | return; 143 | } 144 | 145 | // Ignore headers which are injected by proxies & app servers 146 | // eg: https://cloud.google.com/appengine/docs/go/requests#Go_Request_headers 147 | if (parameter['x-proxy-header'] && !data.isNode) { 148 | return; 149 | } 150 | if (_.isString(parameter.$ref)) { 151 | var segments = parameter.$ref.split('/'); 152 | parameter = swagger.parameters[segments.length === 1 ? segments[0] : segments[2] ]; 153 | } 154 | parameter.camelCaseName = _.camelCase(parameter.name); 155 | if(parameter.enum && parameter.enum.length === 1) { 156 | parameter.isSingleton = true; 157 | parameter.singleton = parameter.enum[0]; 158 | } 159 | if(parameter.in === 'body'){ 160 | parameter.isBodyParameter = true; 161 | } else if(parameter.in === 'path'){ 162 | parameter.isPathParameter = true; 163 | } else if(parameter.in === 'query'){ 164 | if(parameter['x-name-pattern']){ 165 | parameter.isPatternType = true; 166 | parameter.pattern = parameter['x-name-pattern']; 167 | } 168 | parameter.isQueryParameter = true; 169 | } else if(parameter.in === 'header'){ 170 | parameter.isHeaderParameter = true; 171 | } else if(parameter.in === 'formData'){ 172 | parameter.isFormParameter = true; 173 | } 174 | parameter.tsType = ts.convertType(parameter); 175 | parameter.flowType = flow.convertType(parameter); 176 | parameter.cardinality = parameter.required ? '' : '?'; 177 | method.parameters.push(parameter); 178 | }); 179 | 180 | var success = 0; 181 | method.isInlineType = false; 182 | _.forEach(op.responses, function(val, key) { 183 | if (key.startsWith('2')) { 184 | if (val.schema) { 185 | method.methodTsType = ts.convertType(val.schema); 186 | method.methodFlowType = flow.convertType(val.schema); 187 | method.isInlineType = true; 188 | } else { 189 | method.methodResponse = val.description; 190 | } 191 | } 192 | }); 193 | method.hasParameters = method.parameters.length > 0; 194 | data.methods.push(method); 195 | }); 196 | }); 197 | 198 | _.forEach(swagger.definitions, function(definition, name){ 199 | data.definitions.push({ 200 | name: type === 'flow' ? flow.sanitizeReservedWords(name) : name, 201 | description: definition.description, 202 | flowType: flow.convertType(definition, swagger), 203 | tsType: ts.convertType(definition, swagger) 204 | }); 205 | }); 206 | 207 | return data; 208 | }; 209 | 210 | var getViewForSwagger1 = function(opts, type){ 211 | var swagger = opts.swagger; 212 | var data = { 213 | isNode: type === 'node' || type === 'react', 214 | isES6: opts.isES6 || type === 'react', 215 | description: swagger.description, 216 | moduleName: opts.moduleName, 217 | className: opts.className, 218 | domain: swagger.basePath ? swagger.basePath : '', 219 | methods: [] 220 | }; 221 | swagger.apis.forEach(function(api){ 222 | api.operations.forEach(function(op){ 223 | if (op.method === 'OPTIONS') { 224 | return; 225 | } 226 | var method = { 227 | path: api.path, 228 | className: opts.className, 229 | methodName: op.nickname, 230 | method: op.method, 231 | isGET: op.method === 'GET', 232 | isPOST: op.method.toUpperCase() === 'POST', 233 | summary: op.summary, 234 | parameters: op.parameters, 235 | headers: [] 236 | }; 237 | 238 | if(op.produces) { 239 | var headers = []; 240 | headers.value = []; 241 | headers.name = 'Accept'; 242 | headers.value.push(op.produces.map(function(value) { return '\'' + value + '\''; }).join(', ')); 243 | method.headers.push(headers); 244 | } 245 | 246 | op.parameters = op.parameters ? op.parameters : []; 247 | op.parameters.forEach(function(parameter) { 248 | parameter.camelCaseName = _.camelCase(parameter.name); 249 | if(parameter.enum && parameter.enum.length === 1) { 250 | parameter.isSingleton = true; 251 | parameter.singleton = parameter.enum[0]; 252 | } 253 | if(parameter.paramType === 'body'){ 254 | parameter.isBodyParameter = true; 255 | } else if(parameter.paramType === 'path'){ 256 | parameter.isPathParameter = true; 257 | } else if(parameter.paramType === 'query'){ 258 | if(parameter['x-name-pattern']){ 259 | parameter.isPatternType = true; 260 | parameter.pattern = parameter['x-name-pattern']; 261 | } 262 | parameter.isQueryParameter = true; 263 | } else if(parameter.paramType === 'header'){ 264 | parameter.isHeaderParameter = true; 265 | } else if(parameter.paramType === 'form'){ 266 | parameter.isFormParameter = true; 267 | } 268 | }); 269 | data.methods.push(method); 270 | }); 271 | }); 272 | return data; 273 | }; 274 | 275 | var getCode = function(opts, type) { 276 | // For Swagger Specification version 2.0 value of field 'swagger' must be a string '2.0' 277 | var data = opts.swagger.swagger === '2.0' ? getViewForSwagger2(opts, type) : getViewForSwagger1(opts, type); 278 | if (type === 'custom') { 279 | if (!_.isObject(opts.template) || !_.isString(opts.template.class) || !_.isString(opts.template.method)) { 280 | throw new Error('Unprovided custom template. Please use the following template: template: { class: "...", method: "...", request: "..." }'); 281 | } 282 | } else { 283 | if (!_.isObject(opts.template)) { 284 | opts.template = {}; 285 | } 286 | var templates = __dirname + '/../templates/'; 287 | opts.template.class = opts.template.class || fs.readFileSync(templates + type + '-class.mustache', 'utf-8'); 288 | opts.template.method = opts.template.method || fs.readFileSync(templates + (_.includes(['flow', 'typescript'], type) ? type + '-' : '') + 'method.mustache', 'utf-8'); 289 | if(type === 'typescript') { 290 | opts.template.type = opts.template.type || fs.readFileSync(templates + 'type.mustache', 'utf-8'); 291 | } else if (type === 'flow') { 292 | opts.template.type = opts.template.type || fs.readFileSync(templates + 'flow-type.mustache', 'utf-8'); 293 | } 294 | } 295 | 296 | if (opts.mustache) { 297 | _.assign(data, opts.mustache); 298 | } 299 | 300 | var source = Mustache.render(opts.template.class, data, opts.template); 301 | var lintOptions = { 302 | node: type === 'node' || type === 'custom', 303 | browser: type === 'angular' || type === 'custom' || type === 'react', 304 | undef: true, 305 | strict: true, 306 | trailing: true, 307 | smarttabs: true, 308 | maxerr: 999 309 | }; 310 | if (opts.esnext) { 311 | lintOptions.esnext = true; 312 | } 313 | 314 | if(type === 'typescript' || type === 'flow') { 315 | opts.lint = false; 316 | } 317 | 318 | if (opts.lint === undefined || opts.lint === true) { 319 | lint(source, lintOptions); 320 | lint.errors.forEach(function(error) { 321 | if (error.code[0] === 'E') { 322 | throw new Error(error.reason + ' in ' + error.evidence + ' (' + error.code + ')'); 323 | } 324 | }); 325 | } 326 | if (opts.beautify === undefined || opts.beautify === true) { 327 | return beautify(source, { indent_size: 4, max_preserve_newlines: 2 }); 328 | } else { 329 | return source; 330 | } 331 | }; 332 | 333 | exports.CodeGen = { 334 | getTypescriptCode: function(opts){ 335 | if (opts.swagger.swagger !== '2.0') { 336 | throw 'Typescript is only supported for Swagger 2.0 specs.'; 337 | } 338 | return getCode(opts, 'typescript'); 339 | }, 340 | getAngularCode: function(opts){ 341 | return getCode(opts, 'angular'); 342 | }, 343 | getNodeCode: function(opts){ 344 | return getCode(opts, 'node'); 345 | }, 346 | getReactCode: function(opts){ 347 | return getCode(opts, 'react'); 348 | }, 349 | getFlowCode: function(opts) { 350 | return getCode(opts, 'flow'); 351 | }, 352 | getCustomCode: function(opts){ 353 | return getCode(opts, 'custom'); 354 | } 355 | }; 356 | -------------------------------------------------------------------------------- /lib/flow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Recursively converts a swagger type description into a flow type, i.e., a model for our mustache 7 | * template. 8 | * 9 | * Not all types are currently supported, but they should be straightforward to add. 10 | * 11 | * @param swaggerType a swagger type definition, i.e., the right hand side of a swagger type definition. 12 | * @returns a recursive structure representing the type, which can be used as a template model. 13 | */ 14 | 15 | var reservedWords = [ 16 | "abstract","arguments","await","boolean","break","byte","case","catch","char", 17 | "class","const","continue","debugger","default","delete","do","double","else", 18 | "enum","eval","export","extends","false","final","finally","float","for", 19 | "function","goto","if","implements","import","in","instanceof","int","interface", 20 | "let","long","native","new","null","package","private","protected","public","return", 21 | "short","static","super","switch","synchronized","this","throw","throws","transient", 22 | "true","try","typeof","var","void","volatile","while","with","yield" 23 | ]; 24 | 25 | function sanitizeReservedWords(word) { 26 | if (_.includes(reservedWords, word)) { 27 | return word + '_type'; 28 | } 29 | return word; 30 | } 31 | 32 | function getNameFromRef(ref) { 33 | return sanitizeReservedWords(ref.substring(ref.lastIndexOf('/') + 1)); 34 | } 35 | 36 | function convertType(swaggerType, swagger) { 37 | var typespec = { 38 | description: swaggerType.description, 39 | simpleFlowType: undefined, 40 | isObject: false, 41 | isArray: false, 42 | }; 43 | 44 | if (swaggerType.hasOwnProperty('schema')) { 45 | return convertType(swaggerType.schema); 46 | } 47 | 48 | if (_.isString(swaggerType.$ref)) { 49 | typespec.simpleFlowType = getNameFromRef(swaggerType.$ref); 50 | } else if (swaggerType.hasOwnProperty('enum')) { 51 | typespec.simpleFlowType = swaggerType.enum.map(JSON.stringify).join(' | '); 52 | } else if (swaggerType.type === 'string') { 53 | typespec.simpleFlowType = 'string'; 54 | } else if (swaggerType.type === 'number' || swaggerType.type === 'integer') { 55 | typespec.simpleFlowType = 'number'; 56 | } else if (swaggerType.type === 'boolean') { 57 | typespec.simpleFlowType = 'boolean'; 58 | } else if (swaggerType.type === 'array') { 59 | typespec.isArray = true; 60 | typespec.elementType = convertType(swaggerType.items); 61 | } else if (swaggerType.hasOwnProperty('additionalProperties')) { 62 | typespec.isObject = true; 63 | typespec.properties = [convertType(swaggerType.additionalProperties)]; 64 | typespec.properties[0].name = '[string]'; 65 | typespec.properties[0].optional = false; 66 | } else { // remaining types are created as objects 67 | if (swaggerType.minItems >= 0 && swaggerType.hasOwnProperty('title') && !swaggerType.$ref) { 68 | typespec.simpleFlowType = 'any'; 69 | } else { 70 | typespec.isObject = true; 71 | typespec.properties = []; 72 | if (swaggerType.allOf) { 73 | _.forEach(swaggerType.allOf, function (ref) { 74 | if(ref.$ref) { 75 | var name = getNameFromRef(ref.$ref); 76 | _.forEach(swagger.definitions, function (definition, definitionName) { 77 | if (definitionName === name) { 78 | var property = convertType(definition, swagger); 79 | Array.prototype.push.apply(typespec.properties, property.properties); 80 | } 81 | }); 82 | } else { 83 | var property = convertType(ref); 84 | Array.prototype.push.apply(typespec.properties, property.properties); 85 | } 86 | }); 87 | } 88 | 89 | _.forEach(swaggerType.properties, function (propertyType, propertyName) { 90 | var property = convertType(propertyType); 91 | property.name = propertyName; 92 | 93 | property.optional = true; 94 | if (swaggerType.required && swaggerType.required.indexOf(propertyName) !== -1) { 95 | property.optional = false; 96 | } 97 | 98 | typespec.properties.push(property); 99 | }); 100 | } 101 | } 102 | 103 | return typespec; 104 | } 105 | 106 | module.exports = { 107 | convertType: convertType, 108 | sanitizeReservedWords: sanitizeReservedWords, 109 | }; 110 | -------------------------------------------------------------------------------- /lib/typescript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Recursively converts a swagger type description into a typescript type, i.e., a model for our mustache 7 | * template. 8 | * 9 | * Not all type are currently supported, but they should be straightforward to add. 10 | * 11 | * @param swaggerType a swagger type definition, i.e., the right hand side of a swagger type definition. 12 | * @returns a recursive structure representing the type, which can be used as a template model. 13 | */ 14 | function convertType(swaggerType, swagger) { 15 | 16 | var typespec = { description: swaggerType.description, isEnum: false }; 17 | 18 | if (swaggerType.hasOwnProperty('schema')) { 19 | return convertType(swaggerType.schema); 20 | } else if (_.isString(swaggerType.$ref)) { 21 | typespec.tsType = 'ref'; 22 | typespec.target = swaggerType.$ref.substring(swaggerType.$ref.lastIndexOf('/') + 1); 23 | } else if (swaggerType.hasOwnProperty('enum')) { 24 | typespec.tsType = swaggerType.enum.map(function(str) { return JSON.stringify(str); }).join(' | '); 25 | typespec.isAtomic = true; 26 | typespec.isEnum = true; 27 | } else if (swaggerType.type === 'string') { 28 | typespec.tsType = 'string'; 29 | } else if (swaggerType.type === 'number' || swaggerType.type === 'integer') { 30 | typespec.tsType = 'number'; 31 | } else if (swaggerType.type === 'boolean') { 32 | typespec.tsType = 'boolean'; 33 | } else if (swaggerType.type === 'array') { 34 | typespec.tsType = 'array'; 35 | typespec.elementType = convertType(swaggerType.items); 36 | } else /*if (swaggerType.type === 'object')*/ { //remaining types are created as objects 37 | if (swaggerType.minItems >= 0 && swaggerType.hasOwnProperty('title') && !swaggerType.$ref) { 38 | typespec.tsType = 'any'; 39 | } 40 | else { 41 | typespec.tsType = 'object'; 42 | typespec.properties = []; 43 | if (swaggerType.allOf) { 44 | _.forEach(swaggerType.allOf, function (ref) { 45 | if(ref.$ref) { 46 | var refSegments = ref.$ref.split('/'); 47 | var name = refSegments[refSegments.length - 1]; 48 | _.forEach(swagger.definitions, function (definition, definitionName) { 49 | if (definitionName === name) { 50 | var property = convertType(definition, swagger); 51 | Array.prototype.push.apply(typespec.properties, property.properties); 52 | } 53 | }); 54 | } else { 55 | var property = convertType(ref); 56 | Array.prototype.push.apply(typespec.properties, property.properties); 57 | } 58 | }); 59 | } 60 | 61 | _.forEach(swaggerType.properties, function (propertyType, propertyName) { 62 | var property = convertType(propertyType); 63 | property.name = propertyName; 64 | 65 | property.optional = true; 66 | if (swaggerType.required && swaggerType.required.indexOf(propertyName) !== -1) { 67 | property.optional = false; 68 | } 69 | 70 | typespec.properties.push(property); 71 | }); 72 | } 73 | } /*else { 74 | // type unknown or unsupported... just map to 'any'... 75 | typespec.tsType = 'any'; 76 | }*/ 77 | 78 | // Since Mustache does not provide equality checks, we need to do the case distinction via explicit booleans 79 | typespec.isRef = typespec.tsType === 'ref'; 80 | typespec.isObject = typespec.tsType === 'object'; 81 | typespec.isArray = typespec.tsType === 'array'; 82 | typespec.isAtomic = typespec.isAtomic || _.includes(['string', 'number', 'boolean', 'any'], typespec.tsType); 83 | 84 | return typespec; 85 | } 86 | 87 | module.exports.convertType = convertType; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-js-codegen", 3 | "main": "./lib/codegen.js", 4 | "version": "1.13.0", 5 | "description": "A Swagger codegen for JavaScript", 6 | "scripts": { 7 | "test": "grunt", 8 | "clean": "rm -rf tmp-*" 9 | }, 10 | "bin": { 11 | "swagger2js": "bin/swagger2js.js" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/wcandillon/swagger-js-codegen/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/wcandillon/swagger-js-codegen.git" 19 | }, 20 | "keywords": [ 21 | "swagger", 22 | "rest" 23 | ], 24 | "author": { 25 | "name": "William Candillon", 26 | "email": "w@28.io", 27 | "url": "http://28.io" 28 | }, 29 | "license": "Apache-2.0", 30 | "homepage": "https://github.com/wcandillon/swagger-js-codegen", 31 | "dependencies": { 32 | "commander": "^2.9.0", 33 | "js-beautify": "^1.5.1", 34 | "js-yaml": "^3.10.0", 35 | "jshint": "^2.5.1", 36 | "lodash": "^4.17.10", 37 | "mustache": "2.2.1", 38 | "update-notifier": "^2.1.0" 39 | }, 40 | "devDependencies": { 41 | "final-fs": "^1.6.0", 42 | "grunt": "^1.0.3", 43 | "grunt-contrib-jshint": "^1.1.0", 44 | "grunt-jsonlint": "^1.0.4", 45 | "grunt-vows": "^0.4.1", 46 | "matchdep": "^1.0.1 ", 47 | "q": "^1.0.1", 48 | "request": "^2.87.0", 49 | "superagent": "^3.3.1", 50 | "tmp": "0.0.31", 51 | "typescript": "^2.1.4", 52 | "vows": "^0.8.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /templates/angular-class.mustache: -------------------------------------------------------------------------------- 1 | /*jshint -W069 */ 2 | /*global angular:false, btoa */ 3 | angular.module('{{&moduleName}}', []) 4 | .factory('{{&className}}', ['$q', '$http', '$rootScope', function($q, $http, $rootScope){ 5 | 'use strict'; 6 | 7 | /** 8 | * {{&description}} 9 | * @class {{&className}} 10 | * @param {(string|object)} [domainOrOptions] - The project domain or options object. If object, see the object's optional properties. 11 | * @param {string} [domainOrOptions.domain] - The project domain 12 | * @param {string} [domainOrOptions.cache] - An angularjs cache implementation 13 | * @param {object} [domainOrOptions.token] - auth token - object with value property and optional headerOrQueryName and isQuery properties 14 | * @param {string} [cache] - An angularjs cache implementation 15 | */ 16 | var {{&className}} = (function(){ 17 | function {{&className}}(options, cache){ 18 | var domain = (typeof options === 'object') ? options.domain : options; 19 | this.domain = typeof(domain) === 'string' ? domain : '{{&domain}}'; 20 | if(this.domain.length === 0) { 21 | throw new Error('Domain parameter must be specified as a string.'); 22 | } 23 | cache = cache || ((typeof options === 'object') ? options.cache : cache); 24 | this.cache = cache; 25 | {{#isSecure}} 26 | {{#isSecureToken}} 27 | this.token = (typeof options === 'object') ? (options.token ? options.token : {}) : {}; 28 | {{/isSecureToken}} 29 | {{#isSecureApiKey}} 30 | this.apiKey = (typeof options === 'object') ? (options.apiKey ? options.apiKey : {}) : {}; 31 | {{/isSecureApiKey}} 32 | {{#isSecureBasic}} 33 | this.basic = (typeof options === 'object') ? (options.basic ? options.basic : {}) : {}; 34 | {{/isSecureBasic}} 35 | {{/isSecure}} 36 | } 37 | 38 | function mergeQueryParams(parameters, queryParameters) { 39 | if (parameters.$queryParameters) { 40 | Object.keys(parameters.$queryParameters) 41 | .forEach(function(parameterName) { 42 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} parameter = parameters.$queryParameters[parameterName]; 43 | queryParameters[parameterName] = parameter; 44 | }); 45 | } 46 | return queryParameters; 47 | } 48 | 49 | /** 50 | * HTTP Request 51 | * @method 52 | * @name {{&className}}#request 53 | * @param {string} method - http method 54 | * @param {string} url - url to do request 55 | * @param {object} parameters 56 | * @param {object} body - body parameters / object 57 | * @param {object} headers - header parameters 58 | * @param {object} queryParameters - querystring parameters 59 | * @param {object} form - form data object 60 | * @param {object} deferred - promise object 61 | */ 62 | {{&className}}.prototype.request = function(method, url, parameters, body, headers, queryParameters, form, deferred){ 63 | {{#isGET}} 64 | var cached = parameters.$cache && parameters.$cache.get(url); 65 | if(cached !== undefined && parameters.$refresh !== true) { 66 | deferred.resolve(cached); 67 | return deferred.promise; 68 | } 69 | {{/isGET}} 70 | var options = { 71 | timeout: parameters.$timeout, 72 | method: method, 73 | url: url, 74 | params: queryParameters, 75 | data: body, 76 | headers: headers 77 | }; 78 | if(Object.keys(form).length > 0) { 79 | options.data = form; 80 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 81 | options.transformRequest = {{&className}}.transformRequest; 82 | } 83 | $http(options) 84 | .then(function(data, status, headers, config){ 85 | deferred.resolve(data); 86 | if(parameters.$cache !== undefined) { 87 | parameters.$cache.put(url, data, parameters.$cacheItemOpts ? parameters.$cacheItemOpts : {}); 88 | } 89 | }) 90 | .catch(function(data, status, headers, config){ 91 | deferred.reject({ 92 | status: status, 93 | headers: headers, 94 | config: config, 95 | body: data 96 | }); 97 | }); 98 | 99 | }; 100 | 101 | {{&className}}.prototype.$on = function($scope, path, handler){ 102 | var url = this.domain + path; 103 | $scope.$on(url, function(){ 104 | handler(); 105 | }); 106 | return this; 107 | }; 108 | 109 | {{&className}}.prototype.$broadcast = function(path){ 110 | var url = this.domain + path; 111 | //cache.remove(url); 112 | $rootScope.$broadcast(url); 113 | return this; 114 | }; 115 | 116 | {{&className}}.transformRequest = function(obj) { 117 | var str = []; 118 | for(var p in obj) { 119 | var val = obj[p]; 120 | if(angular.isArray(val)) { 121 | val.forEach(function(val){ 122 | str.push(encodeURIComponent(p) + "=" + encodeURIComponent(val)); 123 | }); 124 | } else { 125 | str.push(encodeURIComponent(p) + "=" + encodeURIComponent(val)); 126 | } 127 | } 128 | return str.join("&"); 129 | }; 130 | 131 | {{#isSecure}} 132 | {{#isSecureToken}} 133 | /** 134 | * Set Token 135 | * @method 136 | * @name {{&className}}#setToken 137 | * @param {string} value - token's value 138 | * @param {string} headerOrQueryName - the header or query name to send the token at 139 | * @param {boolean} isQuery - true if send the token as query param, otherwise, send as header param 140 | */ 141 | {{&className}}.prototype.setToken = function (value, headerOrQueryName, isQuery) { 142 | this.token.value = value; 143 | this.token.headerOrQueryName = headerOrQueryName; 144 | this.token.isQuery = isQuery; 145 | }; 146 | {{/isSecureToken}} 147 | {{#isSecureApiKey}} 148 | /** 149 | * Set Api Key 150 | * @method 151 | * @name {{&className}}#setApiKey 152 | * @param {string} value - apiKey's value 153 | * @param {string} headerOrQueryName - the header or query name to send the apiKey at 154 | * @param {boolean} isQuery - true if send the apiKey as query param, otherwise, send as header param 155 | */ 156 | {{&className}}.prototype.setApiKey = function (value, headerOrQueryName, isQuery) { 157 | this.apiKey.value = value; 158 | this.apiKey.headerOrQueryName = headerOrQueryName; 159 | this.apiKey.isQuery = isQuery; 160 | }; 161 | {{/isSecureApiKey}} 162 | {{#isSecureBasic}} 163 | /** 164 | * Set Basic Auth 165 | * @method 166 | * @name {{&className}}#setBasicAuth 167 | * @param {string} username 168 | * @param {string} password 169 | */ 170 | {{&className}}.prototype.setBasicAuth = function (username, password) { 171 | this.basic.username = username; 172 | this.basic.password = password; 173 | }; 174 | {{/isSecureBasic}} 175 | /** 176 | * Set Auth headers 177 | * @method 178 | * @name {{&className}}#setAuthHeaders 179 | * @param {object} headerParams - headers object 180 | */ 181 | {{&className}}.prototype.setAuthHeaders = function (headerParams) { 182 | var headers = headerParams ? headerParams : {}; 183 | {{#isSecureToken}} 184 | if (!this.token.isQuery) { 185 | if (this.token.headerOrQueryName) { 186 | headers[this.token.headerOrQueryName] = this.token.value; 187 | } else if (this.token.value) { 188 | headers['Authorization'] = 'Bearer ' + this.token.value; 189 | } 190 | } 191 | {{/isSecureToken}} 192 | {{#isSecureApiKey}} 193 | if (!this.apiKey.isQuery && this.apiKey.headerOrQueryName) { 194 | headers[this.apiKey.headerOrQueryName] = this.apiKey.value; 195 | } 196 | {{/isSecureApiKey}} 197 | {{#isSecureBasic}} 198 | if (this.basic.username && this.basic.password) { 199 | headers['Authorization'] = 'Basic ' + btoa(this.basic.username + ':' + this.basic.password); 200 | } 201 | {{/isSecureBasic}} 202 | return headers; 203 | }; 204 | {{/isSecure}} 205 | 206 | {{#methods}} 207 | {{> method}} 208 | {{/methods}} 209 | 210 | return {{&className}}; 211 | })(); 212 | 213 | return {{&className}}; 214 | }]); 215 | -------------------------------------------------------------------------------- /templates/flow-class.mustache: -------------------------------------------------------------------------------- 1 | {{#definitions}} 2 | export type {{&name}} = {{#flowType}}{{> type}}{{/flowType}}; 3 | {{/definitions}} 4 | 5 | export default class {{&className}} { 6 | static request( 7 | path: string, 8 | method: 'POST' | 'GET' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH', 9 | query: {[string]: mixed}, 10 | body?: {[string]: any} | string | Array, 11 | ) { 12 | throw new Error("Must be implemented"); 13 | } 14 | {{#methods}} 15 | {{> method}} 16 | {{/methods}} 17 | } 18 | -------------------------------------------------------------------------------- /templates/flow-method.mustache: -------------------------------------------------------------------------------- 1 | static async {{&methodName}}( 2 | {{#hasParameters}} 3 | parameters: { 4 | {{#parameters}} 5 | {{^isSingleton}}'{{&camelCaseName}}'{{&cardinality}}: {{#flowType}}{{> type}}{{/flowType}},{{/isSingleton}} 6 | {{/parameters}} 7 | } 8 | {{/hasParameters}} 9 | ): Promise<{{#methodResponse}}"{{&methodResponse}}"{{/methodResponse}}{{#methodFlowType}}{{> type}}{{/methodFlowType}}> { 10 | let path = '{{&path}}'; 11 | let body; 12 | let query = {}; 13 | {{#parameters}} 14 | {{#required}} 15 | if(parameters['{{&camelCaseName}}'] === undefined) { 16 | throw new Error('Missing required {{¶mType}} parameter: {{&camelCaseName}}'); 17 | } 18 | {{/required}} 19 | 20 | {{#isPathParameter}} 21 | path = path.replace('{{=<% %>=}}{<%&name%>}<%={{ }}=%>', `${parameters['{{&camelCaseName}}']}`); 22 | {{/isPathParameter}} 23 | 24 | {{#isBodyParameter}} 25 | if(parameters['{{&camelCaseName}}'] !== undefined) { 26 | body = parameters['{{&camelCaseName}}']; 27 | } 28 | {{/isBodyParameter}} 29 | 30 | {{#isQueryParameter}} 31 | if(parameters['{{&camelCaseName}}'] !== undefined) { 32 | query['{{&name}}'] = parameters['{{&camelCaseName}}']; 33 | } 34 | {{/isQueryParameter}} 35 | 36 | {{/parameters}} 37 | 38 | return await this.request(path, '{{method}}', query, body); 39 | } 40 | -------------------------------------------------------------------------------- /templates/flow-type.mustache: -------------------------------------------------------------------------------- 1 | {{! must use different delimiters to avoid ambiguities when delimiters directly follow a literal brace {. }} 2 | {{=<% %>=}} 3 | <%#simpleFlowType%><%&simpleFlowType%><%/simpleFlowType%><%! 4 | %><%#isObject%>{<%#properties%> 5 | <%name%><%#optional%>?<%/optional%>: <%>type%>,<%/properties%><%! 6 | %><%^properties%>...<%/properties%> 7 | }<%/isObject%><%! 8 | %><%#isArray%>Array<<%#elementType%><%>type%><%/elementType%>><%/isArray%> 9 | <%={{ }}=%> 10 | -------------------------------------------------------------------------------- /templates/method.mustache: -------------------------------------------------------------------------------- 1 | /** 2 | * {{&summary}} 3 | * @method 4 | * @name {{&className}}#{{&methodName}} 5 | * @param {object} parameters - method options and parameters 6 | {{#parameters}} 7 | {{^isSingleton}} * @param {{=<% %>=}}{<%&type%>}<%={{ }}=%> parameters.{{&camelCaseName}} - {{&description}}{{/isSingleton}} 8 | {{/parameters}} 9 | */ 10 | {{&className}}.prototype.{{&methodName}} = function(parameters){ 11 | if(parameters === undefined) { 12 | parameters = {}; 13 | } 14 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} deferred = {{#isNode}}Q{{/isNode}}{{^isNode}}$q{{/isNode}}.defer(); 15 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} domain = this.domain, path = '{{&path}}'; 16 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} body = {}, queryParameters = {}, headers = {}, form = {}; 17 | 18 | {{#isSecure}} 19 | headers = this.setAuthHeaders(headers); 20 | {{/isSecure}} 21 | {{#headers}} 22 | headers['{{&name}}'] = [{{&value}}]; 23 | {{/headers}} 24 | 25 | {{#parameters}} 26 | {{#isQueryParameter}} 27 | {{#isSingleton}} 28 | queryParameters['{{&name}}'] = '{{&singleton}}'; 29 | {{/isSingleton}} 30 | {{^isSingleton}} 31 | {{#isPatternType}} 32 | Object.keys(parameters).forEach(function(parameterName) { 33 | if(new RegExp('{{&pattern}}').test(parameterName)){ 34 | queryParameters[parameterName] = parameters[parameterName]; 35 | } 36 | }); 37 | {{/isPatternType}} 38 | {{#default}} 39 | /** set default value **/ 40 | queryParameters['{{&name}}'] = {{&default}}; 41 | {{/default}} 42 | 43 | {{^isPatternType}} 44 | if(parameters['{{&camelCaseName}}'] !== undefined){ 45 | queryParameters['{{&name}}'] = parameters['{{&camelCaseName}}']; 46 | } 47 | {{/isPatternType}} 48 | {{/isSingleton}} 49 | {{/isQueryParameter}} 50 | 51 | {{#isPathParameter}} 52 | path = path.replace('{{=<% %>=}}{<%&name%>}<%={{ }}=%>', parameters['{{&camelCaseName}}']); 53 | {{/isPathParameter}} 54 | 55 | {{#isHeaderParameter}} 56 | {{#isSingleton}} 57 | headers['{{&name}}'] = '{{&singleton}}'; 58 | {{/isSingleton}} 59 | {{^isSingleton}} 60 | if(parameters['{{&camelCaseName}}'] !== undefined){ 61 | headers['{{&name}}'] = parameters['{{&camelCaseName}}']; 62 | } 63 | {{/isSingleton}} 64 | {{/isHeaderParameter}} 65 | 66 | {{#isBodyParameter}} 67 | if(parameters['{{&camelCaseName}}'] !== undefined){ 68 | body = parameters['{{&camelCaseName}}']; 69 | } 70 | {{/isBodyParameter}} 71 | 72 | {{#isFormParameter}} 73 | {{#isSingleton}} 74 | form['{{&name}}'] = '{{&singleton}}'; 75 | {{/isSingleton}} 76 | {{^isSingleton}} 77 | if(parameters['{{&camelCaseName}}'] !== undefined){ 78 | form['{{&name}}'] = parameters['{{&camelCaseName}}']; 79 | } 80 | {{/isSingleton}} 81 | {{/isFormParameter}} 82 | 83 | {{#required}} 84 | if(parameters['{{&camelCaseName}}'] === undefined){ 85 | deferred.reject(new Error('Missing required {{¶mType}} parameter: {{&camelCaseName}}')); 86 | return deferred.promise; 87 | } 88 | {{/required}} 89 | 90 | {{/parameters}} 91 | queryParameters = mergeQueryParams(parameters, queryParameters); 92 | 93 | this.request('{{method}}', domain + path, parameters, body, headers, queryParameters, form, deferred); 94 | 95 | return deferred.promise; 96 | }; 97 | -------------------------------------------------------------------------------- /templates/node-class.mustache: -------------------------------------------------------------------------------- 1 | /*jshint -W069 */ 2 | /** 3 | * {{&description}} 4 | * @class {{&className}} 5 | * @param {(string|object)} [domainOrOptions] - The project domain or options object. If object, see the object's optional properties. 6 | * @param {string} [domainOrOptions.domain] - The project domain 7 | * @param {object} [domainOrOptions.token] - auth token - object with value property and optional headerOrQueryName and isQuery properties 8 | */ 9 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} {{&className}} = (function(){ 10 | 'use strict'; 11 | 12 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} request = require('request'); 13 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} Q = require('q'); 14 | 15 | function {{&className}}(options){ 16 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} domain = (typeof options === 'object') ? options.domain : options; 17 | this.domain = domain ? domain : '{{&domain}}'; 18 | if(this.domain.length === 0) { 19 | throw new Error('Domain parameter must be specified as a string.'); 20 | } 21 | {{#isSecure}} 22 | {{#isSecureToken}} 23 | this.token = (typeof options === 'object') ? (options.token ? options.token : {}) : {}; 24 | {{/isSecureToken}} 25 | {{#isSecureApiKey}} 26 | this.apiKey = (typeof options === 'object') ? (options.apiKey ? options.apiKey : {}) : {}; 27 | {{/isSecureApiKey}} 28 | {{#isSecureBasic}} 29 | this.basic = (typeof options === 'object') ? (options.basic ? options.basic : {}) : {}; 30 | {{/isSecureBasic}} 31 | {{/isSecure}} 32 | } 33 | 34 | function mergeQueryParams(parameters, queryParameters) { 35 | if (parameters.$queryParameters) { 36 | Object.keys(parameters.$queryParameters) 37 | .forEach(function(parameterName) { 38 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} parameter = parameters.$queryParameters[parameterName]; 39 | queryParameters[parameterName] = parameter; 40 | }); 41 | } 42 | return queryParameters; 43 | } 44 | 45 | /** 46 | * HTTP Request 47 | * @method 48 | * @name {{&className}}#request 49 | * @param {string} method - http method 50 | * @param {string} url - url to do request 51 | * @param {object} parameters 52 | * @param {object} body - body parameters / object 53 | * @param {object} headers - header parameters 54 | * @param {object} queryParameters - querystring parameters 55 | * @param {object} form - form data object 56 | * @param {object} deferred - promise object 57 | */ 58 | {{&className}}.prototype.request = function(method, url, parameters, body, headers, queryParameters, form, deferred){ 59 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} req = { 60 | method: method, 61 | uri: url, 62 | qs: queryParameters, 63 | headers: headers, 64 | body: body 65 | }; 66 | if(Object.keys(form).length > 0) { 67 | req.form = form; 68 | } 69 | if(typeof(body) === 'object' && !(body instanceof Buffer)) { 70 | req.json = true; 71 | } 72 | request(req, function(error, response, body){ 73 | if(error) { 74 | deferred.reject(error); 75 | } else { 76 | if(/^application\/(.*\\+)?json/.test(response.headers['content-type'])) { 77 | try { 78 | body = JSON.parse(body); 79 | } catch(e) {} 80 | } 81 | if(response.statusCode === 204) { 82 | deferred.resolve({ response: response }); 83 | } else if(response.statusCode >= 200 && response.statusCode <= 299) { 84 | deferred.resolve({ response: response, body: body }); 85 | } else { 86 | deferred.reject({ response: response, body: body }); 87 | } 88 | } 89 | }); 90 | }; 91 | 92 | {{#isSecure}} 93 | {{#isSecureToken}} 94 | /** 95 | * Set Token 96 | * @method 97 | * @name {{&className}}#setToken 98 | * @param {string} value - token's value 99 | * @param {string} headerOrQueryName - the header or query name to send the token at 100 | * @param {boolean} isQuery - true if send the token as query param, otherwise, send as header param 101 | */ 102 | {{&className}}.prototype.setToken = function (value, headerOrQueryName, isQuery) { 103 | this.token.value = value; 104 | this.token.headerOrQueryName = headerOrQueryName; 105 | this.token.isQuery = isQuery; 106 | }; 107 | {{/isSecureToken}} 108 | {{#isSecureApiKey}} 109 | /** 110 | * Set Api Key 111 | * @method 112 | * @name {{&className}}#setApiKey 113 | * @param {string} value - apiKey's value 114 | * @param {string} headerOrQueryName - the header or query name to send the apiKey at 115 | * @param {boolean} isQuery - true if send the apiKey as query param, otherwise, send as header param 116 | */ 117 | {{&className}}.prototype.setApiKey = function (value, headerOrQueryName, isQuery) { 118 | this.apiKey.value = value; 119 | this.apiKey.headerOrQueryName = headerOrQueryName; 120 | this.apiKey.isQuery = isQuery; 121 | }; 122 | {{/isSecureApiKey}} 123 | {{#isSecureBasic}} 124 | /** 125 | * Set Basic Auth 126 | * @method 127 | * @name {{&className}}#setBasicAuth 128 | * @param {string} username 129 | * @param {string} password 130 | */ 131 | {{&className}}.prototype.setBasicAuth = function (username, password) { 132 | this.basic.username = username; 133 | this.basic.password = password; 134 | }; 135 | {{/isSecureBasic}} 136 | /** 137 | * Set Auth headers 138 | * @method 139 | * @name {{&className}}#setAuthHeaders 140 | * @param {object} headerParams - headers object 141 | */ 142 | {{&className}}.prototype.setAuthHeaders = function (headerParams) { 143 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} headers = headerParams ? headerParams : {}; 144 | {{#isSecureToken}} 145 | if (!this.token.isQuery) { 146 | if (this.token.headerOrQueryName) { 147 | headers[this.token.headerOrQueryName] = this.token.value; 148 | } else if (this.token.value) { 149 | headers['Authorization'] = 'Bearer ' + this.token.value; 150 | } 151 | } 152 | {{/isSecureToken}} 153 | {{#isSecureApiKey}} 154 | if (!this.apiKey.isQuery && this.apiKey.headerOrQueryName) { 155 | headers[this.apiKey.headerOrQueryName] = this.apiKey.value; 156 | } 157 | {{/isSecureApiKey}} 158 | {{#isSecureBasic}} 159 | if (this.basic.username && this.basic.password) { 160 | headers['Authorization'] = 'Basic ' + new Buffer(this.basic.username + ':' + this.basic.password).toString("base64"); 161 | } 162 | {{/isSecureBasic}} 163 | return headers; 164 | }; 165 | {{/isSecure}} 166 | 167 | {{#methods}} 168 | {{> method}} 169 | {{/methods}} 170 | 171 | return {{&className}}; 172 | })(); 173 | 174 | exports.{{&className}} = {{&className}}; 175 | -------------------------------------------------------------------------------- /templates/react-class.mustache: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | /*global fetch, btoa */ 3 | import Q from 'q'; 4 | /** 5 | * {{&description}} 6 | * @class {{&className}} 7 | * @param {(string|object)} [domainOrOptions] - The project domain or options object. If object, see the object's optional properties. 8 | * @param {string} [domainOrOptions.domain] - The project domain 9 | * @param {object} [domainOrOptions.token] - auth token - object with value property and optional headerOrQueryName and isQuery properties 10 | */ 11 | let {{&className}} = (function(){ 12 | 'use strict'; 13 | 14 | function {{&className}}(options){ 15 | let domain = (typeof options === 'object') ? options.domain : options; 16 | this.domain = domain ? domain : '{{&domain}}'; 17 | if(this.domain.length === 0) { 18 | throw new Error('Domain parameter must be specified as a string.'); 19 | } 20 | {{#isSecure}} 21 | {{#isSecureToken}} 22 | this.token = (typeof options === 'object') ? (options.token ? options.token : {}) : {}; 23 | {{/isSecureToken}} 24 | {{#isSecureApiKey}} 25 | this.apiKey = (typeof options === 'object') ? (options.apiKey ? options.apiKey : {}) : {}; 26 | {{/isSecureApiKey}} 27 | {{#isSecureBasic}} 28 | this.basic = (typeof options === 'object') ? (options.basic ? options.basic : {}) : {}; 29 | {{/isSecureBasic}} 30 | {{/isSecure}} 31 | } 32 | 33 | function serializeQueryParams(parameters) { 34 | let str = []; 35 | for (let p in parameters) { 36 | if (parameters.hasOwnProperty(p)) { 37 | str.push(encodeURIComponent(p) + '=' + encodeURIComponent(parameters[p])); 38 | } 39 | } 40 | return str.join('&'); 41 | } 42 | 43 | function mergeQueryParams(parameters, queryParameters) { 44 | if (parameters.$queryParameters) { 45 | Object.keys(parameters.$queryParameters) 46 | .forEach(function(parameterName) { 47 | {{#isES6}}let{{/isES6}}{{^isES6}}var{{/isES6}} parameter = parameters.$queryParameters[parameterName]; 48 | queryParameters[parameterName] = parameter; 49 | }); 50 | } 51 | return queryParameters; 52 | } 53 | 54 | /** 55 | * HTTP Request 56 | * @method 57 | * @name {{&className}}#request 58 | * @param {string} method - http method 59 | * @param {string} url - url to do request 60 | * @param {object} parameters 61 | * @param {object} body - body parameters / object 62 | * @param {object} headers - header parameters 63 | * @param {object} queryParameters - querystring parameters 64 | * @param {object} form - form data object 65 | * @param {object} deferred - promise object 66 | */ 67 | {{&className}}.prototype.request = function(method, url, parameters, body, headers, queryParameters, form, deferred){ 68 | const queryParams = queryParameters && Object.keys(queryParameters).length ? serializeQueryParams(queryParameters) : null ; 69 | const urlWithParams = url + (queryParams ? '?' + queryParams : ''); 70 | 71 | if(body && !Object.keys(body).length) { 72 | body = undefined; 73 | } 74 | 75 | fetch(urlWithParams, { 76 | method, 77 | headers, 78 | body: JSON.stringify(body) 79 | }).then((response) => { 80 | return response.json(); 81 | }).then((body) => { 82 | deferred.resolve(body); 83 | }).catch((error) => { 84 | deferred.reject(error); 85 | }); 86 | }; 87 | 88 | {{#isSecure}} 89 | {{#isSecureToken}} 90 | /** 91 | * Set Token 92 | * @method 93 | * @name {{&className}}#setToken 94 | * @param {string} value - token's value 95 | * @param {string} headerOrQueryName - the header or query name to send the token at 96 | * @param {boolean} isQuery - true if send the token as query param, otherwise, send as header param 97 | */ 98 | {{&className}}.prototype.setToken = function (value, headerOrQueryName, isQuery) { 99 | this.token.value = value; 100 | this.token.headerOrQueryName = headerOrQueryName; 101 | this.token.isQuery = isQuery; 102 | }; 103 | {{/isSecureToken}} 104 | {{#isSecureApiKey}} 105 | /** 106 | * Set Api Key 107 | * @method 108 | * @name {{&className}}#setApiKey 109 | * @param {string} value - apiKey's value 110 | * @param {string} headerOrQueryName - the header or query name to send the apiKey at 111 | * @param {boolean} isQuery - true if send the apiKey as query param, otherwise, send as header param 112 | */ 113 | {{&className}}.prototype.setApiKey = function (value, headerOrQueryName, isQuery) { 114 | this.apiKey.value = value; 115 | this.apiKey.headerOrQueryName = headerOrQueryName; 116 | this.apiKey.isQuery = isQuery; 117 | }; 118 | {{/isSecureApiKey}} 119 | {{#isSecureBasic}} 120 | /** 121 | * Set Basic Auth 122 | * @method 123 | * @name {{&className}}#setBasicAuth 124 | * @param {string} username 125 | * @param {string} password 126 | */ 127 | {{&className}}.prototype.setBasicAuth = function (username, password) { 128 | this.basic.username = value; 129 | this.basic.password = password; 130 | }; 131 | {{/isSecureBasic}} 132 | /** 133 | * Set Auth headers 134 | * @method 135 | * @name {{&className}}#setAuthHeaders 136 | * @param {object} headerParams - headers object 137 | */ 138 | {{&className}}.prototype.setAuthHeaders = function (headerParams) { 139 | let headers = headerParams ? headerParams : {}; 140 | {{#isSecureToken}} 141 | if (!this.token.isQuery) { 142 | if (this.token.headerOrQueryName) { 143 | headers[this.token.headerOrQueryName] = this.token.value; 144 | } else if (this.token.value) { 145 | headers['Authorization'] = 'Bearer ' + this.token.value; 146 | } 147 | } 148 | {{/isSecureToken}} 149 | {{#isSecureApiKey}} 150 | if (!this.apiKey.isQuery && this.apiKey.headerOrQueryName) { 151 | headers[this.apiKey.headerOrQueryName] = this.apiKey.value; 152 | } 153 | {{/isSecureApiKey}} 154 | {{#isSecureBasic}} 155 | if (this.basic.username && this.basic.password) { 156 | headers['Authorization'] = 'Basic ' + btoa(this.basic.username + ':' + this.basic.password); 157 | } 158 | {{/isSecureBasic}} 159 | return headers; 160 | }; 161 | {{/isSecure}} 162 | 163 | {{#methods}} 164 | {{> method}} 165 | {{/methods}} 166 | 167 | return {{&className}}; 168 | })(); 169 | 170 | exports.{{&className}} = {{&className}}; 171 | -------------------------------------------------------------------------------- /templates/type.mustache: -------------------------------------------------------------------------------- 1 | {{#tsType}} 2 | {{! must use different delimiters to avoid ambiguities when delimiters directly follow a literal brace {. }} 3 | {{=<% %>=}} 4 | <%#isRef%><%target%><%/isRef%><%! 5 | %><%#isAtomic%><%&tsType%><%/isAtomic%><%! 6 | %><%#isObject%>{<%#properties%> 7 | '<%name%>'<%#optional%>?<%/optional%>: <%>type%><%/properties%> 8 | }<%/isObject%><%! 9 | %><%#isArray%>Array<<%#elementType%><%>type%><%/elementType%>>|<%#elementType%><%>type%><%/elementType%><%/isArray%> 10 | <%={{ }}=%> 11 | {{/tsType}} 12 | -------------------------------------------------------------------------------- /templates/typescript-class.mustache: -------------------------------------------------------------------------------- 1 | {{#imports}} 2 | /// 3 | {{/imports}} 4 | 5 | import * as request from "superagent"; 6 | import {SuperAgentStatic} from "superagent"; 7 | 8 | type CallbackHandler = (err: any, res?: request.Response) => void; 9 | {{#definitions}} 10 | type {{&name}} = {{#tsType}}{{> type}}{{/tsType}}; 11 | {{/definitions}} 12 | 13 | type Logger = { log: (line: string) => any }; 14 | 15 | /** 16 | * {{&description}} 17 | * @class {{&className}} 18 | * @param {(string)} [domainOrOptions] - The project domain. 19 | */ 20 | export default class {{&className}} { 21 | 22 | private domain: string = "{{&domain}}"; 23 | private errorHandlers: CallbackHandler[] = []; 24 | 25 | constructor(domain?: string, private logger?: Logger) { 26 | if(domain) { 27 | this.domain = domain; 28 | } 29 | } 30 | 31 | getDomain() { 32 | return this.domain; 33 | } 34 | 35 | addErrorHandler(handler: CallbackHandler) { 36 | this.errorHandlers.push(handler); 37 | } 38 | 39 | private request(method: string, url: string, body: any, headers: any, queryParameters: any, form: any, reject: CallbackHandler, resolve: CallbackHandler) { 40 | if(this.logger) { 41 | this.logger.log(`Call ${method} ${url}`); 42 | } 43 | 44 | let req = (request as SuperAgentStatic)(method, url).query(queryParameters); 45 | 46 | Object.keys(headers).forEach(key => { 47 | req.set(key, headers[key]); 48 | }); 49 | 50 | if(body) { 51 | req.send(body); 52 | } 53 | 54 | if(typeof(body) === 'object' && !(body.constructor.name === 'Buffer')) { 55 | req.set('Content-Type', 'application/json'); 56 | } 57 | 58 | if(Object.keys(form).length > 0) { 59 | req.type('form'); 60 | req.send(form); 61 | } 62 | 63 | req.end((error, response) => { 64 | if(error || !response.ok) { 65 | reject(error); 66 | this.errorHandlers.forEach(handler => handler(error)); 67 | } else { 68 | resolve(response); 69 | } 70 | }); 71 | } 72 | 73 | {{#methods}} 74 | {{> method}} 75 | 76 | {{/methods}} 77 | } 78 | -------------------------------------------------------------------------------- /templates/typescript-method.mustache: -------------------------------------------------------------------------------- 1 | {{&methodName}}URL(parameters: { 2 | {{#parameters}}{{^isSingleton}}'{{&camelCaseName}}'{{&cardinality}}: {{> type}}, 3 | {{/isSingleton}}{{/parameters}} 4 | $queryParameters?: any, 5 | $domain?: string 6 | }): string { 7 | let queryParameters: any = {}; 8 | const domain = parameters.$domain ? parameters.$domain : this.domain; 9 | let path = '{{&path}}'; 10 | {{#parameters}} 11 | {{#isQueryParameter}} 12 | {{#isSingleton}} 13 | queryParameters['{{&name}}'] = '{{&singleton}}'; 14 | {{/isSingleton}} 15 | {{^isSingleton}} 16 | {{#isPatternType}} 17 | Object.keys(parameters).forEach(function(parameterName) { 18 | if(new RegExp('{{&pattern}}').test(parameterName)){ 19 | queryParameters[parameterName] = parameters[parameterName]; 20 | } 21 | }); 22 | {{/isPatternType}} 23 | {{^isPatternType}} 24 | if(parameters['{{&camelCaseName}}'] !== undefined){ 25 | queryParameters['{{&name}}'] = parameters['{{&camelCaseName}}']; 26 | } 27 | {{/isPatternType}} 28 | {{/isSingleton}} 29 | {{/isQueryParameter}} 30 | 31 | {{#isPathParameter}} 32 | path = path.replace('{{=<% %>=}}{<%&name%>}<%={{ }}=%>', `${parameters['{{&camelCaseName}}']}`); 33 | {{/isPathParameter}} 34 | {{/parameters}} 35 | 36 | if(parameters.$queryParameters) { 37 | Object.keys(parameters.$queryParameters).forEach(function(parameterName) { 38 | queryParameters[parameterName] = parameters.$queryParameters[parameterName]; 39 | }); 40 | } 41 | 42 | {{^isBodyParameter}} 43 | {{#isPOST}} 44 | queryParameters = {}; 45 | {{/isPOST}} 46 | {{/isBodyParameter}} 47 | 48 | 49 | let keys = Object.keys(queryParameters); 50 | return domain + path + (keys.length > 0 ? '?' + (keys.map(key => key + '=' + encodeURIComponent(queryParameters[key])).join('&')): ''); 51 | } 52 | 53 | /** 54 | * {{&summary}} 55 | * @method 56 | {{#externalDocs}} 57 | * @see {@link {{&url}}|{{#description}}{{&description}}{{/description}}{{^description}}External docs{{/description}}} 58 | {{/externalDocs}} 59 | * @name {{&className}}#{{&methodName}} 60 | {{#parameters}} 61 | {{^isSingleton}} * @param {{=<% %>=}}{<%&type%>}<%={{ }}=%> {{&camelCaseName}} - {{&description}}{{/isSingleton}} 62 | {{/parameters}} 63 | */ 64 | {{&methodName}}(parameters: { 65 | {{#parameters}}{{^isSingleton}}'{{&camelCaseName}}'{{&cardinality}}: {{> type}}, 66 | {{/isSingleton}}{{/parameters}} 67 | $queryParameters?: any, 68 | $domain?: string 69 | }): Promise { 70 | const domain = parameters.$domain ? parameters.$domain : this.domain; 71 | let path = '{{&path}}'; 72 | let body: any; 73 | let queryParameters: any = {}; 74 | let headers: any = {}; 75 | let form: any = {}; 76 | return new Promise((resolve, reject) => { 77 | {{#headers}} 78 | headers['{{&name}}'] = {{&value}}; 79 | {{/headers}} 80 | 81 | {{#parameters}} 82 | 83 | {{#isQueryParameter}} 84 | {{#isSingleton}} 85 | queryParameters['{{&name}}'] = '{{&singleton}}'; 86 | {{/isSingleton}} 87 | {{^isSingleton}} 88 | {{#isPatternType}} 89 | Object.keys(parameters).forEach(function(parameterName) { 90 | if(new RegExp('{{&pattern}}').test(parameterName)){ 91 | queryParameters[parameterName] = parameters[parameterName]; 92 | } 93 | }); 94 | {{/isPatternType}} 95 | {{^isPatternType}} 96 | if(parameters['{{&camelCaseName}}'] !== undefined) { 97 | queryParameters['{{&name}}'] = parameters['{{&camelCaseName}}']; 98 | } 99 | {{/isPatternType}} 100 | {{/isSingleton}} 101 | {{/isQueryParameter}} 102 | 103 | {{#isPathParameter}} 104 | path = path.replace('{{=<% %>=}}{<%&name%>}<%={{ }}=%>', `${parameters['{{&camelCaseName}}']}`); 105 | {{/isPathParameter}} 106 | 107 | {{#isHeaderParameter}} 108 | {{#isSingleton}} 109 | headers['{{&name}}'] = '{{&singleton}}'; 110 | {{/isSingleton}} 111 | {{^isSingleton}} 112 | if(parameters['{{&camelCaseName}}'] !== undefined) { 113 | headers['{{&name}}'] = parameters['{{&camelCaseName}}']; 114 | } 115 | {{/isSingleton}} 116 | {{/isHeaderParameter}} 117 | 118 | {{#isBodyParameter}} 119 | if(parameters['{{&camelCaseName}}'] !== undefined) { 120 | body = parameters['{{&camelCaseName}}']; 121 | } 122 | {{/isBodyParameter}} 123 | 124 | {{#isFormParameter}} 125 | {{#isSingleton}} 126 | form['{{&name}}'] = '{{&singleton}}'; 127 | {{/isSingleton}} 128 | {{^isSingleton}} 129 | if(parameters['{{&camelCaseName}}'] !== undefined) { 130 | form['{{&name}}'] = parameters['{{&camelCaseName}}']; 131 | } 132 | {{/isSingleton}} 133 | {{/isFormParameter}} 134 | 135 | {{#required}} 136 | if(parameters['{{&camelCaseName}}'] === undefined) { 137 | reject(new Error('Missing required {{¶mType}} parameter: {{&camelCaseName}}')); 138 | return; 139 | } 140 | {{/required}} 141 | 142 | {{/parameters}} 143 | 144 | if(parameters.$queryParameters) { 145 | Object.keys(parameters.$queryParameters).forEach(function(parameterName){ 146 | queryParameters[parameterName] = parameters.$queryParameters[parameterName]; 147 | }); 148 | } 149 | 150 | {{^isBodyParameter}} 151 | {{#isPOST}} 152 | form = queryParameters; 153 | queryParameters = {}; 154 | {{/isPOST}} 155 | {{/isBodyParameter}} 156 | 157 | this.request('{{method}}', domain + path, body, headers, queryParameters, form, reject, resolve); 158 | }); 159 | } 160 | -------------------------------------------------------------------------------- /tests/apis/_batch.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "1.0", 3 | "swaggerVersion": "1.2", 4 | "basePath": "http://portal.28.io/v1", 5 | "resourcePath": "/_batch", 6 | "produces": [ 7 | "application/json" 8 | ], 9 | "description": "

These resources can be used to perform batch operations. The endpoint of these resources is based on your project name. For instance, if your 28.io project is named myproject, your endpoint for this API will be will be: http://myproject.28.io/v1/_batch.

", 10 | "apis": [ 11 | { 12 | "path": "/_batch/project", 13 | "description": "Import project contents from an archive", 14 | "operations": [ 15 | { 16 | "method": "PUT", 17 | "summary": "Import project contents from an archive", 18 | "notes": "

This method imports modules and public/private queries from an archive. The archive can either be specified through the url query parameter or through the request body.

The following archive formats and compression algorithms are supported:

  • ZIP (with compression DEFLATE or STORE)
  • TAR (with compression GZIP)

When importing, modules will be loaded from the lib and modules folders (if any), whereas public and private queries will loaded from the public and private folders, respectively. These folders will be first searched in the root of the archive. If none of these folders is found and the archive root contains a single folder, the search will be repeated in that folder. If the archive has a different structure you can specify the path inside the archive where the modules and queries folders are located through the root parameter.

The subfolder structure will be preserved during the import.

The behaviour of the import can be controlled through the overwrite and delete-orphaned parameters. By default, orphaned file are removed and existing modules and queries are overwritten. To see the changes that will be performed by a request before they are made, you can use the simulate query parameter.

The response contains the list of queries and modules of the project along with the taken action. Additionally a list of the files contained in the archive which have been ignored is returned. If the operation succeeds any precompiled query which source has been updated or depending on an updated module will be deleted.

", 19 | "type": "ProjectImport", 20 | "nickname": "importProject", 21 | "parameters": [ 22 | { 23 | "name": "url", 24 | "description": "The archive url.", 25 | "required": false, 26 | "type": "string", 27 | "paramType": "query" 28 | }, 29 | { 30 | "name": "archive", 31 | "description": "The archive contents.", 32 | "required": false, 33 | "type": "string", 34 | "paramType": "body" 35 | }, 36 | { 37 | "name": "root", 38 | "description": "The path inside the archive that contains the modules and queries folders. Use '/' as folder separator.", 39 | "required": false, 40 | "type": "string", 41 | "paramType": "query" 42 | }, 43 | { 44 | "name": "overwrite", 45 | "description": "Whether to overwrite current project queries and modules. Default is true.", 46 | "required": false, 47 | "type": "string", 48 | "enum": [ 49 | "yes", 50 | "if-newer", 51 | "no" 52 | ], 53 | "paramType": "query" 54 | }, 55 | { 56 | "name": "delete-orphaned", 57 | "description": "Whether to delete orphaned file or not. Default is false.", 58 | "required": false, 59 | "type": "boolean", 60 | "paramType": "query" 61 | }, 62 | { 63 | "name": "simulate", 64 | "description": "Whether to simulate the operation or not. Default is false.", 65 | "required": false, 66 | "type": "boolean", 67 | "paramType": "query" 68 | }, 69 | { 70 | "name": "token", 71 | "description": "A project token.", 72 | "required": true, 73 | "type": "string", 74 | "paramType": "query" 75 | }, 76 | { 77 | "name": "Content-Type", 78 | "type": "string", 79 | "paramType": "header", 80 | "enum": [ 81 | "application/zip", 82 | "application/x-gzip" 83 | ] 84 | } 85 | ], 86 | "responseMessages": [ 87 | { 88 | "code": 400, 89 | "message": "Bad request: a parameter is missing or invalid.", 90 | "responseModel": "Error" 91 | }, 92 | { 93 | "code": 401, 94 | "message": "Unauthorized: the specified project token is invalid or expired.", 95 | "responseModel": "Error" 96 | }, 97 | { 98 | "code": 409, 99 | "message": "The specified archive contains the same module in the lib and modules folder.", 100 | "responseModel": "Error" 101 | }, 102 | { 103 | "code": 422, 104 | "message": "The specified archive cannot be downloaded or is corrupted.", 105 | "responseModel": "Error" 106 | }, 107 | { 108 | "code": 500, 109 | "message": "An internal error occurred during the processing of the request.", 110 | "responseModel": "Error" 111 | } 112 | ], 113 | "successMessages": [ 114 | { 115 | "code": 200, 116 | "message": "The project contents have been imported." 117 | } 118 | ], 119 | "examples": [ 120 | { 121 | "description": "The following request imports the project contents from an archive accessible through an URL.", 122 | "title": "Importing the project contents from an archive", 123 | "request": "curl -X PUT \"http://myproject.28.io/v1/_batch/project?url=http%3A%2F%2Fexample.com%2Farchive.zip&token=Rldtayt1YzV5a05FYlRvUFdqc0d4aGcveHJjPToyMDEzLTExLTA2VDA1OjM1OjUzLjk4Njg3N1o%3D\"", 124 | "response": "{ \"removedCompiledQueries\" : [], \"created\": [\"public/query.jq\", \"lib/module.jq\"], \"updated\": [], \"deleted\": [], \"not-updated\": [], \"kept\": [], \"root\": \"/\", \"ignored\": [] }" 125 | } 126 | ] 127 | } 128 | ] 129 | } 130 | ], 131 | "models": { 132 | "Error": { 133 | "id": "Error", 134 | "description": "Error information", 135 | "required": [ 136 | "request_id", 137 | "context", 138 | "message", 139 | "description" 140 | ], 141 | "properties": { 142 | "request_id": { 143 | "type": "string", 144 | "description": "The request identifier" 145 | }, 146 | "context": { 147 | "type": "string", 148 | "description": "The complete domain name of the project" 149 | }, 150 | "message": { 151 | "type": "string", 152 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 153 | }, 154 | "type": { 155 | "type": "string", 156 | "description": "For XQuery errors, the type of the error (e.g. static)" 157 | }, 158 | "code": { 159 | "type": "string", 160 | "description": "For XQuery errors, the error code" 161 | }, 162 | "location": { 163 | "type": "Location", 164 | "description": "For XQuery errors, the error location (if available)" 165 | }, 166 | "stack-trace": { 167 | "type": "array", 168 | "items": { 169 | "$ref": "StackEntry" 170 | }, 171 | "description": "For XQuery errors, the error stack trace (if available)" 172 | } 173 | } 174 | }, 175 | "Location": { 176 | "id": "Location", 177 | "description": "Error information", 178 | "required": [ 179 | "module", 180 | "line-number", 181 | "line-number-end", 182 | "column-number", 183 | "column-number-end" 184 | ], 185 | "properties": { 186 | "module": { 187 | "type": "string", 188 | "description": "The error module" 189 | }, 190 | "line-number": { 191 | "type": "string", 192 | "description": "The error first line number" 193 | }, 194 | "line-number-end": { 195 | "type": "string", 196 | "description": "The error last line number" 197 | }, 198 | "column-number": { 199 | "type": "string", 200 | "description": "The error first column number" 201 | }, 202 | "column-number-end": { 203 | "type": "string", 204 | "description": "The error last column number" 205 | } 206 | } 207 | }, 208 | "StackEntry": { 209 | "id": "StackEntry", 210 | "description": "A stack entry", 211 | "required": [ 212 | "function", 213 | "location" 214 | ], 215 | "properties": { 216 | "function": { 217 | "type": "Function", 218 | "description": "The function of the call" 219 | }, 220 | "type": { 221 | "type": "Location", 222 | "description": "The location of the call" 223 | } 224 | } 225 | }, 226 | "ProjectImport": { 227 | "id": "ProjectImport", 228 | "description": "A project import results", 229 | "required": [ 230 | "removedCompiledQueries", 231 | "created", 232 | "updated", 233 | "not-updated", 234 | "deleted", 235 | "kept", 236 | "root", 237 | "ignored" 238 | ], 239 | "properties": { 240 | "removedCompiledQueries": { 241 | "type": "array", 242 | "items": { 243 | "$ref": "string" 244 | }, 245 | "description": "The list of precompiled queries that have been removed" 246 | }, 247 | "created": { 248 | "type": "array", 249 | "items": { 250 | "$ref": "string" 251 | }, 252 | "description": "The list of files that existed only in the archive that have been created" 253 | }, 254 | "updated": { 255 | "type": "array", 256 | "items": { 257 | "$ref": "string" 258 | }, 259 | "description": "The list of files that existed both in the project and in the archive that have been updated" 260 | }, 261 | "not-updated": { 262 | "type": "array", 263 | "items": { 264 | "$ref": "string" 265 | }, 266 | "description": "The list of files that existed both in the project and in the archive that have not been updated" 267 | }, 268 | "deleted": { 269 | "type": "array", 270 | "items": { 271 | "$ref": "string" 272 | }, 273 | "description": "The list of files that existed only in the project that have been deleted" 274 | }, 275 | "kept": { 276 | "type": "array", 277 | "items": { 278 | "$ref": "string" 279 | }, 280 | "description": "The list of files that existed only in the project that have been kept" 281 | }, 282 | "root" : { 283 | "type": "string", 284 | "description": "The autodetected or specified path to the folder in which the queries and modules have been searched" 285 | }, 286 | "ignored": { 287 | "type": "array", 288 | "items": { 289 | "$ref": "string" 290 | }, 291 | "description": "The list of files in the archive that have been ignored. This happens if the file has an unsupported extension or is a system module" 292 | } 293 | } 294 | } 295 | } 296 | } -------------------------------------------------------------------------------- /tests/apis/_modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "1.0", 3 | "swaggerVersion": "1.2", 4 | "basePath": "http://portal.28.io/v1", 5 | "resourcePath": "/_modules", 6 | "produces": [ 7 | "application/json" 8 | ], 9 | "description": "

These resources can be used to manage JSONiq and XQuery library modules. The endpoint of these resources is based on your project name. For instance, if your 28.io project is named myproject, your endpoint for this API will be: http://myproject.28.io/v1/_modules.

This API does not allow to retrieve the source code, modify or delete system modules.

", 10 | "apis": [ 11 | { 12 | "path": "/_modules", 13 | "description": "Module Listing", 14 | "operations": [ 15 | { 16 | "method": "GET", 17 | "summary": "Lists available modules", 18 | "nickname": "listModules", 19 | "notes": "

This method retrieves the list of modules which can be imported by a project.

By default, only modules defined in the project will be listed. To also list system-provided modules, you can set the include-system query parameter to true.

To include module source codes, you can set the include-src query parameter to true. Regardless of the value of the include-src parameter, the source code of system modules will not be returned.

To perform namespace analysis and report for each module whether it has a namespace, the evenutal namespace URI and whether the module can be imported without location hints, set the include-ns query parameter to true.

To filter the returned modules you can use the starts-with query parameter. In this case, only the modules which path starts with the specified string will be returned.

If two or more module have the same namespace and thus the same path, the details of the module which would be imported by a query are shown.

", 20 | "type": "ModuleListing", 21 | "parameters": [ 22 | { 23 | "name": "starts-with", 24 | "description": "Filter the available module by their module path.", 25 | "required": false, 26 | "type": "string", 27 | "paramType": "query" 28 | }, 29 | { 30 | "name": "include-system", 31 | "description": "Include modules provided by the platform.", 32 | "required": false, 33 | "type": "boolean", 34 | "default": false, 35 | "paramType": "query" 36 | }, 37 | { 38 | "name": "include-ns", 39 | "description": "Include each module's namespace in the listing.", 40 | "required": false, 41 | "type": "boolean", 42 | "default": false, 43 | "paramType": "query" 44 | }, 45 | { 46 | "name": "include-src", 47 | "description": "Include each module's source code in the listing.", 48 | "required": false, 49 | "type": "boolean", 50 | "default": false, 51 | "paramType": "query" 52 | }, 53 | { 54 | "name": "token", 55 | "description": "A project token.", 56 | "required": true, 57 | "type": "string", 58 | "paramType": "query" 59 | } 60 | ], 61 | "responseMessages": [ 62 | { 63 | "code": 400, 64 | "message": "Bad request: a parameter is missing or invalid.", 65 | "responseModel": "Error" 66 | }, 67 | { 68 | "code": 403, 69 | "message": "Unauthorized: the specified project token is invalid or expired.", 70 | "responseModel": "Error" 71 | }, 72 | { 73 | "code": 500, 74 | "message": "An internal error occurred during the processing of the request.", 75 | "responseModel": "Error" 76 | } 77 | ], 78 | "successMessages": [ 79 | { 80 | "code": 200, 81 | "message": "Returns available module listing." 82 | } 83 | ], 84 | "examples": [ 85 | { 86 | "title": "Listing all project modules", 87 | "description": "The following request retrieves the list of all modules defined in the project myproject.", 88 | "request": "curl -X GET \"http://myproject.28.io/v1/_modules?token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\"", 89 | "response": "< 200 OK\n{\"/io/myproject/module\": {\n \"system\": false,\n \"language\": \"jsoniq\",\n \"lastModified\": \"2013-12-16T15:41:15Z\"\n}}" 90 | }] 91 | } 92 | ] 93 | } 94 | , 95 | { 96 | "path": "/_modules/{module-path}", 97 | "description": "Module Management", 98 | "operations": [ 99 | { 100 | "method": "GET", 101 | "summary": "Retrieves the source code of the specified project module", 102 | "notes": "

This method retrieves the source code of a project module. The response content type is set according to the query language of the module. If the query does not declare its own dialect or cannot be parsed, the query language is considered to be XQuery.

This operation cannot be used to retrieve the source code of system modules.

", 103 | "type": "string", 104 | "produces": [ 105 | "text/x-jsoniq;charset=UTF-8", 106 | "text/x-xquery;charset=UTF-8" 107 | ], 108 | "nickname": "getModule", 109 | "parameters": [ 110 | { 111 | "name": "module-path", 112 | "description": "The module path.", 113 | "required": true, 114 | "type": "string", 115 | "paramType": "path" 116 | }, 117 | { 118 | "name": "token", 119 | "description": "A project token.", 120 | "required": true, 121 | "type": "string", 122 | "paramType": "query" 123 | } 124 | ], 125 | "responseMessages": [ 126 | { 127 | "code": 400, 128 | "message": "Bad request: a parameter is missing or invalid.", 129 | "responseModel": "Error" 130 | }, 131 | { 132 | "code": 403, 133 | "message": "Unauthorized: the specified project token is invalid or expired or the module path corresponds to a system module.", 134 | "responseModel": "Error" 135 | }, 136 | { 137 | "code": 404, 138 | "message": "The specified module cannot be found.", 139 | "responseModel": "Error" 140 | }, 141 | { 142 | "code": 500, 143 | "message": "An internal error occurred during the processing of the request.", 144 | "responseModel": "Error" 145 | } 146 | ], 147 | "successMessages": [ 148 | { 149 | "code": 200, 150 | "message": "Returns module source code." 151 | } 152 | ], 153 | "examples": [ 154 | { 155 | "title": "Retrieving a project module source code", 156 | "description": "The following request retrieves the source code of the \"/io/myproject/module\" module defined in the myproject project.", 157 | "request": "curl -X GET \"http://myproject.28.io/v1/_modules/io/myproject/module?token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\"", 158 | "response": "< 200 OK\nmodule namespace m = \"http://myproject.io/module\"; declare function m:one() {1};" 159 | } 160 | ] 161 | }, 162 | { 163 | "method": "POST", 164 | "summary": "Creates a new project module", 165 | "notes": "

This method creates a new project module. If the compile option is none, the module will not be compiled, if it is lax it will be compiled and any potential compilation error reported. In this case, compilation errors will not prevent the module to be created. To only create the module if no compilation errors are present, set the compile option to strict. The default is lax.

It is not allowed to create a project module with the same path of an existing system or project module, even if the existing module has a different extension. If the operation succeeds any precompiled query depending on the updated module will be deleted.

", 166 | "type": "ModuleUpdate", 167 | "nickname": "createModule", 168 | "parameters": [ 169 | { 170 | "name": "module-path", 171 | "description": "The module path.", 172 | "required": true, 173 | "type": "string", 174 | "paramType": "path" 175 | }, 176 | { 177 | "name": "compile", 178 | "description": "The kind of compilation to perform. The default is \"lax\".", 179 | "required": false, 180 | "type": "string", 181 | "enum": [ 182 | "strict", 183 | "lax", 184 | "none" 185 | ], 186 | "paramType": "query" 187 | }, 188 | { 189 | "name": "extension", 190 | "description": "The new module extension. The default is \"jq\".", 191 | "required": false, 192 | "type": "string", 193 | "enum": [ 194 | "jq", 195 | "xq", 196 | "module" 197 | ], 198 | "paramType": "query" 199 | }, 200 | { 201 | "name": "token", 202 | "description": "A project token.", 203 | "required": true, 204 | "type": "string", 205 | "paramType": "query" 206 | }, 207 | { 208 | "name": "module-body", 209 | "description": "The source code of the module.", 210 | "required": false, 211 | "type": "string", 212 | "paramType": "body" 213 | }, 214 | { 215 | "name": "Content-Type", 216 | "paramType": "header", 217 | "enum": [ 218 | "text/plain; charset=utf-8" 219 | ], 220 | "type": "string" 221 | } 222 | 223 | ], 224 | "responseMessages": [ 225 | { 226 | "code": 400, 227 | "message": "Bad request: a parameter is missing or invalid.", 228 | "responseModel": "Error" 229 | }, 230 | { 231 | "code": 403, 232 | "message": "Unauthorized: the specified project token is invalid or expired or the module path corresponds to a system module.", 233 | "responseModel": "Error" 234 | }, 235 | { 236 | "code": 409, 237 | "message": "The specified module already exists.", 238 | "responseModel": "Error" 239 | }, 240 | { 241 | "code": 422, 242 | "message": "A compilation error occurred in strict mode.", 243 | "responseModel": "Error" 244 | }, 245 | { 246 | "code": 500, 247 | "message": "An internal error occurred during the processing of the request.", 248 | "responseModel": "Error" 249 | } 250 | ], 251 | "successMessages": [ 252 | { 253 | "code": 201, 254 | "message": "The module has been created." 255 | } 256 | ], 257 | "examples": [ 258 | { 259 | "title": "Creating a new module", 260 | "description": "The following request retrieves the source code of the \"/io/myproject/module\" module of the myproject project.", 261 | "request": "curl -X POST -H \"Content-Type: text/plain\" -d \"module namespace m = \\\"http://myproject.io/module\\\"; declare function m:one() {1};\" \"http://myproject.28.io/v1/_modules/io/myproject/module?token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\"", 262 | "response": "< 201 Created" 263 | } 264 | ] 265 | }, 266 | { 267 | "method": "PUT", 268 | "summary": "Creates or updates the specified project module", 269 | "notes": "

This method creates or updates a project module. If the compile option is none, the module will not be compiled, if it is lax it will be compiled and any potential compilation error reported. In this case, compilation errors will not prevent the module to be created or updated. To only create the module if no compilation errors are present, set the compile option to strict. The default is lax.

It is not allowed to update system modules. If the operation succeeds all existing modules with the same path, even with different extensions, are removed. Moreover, all precompiled queries depending on the updated module will be become non precompiled.

", 270 | "type": "ModuleUpdate", 271 | "nickname": "saveModule", 272 | "parameters": [ 273 | { 274 | "name": "module-path", 275 | "description": "The module path.", 276 | "required": true, 277 | "type": "string", 278 | "paramType": "path" 279 | }, 280 | { 281 | "name": "compile", 282 | "description": "The kind of compilation to perform. The default is \"lax\".", 283 | "required": false, 284 | "type": "string", 285 | "enum": [ 286 | "strict", 287 | "lax", 288 | "none" 289 | ], 290 | "paramType": "query" 291 | }, 292 | { 293 | "name": "extension", 294 | "description": "The new module extension. The default is \"jq\".", 295 | "required": false, 296 | "type": "string", 297 | "enum": [ 298 | "jq", 299 | "xq", 300 | "module" 301 | ], 302 | "paramType": "query" 303 | }, 304 | { 305 | "name": "token", 306 | "description": "A project token.", 307 | "required": true, 308 | "type": "string", 309 | "paramType": "query" 310 | }, 311 | { 312 | "name": "module-body", 313 | "description": "The module source code", 314 | "required": false, 315 | "type": "string", 316 | "paramType": "body" 317 | }, 318 | { 319 | "name": "Content-Type", 320 | "type": "string", 321 | "paramType": "header", 322 | "enum": [ 323 | "text/plain; charset=utf-8" 324 | ] 325 | } 326 | 327 | ], 328 | "responseMessages": [ 329 | { 330 | "code": 400, 331 | "message": "Bad request: a parameter is missing or invalid.", 332 | "responseModel": "Error" 333 | }, 334 | { 335 | "code": 403, 336 | "message": "Unauthorized: the specified project token is invalid or expired or the module path corresponds to a system module.", 337 | "responseModel": "Error" 338 | }, 339 | { 340 | "code": 404, 341 | "message": "The specified module cannot be found.", 342 | "responseModel": "Error" 343 | }, 344 | { 345 | "code": 422, 346 | "message": "A compilation error occurred in strict mode.", 347 | "responseModel": "Error" 348 | }, 349 | { 350 | "code": 500, 351 | "message": "An internal error occurred during the processing of the request.", 352 | "responseModel": "Error" 353 | } 354 | ], 355 | "successMessages": [ 356 | { 357 | "code": 200, 358 | "message": "The module has been saved." 359 | } 360 | ], 361 | "examples": [ 362 | { 363 | "title": "Saving a module", 364 | "description": "The following request saves the \"/io/myproject/module\" module in the myproject project.", 365 | "request": "curl -X POST -H \"Content-Type: text/plain\" -d \"module namespace m = \\\"http://myproject.io/module\\\"; declare function m:one() {1};\" \"http://myproject.28.io/_modules/io/myproject/module?token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\"", 366 | "response": "< 201 Created" 367 | }] 368 | }, 369 | { 370 | "method": "DELETE", 371 | "summary": "Removes the specified project module", 372 | "notes": "

This method removes the specified project module. If the operation succeeds any precompiled query depending on the updated module will be deleted.

", 373 | "type": "ModuleDelete", 374 | "nickname": "removeModule", 375 | "parameters": [ 376 | { 377 | "name": "module-path", 378 | "description": "The module path.", 379 | "required": true, 380 | "type": "string", 381 | "paramType": "path" 382 | }, 383 | { 384 | "name": "token", 385 | "description": "A project token.", 386 | "required": true, 387 | "type": "string", 388 | "paramType": "query" 389 | } 390 | ], 391 | "responseMessages": [ 392 | { 393 | "code": 400, 394 | "message": "Bad request: a parameter is missing or invalid.", 395 | "responseModel": "Error" 396 | }, 397 | { 398 | "code": 403, 399 | "message": "Unauthorized: the specified project token is invalid or expired or the module path corresponds to a system module.", 400 | "responseModel": "Error" 401 | }, 402 | { 403 | "code": 404, 404 | "message": "The specified module cannot be found.", 405 | "responseModel": "Error" 406 | }, 407 | { 408 | "code": 500, 409 | "message": "An internal error occurred during the processing of the request.", 410 | "responseModel": "Error" 411 | } 412 | ], 413 | "successMessages": [ 414 | { 415 | "code": 200, 416 | "message": "The module has been deleted." 417 | } 418 | ], 419 | "examples": [ 420 | { 421 | "title": "Deleting a module", 422 | "description": "The following request deletes the \"/io/myproject/module\" module from the myproject project.", 423 | "request": "curl -X DELETE \"http://myproject.28.io/v1/_modules/io/myproject/module?token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\"", 424 | "response": "< 200 OK" 425 | } 426 | ] 427 | } 428 | ] 429 | } 430 | ], 431 | "models": { 432 | "ModuleListing": { 433 | "id": "ModuleListing", 434 | "description": "A list of modules", 435 | "required": [], 436 | "properties": { 437 | "modulePath": { 438 | "type": "Module", 439 | "description": "A different field is present for each listed module. The name of the field is the module path" 440 | } 441 | } 442 | }, 443 | "Module": { 444 | "id": "Module", 445 | "description": "Module metadata", 446 | "required": [ 447 | "system" 448 | ], 449 | "properties": { 450 | "system": { 451 | "type": "boolean", 452 | "description": "Whether the module is provided by the system or defined in the project" 453 | }, 454 | "language": { 455 | "type": "string", 456 | "enum": [ 457 | "jsoniq", 458 | "xquery" 459 | ], 460 | "description": "The module language. This field is only present for non-system modules. If no language definition is contained in the module, \"xquery\" is assumed" 461 | }, 462 | "extension": { 463 | "type": "string", 464 | "enum": [ 465 | "jq", 466 | "xq", 467 | "module" 468 | ], 469 | "description": "The module extension. This field is only present for non-system modules." 470 | }, 471 | "lastModified": { 472 | "type": "date-time", 473 | "description": "The date and time the module was last modified. This field is only present for non-system modules" 474 | }, 475 | "hasNamespace": { 476 | "type": "boolean", 477 | "description": "Whether the module can be parsed without errors and contains a namespace declaration. This field is only present if the \"include-ns\" parameter has been set to true in the request" 478 | }, 479 | "namespace": { 480 | "type": "string", 481 | "description": "The module namespace, provided that the module can be parsed without errors and contains a namespace declaration. This field is only present if the \"include-ns\" parameter has been set to true in the request" 482 | }, 483 | "autoResolution": { 484 | "type": "boolean", 485 | "description": "Whether the module can be imported without location hints. This field is only present if the \"include-ns\" parameter has been set to true in the request" 486 | }, 487 | "source": { 488 | "type": "string", 489 | "description": "The module source code. This field is only present for non-system modules and if the \"include-src\" parameter has been set to true in the request" 490 | } 491 | } 492 | }, 493 | "ModuleUpdate": { 494 | "id": "ModuleUpdate", 495 | "description": "The result of the module compilation", 496 | "required": [ 497 | "success", 498 | "autoResolution", 499 | "removedCompiledQueries" 500 | ], 501 | "properties": { 502 | "success": { 503 | "type": "boolean", 504 | "enum": [ 505 | "true" 506 | ] 507 | }, 508 | "request_id": { 509 | "type": "string", 510 | "description": "The request identifier. Present only if the compilation fails" 511 | }, 512 | "autoResolution": { 513 | "type": "boolean", 514 | "description": "Whether the module can be imported without location hints" 515 | }, 516 | "compilationError" : { 517 | "type": "CompilationError", 518 | "description": "The compilation error occurred during the module compilation, if any" 519 | }, 520 | "removedCompiledQueries" : { 521 | "type": "array", 522 | "items": { "$ref": "string" }, 523 | "description": "The list of compiled queries which have been removed" 524 | } 525 | } 526 | }, 527 | "ModuleDelete": { 528 | "id": "ModuleDelete", 529 | "description": "The result of the module compilation", 530 | "required": [ 531 | "success" 532 | ], 533 | "properties": { 534 | "success": { 535 | "type": "boolean", 536 | "enum": [ 537 | "true" 538 | ] 539 | }, 540 | "removedCompiledQueries" : { 541 | "type": "array", 542 | "items": { "$ref": "string" }, 543 | "description": "The list of compiled queries which have been removed" 544 | } 545 | } 546 | }, 547 | "CompilationError": { 548 | "id": "CompilationError", 549 | "description": "Compilation error information", 550 | "required": [ 551 | "context", 552 | "message", 553 | "description" 554 | ], 555 | "properties": { 556 | "context": { 557 | "type": "string", 558 | "description": "The complete domain name of the project" 559 | }, 560 | "message": { 561 | "type": "string", 562 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 563 | }, 564 | "type": { 565 | "type": "string", 566 | "description": "For XQuery errors, the type of the error (e.g. static)" 567 | }, 568 | "code": { 569 | "type": "string", 570 | "description": "For XQuery errors, the error code" 571 | }, 572 | "location": { 573 | "type": "Location", 574 | "description": "For XQuery errors, the error location (if available)" 575 | } 576 | } 577 | }, 578 | "Success": { 579 | "id": "Success", 580 | "description": "Default success response", 581 | "required": [ 582 | "success" 583 | ], 584 | "properties": { 585 | "success": { 586 | "type": "boolean", 587 | "enum": [ 588 | "true" 589 | ] 590 | } 591 | } 592 | }, 593 | "Error": { 594 | "id": "Error", 595 | "description": "Error information", 596 | "required": [ 597 | "request_id", 598 | "context", 599 | "message", 600 | "description" 601 | ], 602 | "properties": { 603 | "request_id": { 604 | "type": "string", 605 | "description": "The request identifier" 606 | }, 607 | "context": { 608 | "type": "string", 609 | "description": "The complete domain name of the project" 610 | }, 611 | "message": { 612 | "type": "string", 613 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 614 | }, 615 | "type": { 616 | "type": "string", 617 | "description": "For XQuery errors, the type of the error (e.g. static)" 618 | }, 619 | "code": { 620 | "type": "string", 621 | "description": "For XQuery errors, the error code" 622 | }, 623 | "location": { 624 | "type": "Location", 625 | "description": "For XQuery errors, the error location (if available)" 626 | }, 627 | "stack-trace": { 628 | "type": "array", 629 | "items": { 630 | "$ref": "StackEntry" 631 | }, 632 | "description": "For XQuery errors, the error stack trace (if available)" 633 | } 634 | } 635 | }, 636 | "Location": { 637 | "id": "Location", 638 | "description": "Error information", 639 | "required": [ 640 | "module", 641 | "line-number", 642 | "line-number-end", 643 | "column-number", 644 | "column-number-end" 645 | ], 646 | "properties": { 647 | "module": { 648 | "type": "string", 649 | "description": "The error module" 650 | }, 651 | "line-number": { 652 | "type": "string", 653 | "description": "The error first line number" 654 | }, 655 | "line-number-end": { 656 | "type": "string", 657 | "description": "The error last line number" 658 | }, 659 | "column-number": { 660 | "type": "string", 661 | "description": "The error first column number" 662 | }, 663 | "column-number-end": { 664 | "type": "string", 665 | "description": "The error last column number" 666 | } 667 | } 668 | }, 669 | "StackEntry": { 670 | "id": "StackEntry", 671 | "description": "A stack entry", 672 | "required": [ 673 | "function", 674 | "location" 675 | ], 676 | "properties": { 677 | "function": { 678 | "type": "Function", 679 | "description": "The function of the call" 680 | }, 681 | "type": { 682 | "type": "Location", 683 | "description": "The location of the call" 684 | } 685 | } 686 | } 687 | } 688 | } 689 | -------------------------------------------------------------------------------- /tests/apis/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "1.0", 3 | "swaggerVersion": "1.2", 4 | "basePath": "http://portal.28.io", 5 | "resourcePath": "/auth", 6 | "produces": ["application/json"], 7 | "description": "

This OAuth2 compliant API can be used to authorize requests. The endpoint for these methods is http://portal.28.io/auth.

", 8 | "apis": [ 9 | { 10 | "path": "/auth", 11 | "operations": [ 12 | { 13 | "method": "POST", 14 | "summary": "Creates or refreshes authorization tokens", 15 | "notes": "

This OAuth2 compliant endpoint can be used both create new authorization tokens or to refresh an existing ones. There are three types of authorization tokens provided by this endpoint.

Access token
The access token is used to authorize requests on your 28.io account. These methods are currently unstable and are not documented yet.
Refresh Token
This token is used to renew the validity of your current authorization tokens.
Project token
This token is used to authorize requests to a 28.io project. For instance, the project token named myproject can be used to authorize any request to the http://myproject.28.io endpoint.

Any successful request to this endpoint will return the access, refresh, and project tokens.

To create new authorization tokens, the grant_type parameter must be set to client_credentials and the email. The password parameters must be specified as well.

To refresh the validity of your authorized tokens, the grant_type parameter must be set to refresh_token and the refresh_token parameter must be specified. In this scenario, new authorization tokens will be granted.

The format of the expiration date of a token is ISO 8601 compliant.

", 16 | "type": "Authentication", 17 | "nickname": "authenticate", 18 | "parameters": [ 19 | { 20 | "name": "grant_type", 21 | "description": "Authorization grant type. Use client_credentials to create a token or refresh_token to refresh a token", 22 | "required": true, 23 | "type": "string", 24 | "enum": ["client_credentials", "refresh_token"], 25 | "paramType": "query" 26 | }, 27 | { 28 | "name": "email", 29 | "description": "The account email. Mandatory if grant_type=client_credentials.", 30 | "required": false, 31 | "type": "string", 32 | "paramType": "query" 33 | }, 34 | { 35 | "name": "password", 36 | "description": "The account password. Mandatory if grant_type=client_credentials.", 37 | "required": false, 38 | "type": "string", 39 | "paramType": "query" 40 | }, 41 | { 42 | "name": "refresh_token", 43 | "description": "The refresh_token obtained in the last successful request to this endpoint. Mandatory if grant_type=refresh_token.", 44 | "required": false, 45 | "type": "string", 46 | "paramType": "query" 47 | } 48 | ], 49 | "successMessages": [ 50 | { 51 | "code": 200, 52 | "message": "New authorization tokens have been granted.", 53 | "responseModel": "Authentication" 54 | } 55 | ], 56 | "responseMessages": [ 57 | { 58 | "code": 400, 59 | "message": "Bad request: a parameter is missing or invalid.", 60 | "responseModel": "Error" 61 | }, 62 | { 63 | "code": 403, 64 | "message": "The specified password or refresh token is invalid.", 65 | "responseModel": "Error" 66 | }, 67 | { 68 | "code": 409, 69 | "message": "The specified user has not been confirmed.", 70 | "responseModel": "Error" 71 | }, 72 | { 73 | "code": 404, 74 | "message": "The specified account cannot be found.", 75 | "responseModel": "Error" 76 | }, 77 | { 78 | "code": 500, 79 | "message": "An internal error occurred during the processing of the request.", 80 | "responseModel": "Error" 81 | } 82 | ], 83 | "examples": [ 84 | { 85 | "title": "Granting a new authorization token", 86 | "description": "The following request creates a new authorization token.", 87 | "request": "curl -X POST http://portal.28.io/auth?email=my@email.com&password=mypassword&grant_type=client_credentials", 88 | "response": "< 200 OK\n{\n \"token_type\" : \"bearer\",\n \"expiration_date\" : \"2013-11-06T01:54:29.83945Z\",\n \"access_token\" : \"dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\",\n \"refresh_token\" : \"dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\",\n \"project_tokens\" : {\n \"project_zurich\" : \"QllXcXM3OHV4N2IweitabGZKeExkZVBreGZJPToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==\" },\n \"accountMetadata\" : {\n \"id\" : \"1\",\n \"email\" : \"my@email.com\",\n \"firstname\" : \"John\",\n \"lastname\" : \"Smith\",\n \"company\" : \"Acme\",\n \"createdAt\" : \"2013-10-31T15:26:12.702226Z\",\n \"type\" : \"free\",\n \"confirmedAt\" : \"2013-10-31T15:26:31.081456Z\"\n },\n \"projectsMetadata\" : [{\n \"name\" : \"zurich\",\n \"created\" : \"2013-10-31T15:59:55.117077Z\",\n \"package\" : \"free\",\n \"status\" : \"active\",\n \"avatarKey\" : 8,\n \"version\" : \"1.0\",\n \"sausaVesion\" : \"2.8.0\",\n \"lastUpdated\" : \"2013-10-31T15:59:55.117077Z\",\n \"lastModified\" : \"2013-10-31T15:59:55.117077Z\",\n \"databaseType\" : \"28msec\"\n }]\n}" 89 | } 90 | , 91 | { 92 | "title": "Refreshing an authorization token", 93 | "description": "The following request refreshes the validity of an authorization token.", 94 | "request": "curl -X POST http://portal.28.io/auth?refresh_token=dGZKZ0VYT1VWV2l1R1BLQ1NBTjVmcEJlWU04PToyMDEzLTExLTA2VDAxOjU0OjI5LjgzOTQ1Wg==&grant_type=refresh_token", 95 | "response": "< 200 OK\n{\n \"token_type\" : \"bearer\",\n \"expiration_date\" : \"2013-11-06T02:17:50.769451Z\",\n \"access_token\" : \"ejRISUlaY2FFNUs2dGZOQ0FrTnRRY2hRVkNnPToyMDEzLTExLTA2VDAyOjE3OjUwLjc2OTQ1MVo=\",\n \"refresh_token\" : \"ejRISUlaY2FFNUs2dGZOQ0FrTnRRY2hRVkNnPToyMDEzLTExLTA2VDAyOjE3OjUwLjc2OTQ1MVo=\",\n \"project_tokens\" : {\n \"project_zurich\" : \"aUhxKzRJRU1VbHhwWS9OWGNsS2lDdXpKVXNrPToyMDEzLTExLTA2VDAyOjE3OjUwLjc2OTQ1MVo=\"\n }\n}" 96 | } 97 | ] 98 | } 99 | ] 100 | } 101 | ], 102 | "models": { 103 | "Error": { 104 | "id": "Error", 105 | "description": "Error information", 106 | "required": [ 107 | "code", 108 | "message" 109 | ], 110 | "properties": { 111 | "code": { 112 | "type": "string", 113 | "description": "The XQuery error code of the error" 114 | }, 115 | "message": { 116 | "type": "string", 117 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 118 | }, 119 | "description": { 120 | "type": "string", 121 | "description": "The error description" 122 | }, 123 | "module": { 124 | "type": "string", 125 | "description": "The error module" 126 | }, 127 | "line-number": { 128 | "type": "string", 129 | "description": "The error line number" 130 | }, 131 | "column-number": { 132 | "type": "string", 133 | "description": "The error column number" 134 | } 135 | } 136 | }, 137 | "Authentication": { 138 | "id": "Authentication", 139 | "description": "Authentication data", 140 | "required": [ 141 | "token_type", 142 | "expiration_date", 143 | "access_token", 144 | "refresh_token", 145 | "project_tokens" 146 | ], 147 | "properties": { 148 | "token_type": { 149 | "type": "string", 150 | "description": "The API token type", 151 | "enum": [ 152 | "bearer" 153 | ] 154 | }, 155 | "expiration_date": { 156 | "type": "date-time", 157 | "description": "The expiration date of all the tokens in the response" 158 | }, 159 | "access_token": { 160 | "type": "string", 161 | "description": "The API token" 162 | }, 163 | "refresh_token": { 164 | "type": "string", 165 | "description": "The refresh token which can be used to refresh both the API and project tokens" 166 | }, 167 | "project_tokens": { 168 | "type": "ProjectTokens", 169 | "description": "The project tokens which can be used to make request to the APIs on the project endpoints" 170 | } 171 | } 172 | }, 173 | "ProjectTokens": { 174 | "id": "ProjectTokens", 175 | "description": "An object containing zero or more project tokens. Each project token is stored in a different field project_{name}, where {name} is the project name, (e.g.: portal).", 176 | "properties": { 177 | "project_{name}": { 178 | "type": "string", 179 | "description": "The token for the project {name}. One field for each project owned by the account will be present" 180 | } 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /tests/apis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "1.0", 3 | "swaggerVersion": "1.2", 4 | "basePath": "http://api.xbrl.io", 5 | "resourcePath": "/packages", 6 | "produces": [ 7 | "application/json" 8 | ], 9 | "apis": [ 10 | { 11 | "path": "/package", 12 | "operations": [ 13 | { 14 | "method": "GET", 15 | "summary": "Lists available packages", 16 | "notes": "This method requires no authentication.", 17 | "type": "array", 18 | "items": { 19 | "$ref": "Package" 20 | }, 21 | "nickname": "listPackages", 22 | "parameters": [], 23 | "responseMessages": [ 24 | { 25 | "code": 500, 26 | "message": "An internal error occurred during the processing of the request.", 27 | "responseModel": "Error" 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | ], 34 | "models": { 35 | "Error": { 36 | "id": "Error", 37 | "description": "Error information", 38 | "required": [ 39 | "code", 40 | "message", 41 | "description" 42 | ], 43 | "properties": { 44 | "code": { 45 | "type": "string", 46 | "description": "The XQuery error code of the error" 47 | }, 48 | "message": { 49 | "type": "string", 50 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 51 | }, 52 | "description": { 53 | "type": "string", 54 | "description": "The error description" 55 | }, 56 | "module": { 57 | "type": "string", 58 | "description": "The error module" 59 | }, 60 | "line-number": { 61 | "type": "string", 62 | "description": "The error line number" 63 | }, 64 | "column-number": { 65 | "type": "string", 66 | "description": "The error column number" 67 | } 68 | } 69 | }, 70 | "Package": { 71 | "id": "Package", 72 | "description": "A package", 73 | "required": [ 74 | "id", 75 | "name", 76 | "demo", 77 | "description" 78 | ], 79 | "properties": { 80 | "id": { 81 | "type": "string", 82 | "description": "The package id" 83 | }, 84 | "name": { 85 | "type": "string", 86 | "description": "The package name" 87 | }, 88 | "demo": { 89 | "type": "boolean", 90 | "description": "Whether the package is a demo package or not" 91 | }, 92 | "description": { 93 | "type": "string", 94 | "description": "The package description" 95 | }, 96 | "price": { 97 | "type": "string", 98 | "description": "The package price" 99 | }, 100 | "details" : { 101 | "type": "object", 102 | "description": "The package details" 103 | } 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /tests/apis/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "1.0", 3 | "swaggerVersion": "1.2", 4 | "basePath": "http://api.xbrl.io", 5 | "resourcePath": "/ping", 6 | "produces": ["application/json"], 7 | "apis": [ 8 | { 9 | "path": "/ping", 10 | "operations":[ 11 | { 12 | "method": "GET", 13 | "summary": "Checks if the service is available.", 14 | "type": "Success", 15 | "nickname": "ping" 16 | } 17 | ] 18 | } 19 | ], 20 | "models": { 21 | "Success": { 22 | "id": "Success", 23 | "description": "Default success response", 24 | "required": ["success"], 25 | "properties": { 26 | "success": { 27 | "type": "boolean", 28 | "enum": [ 29 | "true" 30 | ] 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/apis/protected.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.0.1", 5 | "title": "title" 6 | }, 7 | "host": "portal.28.io", 8 | "basePath": "/api", 9 | "schemes": [ 10 | "http" 11 | ], 12 | "securityDefinitions": { 13 | "oauth2": { 14 | "type": "oauth2", 15 | "scopes": {}, 16 | "flow": "password", 17 | "tokenUrl": "" 18 | } 19 | }, 20 | "paths": { 21 | "/auth": { 22 | "post": { 23 | "description": "Get token", 24 | "operationId": "auth", 25 | "parameters": [ 26 | { 27 | "name": "grant_type", 28 | "description": "Authorization grant type. Use client_credentials to create a token or refresh_token to refresh a token", 29 | "required": true, 30 | "type": "string", 31 | "enum": ["client_credentials", "refresh_token"], 32 | "in": "query" 33 | }, 34 | { 35 | "name": "email", 36 | "description": "The account email. Mandatory if grant_type=client_credentials.", 37 | "required": false, 38 | "type": "string", 39 | "in": "query" 40 | }, 41 | { 42 | "name": "password", 43 | "description": "The account password. Mandatory if grant_type=client_credentials.", 44 | "required": false, 45 | "type": "string", 46 | "in": "query" 47 | }, 48 | { 49 | "name": "refresh_token", 50 | "description": "The refresh_token obtained in the last successful request to this endpoint. Mandatory if grant_type=refresh_token.", 51 | "required": false, 52 | "type": "string", 53 | "in": "query" 54 | } 55 | ], 56 | "responses": { 57 | "200": { 58 | "description": "Token", 59 | "schema": { 60 | "$ref": "#/definitions/Authentication" 61 | } 62 | }, 63 | "security": [ 64 | { 65 | "oauth2": [] 66 | } 67 | ], 68 | "401": { 69 | "description": "Unauthorized", 70 | "schema": { 71 | "$ref": "#/definitions/Error" 72 | } 73 | }, 74 | "default": { 75 | "description": "Error", 76 | "schema": { 77 | "$ref": "#/definitions/Error" 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | "/project": { 84 | "get": { 85 | "description": "Get secure", 86 | "operationId": "getSecure", 87 | "parameters": [ 88 | { 89 | "name": "token", 90 | "description": "Auth token", 91 | "required": false, 92 | "type": "string", 93 | "in": "query" 94 | } 95 | ], 96 | "security": [ 97 | { 98 | "oauth2": [] 99 | } 100 | ], 101 | "responses": { 102 | "200": { 103 | "description": "Secure response returned" 104 | }, 105 | "401": { 106 | "description": "Unauthorized" 107 | } 108 | } 109 | } 110 | } 111 | }, 112 | "definitions": { 113 | "Authentication": { 114 | "required": [ 115 | "token_type", 116 | "expiration_date", 117 | "access_token", 118 | "refresh_token", 119 | "project_tokens" 120 | ], 121 | "properties": { 122 | "token_type": { 123 | "type": "string", 124 | "description": "The API token type", 125 | "enum": [ 126 | "bearer" 127 | ] 128 | }, 129 | "expiration_date": { 130 | "type": "date-time", 131 | "description": "The expiration date of all the tokens in the response" 132 | }, 133 | "access_token": { 134 | "type": "string", 135 | "description": "The API token" 136 | }, 137 | "refresh_token": { 138 | "type": "string", 139 | "description": "The refresh token which can be used to refresh both the API and project tokens" 140 | }, 141 | "project_tokens": { 142 | "type": "ProjectTokens", 143 | "description": "The project tokens which can be used to make request to the APIs on the project endpoints" 144 | } 145 | } 146 | }, 147 | "Error": { 148 | "required": [ 149 | "code", 150 | "message" 151 | ], 152 | "properties": { 153 | "code": { 154 | "type": "string", 155 | "description": "The XQuery error code of the error" 156 | }, 157 | "message": { 158 | "type": "string", 159 | "description": "A formatted string which contain the error code (always) and the module name, line and column-number and error description (when available)" 160 | }, 161 | "description": { 162 | "type": "string", 163 | "description": "The error description" 164 | }, 165 | "module": { 166 | "type": "string", 167 | "description": "The error module" 168 | }, 169 | "line-number": { 170 | "type": "string", 171 | "description": "The error line number" 172 | }, 173 | "column-number": { 174 | "type": "string", 175 | "description": "The error column number" 176 | } 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /tests/apis/ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.0.0", 5 | "title": "" 6 | }, 7 | "parameters": { 8 | "id": { 9 | "name": "id", 10 | "in": "query", 11 | "description": "id", 12 | "required": true, 13 | "type": "string" 14 | } 15 | }, 16 | "paths": { 17 | "/persons": { 18 | "parameters": [ 19 | { 20 | "$ref": "#/parameters/id" 21 | } 22 | ], 23 | "get": { 24 | "description": "Gets `Person` object.", 25 | "responses": { 26 | "200": { 27 | "description": "Successful response", 28 | "schema": { 29 | "type": "object", 30 | "properties": { 31 | "name": { 32 | "type": "string" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/apis/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.0.0", 5 | "title": "myApp" 6 | }, 7 | "basePath": "/api", 8 | "consumes": ["application/json", "application/x-www-form-urlencoded", "application/xml", "text/xml"], 9 | "produces": ["application/json", "application/xml", "text/xml", "application/javascript", "text/javascript"], 10 | "paths": { 11 | 12 | "/Users/{id}": { 13 | "head": { 14 | "tags": ["User"], 15 | "summary": "Check whether a model instance exists in the data source.", 16 | "externalDocs": { 17 | "description": "More info on the WIKI", 18 | "url": "https://example.atlassian.net/wiki/pages/example" 19 | }, 20 | "operationId": "User.exists__head_Users_{id}", 21 | "parameters": [{ 22 | "name": "id", 23 | "in": "path", 24 | "description": "Model id", 25 | "required": true, 26 | "type": "string", 27 | "format": "JSON" 28 | }], 29 | "responses": { 30 | "200": { 31 | "description": "Request was successful", 32 | "schema": { 33 | "type": "object" 34 | } 35 | } 36 | }, 37 | "deprecated": false 38 | } 39 | } 40 | }, 41 | "definitions": { 42 | "xany": { 43 | "properties": {} 44 | }, 45 | "ObjectID": { 46 | "type": "string", 47 | "pattern": "^[a-fA-F\\d]{24}$" 48 | }, 49 | "User": { 50 | "properties": { 51 | "username": { 52 | "type": "string" 53 | }, 54 | "id": { 55 | "type": "number", 56 | "format": "double" 57 | } 58 | }, 59 | "additionalProperties": false 60 | } 61 | }, 62 | "tags": [{ 63 | "name": "User" 64 | }] 65 | } 66 | -------------------------------------------------------------------------------- /tests/apis/uber.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Uber API", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "api.uber.com", 9 | "schemes": [ 10 | "https" 11 | ], 12 | "basePath": "/v1", 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "parameters": { 17 | "id": { 18 | "name": "id", 19 | "in": "path", 20 | "type": "integer", 21 | "format": "int32", 22 | "required": true 23 | } 24 | }, 25 | "paths": { 26 | "/products": { 27 | "get": { 28 | "summary": "Product Types", 29 | "description": "The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order.", 30 | "parameters": [ 31 | { 32 | "name": "latitude", 33 | "in": "query", 34 | "description": "Latitude component of location.", 35 | "required": true, 36 | "type": "number", 37 | "format": "double" 38 | }, 39 | { 40 | "name": "longitude", 41 | "in": "query", 42 | "description": "Longitude component of location.", 43 | "required": true, 44 | "type": "number", 45 | "format": "double" 46 | }, 47 | { 48 | "name": "format", 49 | "in": "query", 50 | "x-exclude-from-bindings": true 51 | } 52 | ], 53 | "tags": [ 54 | "Products" 55 | ], 56 | "responses": { 57 | "200": { 58 | "description": "An array of products", 59 | "schema": { 60 | "type": "array", 61 | "items": { 62 | "$ref": "Product" 63 | } 64 | } 65 | }, 66 | "default": { 67 | "description": "Unexpected error", 68 | "schema": { 69 | "$ref": "Error" 70 | } 71 | } 72 | } 73 | } 74 | }, 75 | "/products/{id}": { 76 | "get": { 77 | "summary": "Product Types", 78 | "description": "The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order.", 79 | "parameters": [ 80 | { 81 | "$ref": "id" 82 | }, 83 | { 84 | "name": "latitude", 85 | "in": "query", 86 | "description": "Latitude component of location.", 87 | "required": true, 88 | "type": "number", 89 | "format": "double" 90 | }, 91 | { 92 | "name": "longitude", 93 | "in": "query", 94 | "description": "Longitude component of location.", 95 | "required": true, 96 | "type": "number", 97 | "format": "double" 98 | } 99 | ], 100 | "tags": [ 101 | "Products" 102 | ], 103 | "responses": { 104 | "200": { 105 | "description": "An array of products", 106 | "schema": { 107 | "type": "array", 108 | "items": { 109 | "$ref": "Product" 110 | } 111 | } 112 | }, 113 | "default": { 114 | "description": "Unexpected error", 115 | "schema": { 116 | "$ref": "Error" 117 | } 118 | } 119 | } 120 | } 121 | }, 122 | "/estimates/price": { 123 | "get": { 124 | "summary": "Price Estimates", 125 | "description": "The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.

The response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier.", 126 | "parameters": [ 127 | { 128 | "name": "start_latitude", 129 | "in": "query", 130 | "description": "Latitude component of start location.", 131 | "required": true, 132 | "type": "number", 133 | "format": "double" 134 | }, 135 | { 136 | "name": "start_longitude", 137 | "in": "query", 138 | "description": "Longitude component of start location.", 139 | "required": true, 140 | "type": "number", 141 | "format": "double" 142 | }, 143 | { 144 | "name": "end_latitude", 145 | "in": "query", 146 | "description": "Latitude component of end location.", 147 | "required": true, 148 | "type": "number", 149 | "format": "double" 150 | }, 151 | { 152 | "name": "end_longitude", 153 | "in": "query", 154 | "description": "Longitude component of end location.", 155 | "required": true, 156 | "type": "number", 157 | "format": "double" 158 | } 159 | ], 160 | "tags": [ 161 | "Estimates" 162 | ], 163 | "responses": { 164 | "200": { 165 | "description": "An array of price estimates by product", 166 | "schema": { 167 | "type": "array", 168 | "items": { 169 | "$ref": "PriceEstimate" 170 | } 171 | } 172 | }, 173 | "default": { 174 | "description": "Unexpected error", 175 | "schema": { 176 | "$ref": "Error" 177 | } 178 | } 179 | } 180 | } 181 | }, 182 | "/estimates/time": { 183 | "get": { 184 | "summary": "Time Estimates", 185 | "description": "The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs.", 186 | "parameters": [ 187 | { 188 | "name": "start_latitude", 189 | "in": "query", 190 | "description": "Latitude component of start location.", 191 | "required": true, 192 | "type": "number", 193 | "format": "double" 194 | }, 195 | { 196 | "name": "start_longitude", 197 | "in": "query", 198 | "description": "Longitude component of start location.", 199 | "required": true, 200 | "type": "number", 201 | "format": "double" 202 | }, 203 | { 204 | "name": "customer_uuid", 205 | "in": "query", 206 | "type": "string", 207 | "format": "uuid", 208 | "description": "Unique customer identifier to be used for experience customization." 209 | }, 210 | { 211 | "name": "product_id", 212 | "in": "query", 213 | "type": "string", 214 | "description": "Unique identifier representing a specific product for a given latitude & longitude." 215 | } 216 | ], 217 | "tags": [ 218 | "Estimates" 219 | ], 220 | "responses": { 221 | "200": { 222 | "description": "An array of products", 223 | "schema": { 224 | "type": "array", 225 | "items": { 226 | "$ref": "Product" 227 | } 228 | } 229 | }, 230 | "default": { 231 | "description": "Unexpected error", 232 | "schema": { 233 | "$ref": "Error" 234 | } 235 | } 236 | } 237 | } 238 | }, 239 | "/me": { 240 | "get": { 241 | "summary": "User Profile", 242 | "description": "The User Profile endpoint returns information about the Uber user that has authorized with the application.", 243 | "tags": [ 244 | "User" 245 | ], 246 | "responses": { 247 | "200": { 248 | "description": "Profile information for a user", 249 | "schema": { 250 | "$ref": "Profile" 251 | } 252 | }, 253 | "default": { 254 | "description": "Unexpected error", 255 | "schema": { 256 | "$ref": "Error" 257 | } 258 | } 259 | } 260 | } 261 | }, 262 | "/history": { 263 | "get": { 264 | "summary": "User Activity", 265 | "description": "The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary.", 266 | "parameters": [ 267 | { 268 | "name": "offset", 269 | "in": "query", 270 | "type": "integer", 271 | "format": "int32", 272 | "description": "Offset the list of returned results by this amount. Default is zero." 273 | }, 274 | { 275 | "name": "limit", 276 | "in": "query", 277 | "type": "integer", 278 | "format": "int32", 279 | "description": "Number of items to retrieve. Default is 5, maximum is 100." 280 | } 281 | ], 282 | "tags": [ 283 | "User" 284 | ], 285 | "responses": { 286 | "200": { 287 | "description": "History information for the given user", 288 | "schema": { 289 | "$ref": "Activities" 290 | } 291 | }, 292 | "default": { 293 | "description": "Unexpected error", 294 | "schema": { 295 | "$ref": "Error" 296 | } 297 | } 298 | } 299 | } 300 | } 301 | }, 302 | "definitions": { 303 | "Product": { 304 | "properties": { 305 | "product_id": { 306 | "type": "string", 307 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." 308 | }, 309 | "description": { 310 | "type": "string", 311 | "description": "Description of product." 312 | }, 313 | "display_name": { 314 | "type": "string", 315 | "description": "Display name of product." 316 | }, 317 | "capacity": { 318 | "type": "string", 319 | "description": "Capacity of product. For example, 4 people." 320 | }, 321 | "image": { 322 | "type": "string", 323 | "description": "Image URL representing the product." 324 | } 325 | } 326 | }, 327 | "PriceEstimate": { 328 | "properties": { 329 | "product_id": { 330 | "type": "string", 331 | "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" 332 | }, 333 | "currency_code": { 334 | "type": "string", 335 | "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 336 | }, 337 | "display_name": { 338 | "type": "string", 339 | "description": "Display name of product." 340 | }, 341 | "estimate": { 342 | "type": "string", 343 | "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." 344 | }, 345 | "low_estimate": { 346 | "type": "number", 347 | "description": "Lower bound of the estimated price." 348 | }, 349 | "high_estimate": { 350 | "type": "number", 351 | "description": "Upper bound of the estimated price." 352 | }, 353 | "surge_multiplier": { 354 | "type": "number", 355 | "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." 356 | } 357 | } 358 | }, 359 | "Profile": { 360 | "properties": { 361 | "first_name": { 362 | "type": "string", 363 | "description": "First name of the Uber user." 364 | }, 365 | "last_name": { 366 | "type": "string", 367 | "description": "Last name of the Uber user." 368 | }, 369 | "email": { 370 | "type": "string", 371 | "description": "Email address of the Uber user" 372 | }, 373 | "picture": { 374 | "type": "string", 375 | "description": "Image URL of the Uber user." 376 | }, 377 | "promo_code": { 378 | "type": "string", 379 | "description": "Promo code of the Uber user." 380 | } 381 | } 382 | }, 383 | "Activity": { 384 | "properties": { 385 | "uuid": { 386 | "type": "string", 387 | "description": "Unique identifier for the activity" 388 | } 389 | } 390 | }, 391 | "Activities": { 392 | "properties": { 393 | "offset": { 394 | "type": "integer", 395 | "format": "int32", 396 | "description": "Position in pagination." 397 | }, 398 | "limit": { 399 | "type": "integer", 400 | "format": "int32", 401 | "description": "Number of items to retrieve (100 max)." 402 | }, 403 | "count": { 404 | "type": "integer", 405 | "format": "int32", 406 | "description": "Total number of items available." 407 | }, 408 | "history": { 409 | "type": "array", 410 | "$ref": "Activity" 411 | } 412 | } 413 | }, 414 | "Error": { 415 | "properties": { 416 | "code": { 417 | "type": "integer", 418 | "format": "int32" 419 | }, 420 | "message": { 421 | "type": "string" 422 | }, 423 | "fields": { 424 | "type": "string" 425 | } 426 | } 427 | } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /tests/apis/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.0.0", 5 | "title": "" 6 | }, 7 | "paths": { 8 | "/users/{userId}": { 9 | "parameters": [ 10 | { 11 | "name": "userId", 12 | "in": "path", 13 | "description": "User's id", 14 | "required": true, 15 | "type": "string" 16 | } 17 | ], 18 | "get": { 19 | "description": "Get user", 20 | "operationId": "findById", 21 | "responses": { 22 | "200": { 23 | "description": "User returned", 24 | "schema": { 25 | "type": "object", 26 | "properties": { 27 | "name": { 28 | "type": "string" 29 | } 30 | } 31 | } 32 | }, 33 | "404": { 34 | "description": "User not found" 35 | } 36 | } 37 | }, 38 | "delete": { 39 | "description": "Delete user", 40 | "operationId": "delete", 41 | "responses": { 42 | "200": { 43 | "description": "User deleted" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/generation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var vows = require('vows'); 5 | var fs = require('fs'); 6 | var ffs = require('final-fs'); 7 | var ts = require('typescript'); 8 | var tmp = require('tmp'); 9 | 10 | var CodeGen = require('../lib/codegen').CodeGen; 11 | 12 | function compileString(testName, input) { 13 | var tmpDir = tmp.dirSync({ 14 | dir: './', 15 | unsafeCleanup: true, 16 | keep: true 17 | }); 18 | var tmpFile = tmp.fileSync({ 19 | postfix: '.ts', 20 | dir: tmpDir.name, 21 | keep: true 22 | }); 23 | fs.writeFileSync(tmpFile.fd, input); 24 | 25 | var program = ts.createProgram([tmpFile.name], { 26 | module: ts.ModuleKind.CommonJS, 27 | target: ts.ScriptTarget.ES2016, // Makes promises resolve 28 | moduleResolution: ts.ModuleResolutionKind.NodeJs // ensure we can use node_modules 29 | }); 30 | var emitResult = program.emit(); 31 | 32 | var allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); 33 | 34 | allDiagnostics.forEach(function(diagnostic) { 35 | var lineAndCharacter = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); 36 | var line = lineAndCharacter.line; 37 | var character = lineAndCharacter.character; 38 | var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 39 | var outputLine = diagnostic.file.text.split('\n')[line]; 40 | console.log('\n' + testName + ': (' + (line + 1) + ',' + (character + 1) + '): ' + message); 41 | console.log(' ERROR line: ' + outputLine.trim()); 42 | }); 43 | 44 | var errorsSeen = allDiagnostics.length !== 0; 45 | if (errorsSeen) { 46 | console.log(' ERRORS seen, generated code preserved in: ' + tmpFile.name); 47 | } else { 48 | tmpFile.removeCallback(); 49 | tmpDir.removeCallback(); 50 | } 51 | return !errorsSeen; 52 | } 53 | 54 | var batch = {}; 55 | var list = ffs.readdirSync('tests/apis'); 56 | list.forEach(function(file){ 57 | file = 'tests/apis/' + file; 58 | batch[file] = function(){ 59 | var swagger = JSON.parse(fs.readFileSync(file, 'UTF-8')); 60 | var result = CodeGen.getNodeCode({ 61 | className: 'Test', 62 | swagger: swagger 63 | }); 64 | assert(typeof(result), 'string'); 65 | result = CodeGen.getReactCode({ 66 | moduleName: 'Test', 67 | className: 'Test', 68 | swagger: swagger 69 | }); 70 | assert(typeof(result), 'string'); 71 | result = CodeGen.getAngularCode({ 72 | moduleName: 'Test', 73 | className: 'Test', 74 | swagger: swagger 75 | }); 76 | assert(typeof(result), 'string'); 77 | result = CodeGen.getAngularCode({ 78 | moduleName: 'Test', 79 | className: 'Test', 80 | swagger: swagger, 81 | lint: false, 82 | beautify: false 83 | }); 84 | assert(typeof(result), 'string'); 85 | assert(typeof(result), 'string'); 86 | if(swagger.swagger === '2.0') { 87 | result = CodeGen.getTypescriptCode({ 88 | moduleName: 'Test', 89 | className: 'Test', 90 | swagger: swagger, 91 | lint: false 92 | }); 93 | assert(compileString('typescript generation: ' + file, result), 'typescript compilation failed'); 94 | assert(typeof(result), 'string'); 95 | } 96 | result = CodeGen.getCustomCode({ 97 | moduleName: 'Test', 98 | className: 'Test', 99 | swagger: swagger, 100 | template: { 101 | class: fs.readFileSync(__dirname + '/../templates/angular-class.mustache', 'utf-8'), 102 | method: fs.readFileSync(__dirname + '/../templates/method.mustache', 'utf-8') 103 | } 104 | }); 105 | assert(typeof(result), 'string'); 106 | }; 107 | }); 108 | vows.describe('Test Generation').addBatch(batch).export(module); 109 | --------------------------------------------------------------------------------