├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── generators ├── app │ ├── index.js │ └── templates │ │ ├── .eslintignore │ │ ├── .eslintrc │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── express │ │ └── server.js │ │ ├── hapi │ │ └── server.js │ │ ├── package.json │ │ └── restify │ │ └── server.js ├── data │ ├── index.js │ └── templates │ │ ├── data.js │ │ ├── mockgen.js │ │ └── security.js ├── handler │ ├── index.js │ └── templates │ │ ├── express │ │ └── handler.js │ │ ├── hapi │ │ └── handler.js │ │ └── restify │ │ └── handler.js ├── prompt.js └── test │ ├── index.js │ └── templates │ ├── express │ └── test.js │ ├── hapi │ └── test.js │ └── restify │ └── test.js ├── lib ├── routegen.js └── util │ └── index.js ├── package-lock.json ├── package.json └── test ├── app.js ├── data.js ├── fixture ├── petstore.json ├── petstore_no_security.json └── uber.yaml ├── handler.js ├── test.js └── util ├── index.js └── testsuite.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | templates 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 4 6 | ], 7 | "quotes": [ 8 | 2, 9 | "single" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [ 16 | 2, 17 | "always" 18 | ] 19 | }, 20 | "env": { 21 | "es6": true, 22 | "node": true 23 | }, 24 | "extends": "eslint:recommended" 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.orig 9 | 10 | work 11 | build 12 | pids 13 | logs 14 | results 15 | coverage 16 | lib-cov 17 | html-report 18 | xunit.xml 19 | node_modules 20 | npm-debug.log 21 | 22 | .project 23 | .idea 24 | .settings 25 | .iml 26 | *.sublime-workspace 27 | *.sublime-project 28 | 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | .AppleDouble 34 | .LSOverride 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | test/temp 39 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Automatically ignored per: 2 | # https://www.npmjs.org/doc/developers.html#Keeping-files-out-of-your-package 3 | # 4 | # .*.swp 5 | # ._* 6 | # .DS_Store 7 | # .git 8 | # .hg 9 | # .lock-wscript 10 | # .svn 11 | # .wafpickle-* 12 | # CVS 13 | # npm-debug.log 14 | # node_modules 15 | 16 | *.seed 17 | *.log 18 | *.csv 19 | *.dat 20 | *.out 21 | *.pid 22 | *.gz 23 | *.orig 24 | 25 | work 26 | build 27 | ./test 28 | pids 29 | logs 30 | results 31 | coverage 32 | lib-cov 33 | html-report 34 | xunit.xml 35 | 36 | .project 37 | .idea 38 | .settings 39 | .iml 40 | *.sublime-workspace 41 | *.sublime-project 42 | 43 | ehthumbs.db 44 | Icon? 45 | Thumbs.db 46 | .AppleDouble 47 | .LSOverride 48 | .Spotlight-V100 49 | .Trashes 50 | 51 | test/temp 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | - "8" 6 | branches: 7 | only: 8 | - master 9 | script: 10 | - "npm run cover" 11 | - "npm run lint" 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v4.1.0 2 | 3 | - Use `hapi-openapi` instead of `swaggerize-hapi`. 4 | 5 | # v4.0.0 6 | 7 | - Support latest swaggerize-hapi. 8 | - [BREAKING] drop support for 0.12. 9 | 10 | # v3.1.0 11 | 12 | - Add the useful meta information, generator version, to the generated app's package.json. 13 | - Add `security` option by default to the unit test files generated for all the frameworks. 14 | 15 | # v3.0.0 16 | 17 | ## Breaking changes 18 | - `models` are not generated anymore, instead `data` providers are generated. Data providers use `mock` responses by default. 19 | 20 | ## Enhancements and Bug fixes 21 | - new yeoman infrastructure 22 | - #80 - Use swagger parser to parse and validate swagger 2.0 spec 23 | - #61, #54 - Support to define Reference Object as a Relative Schema File 24 | - #17 - Pass the swagger source file as a CLI option 25 | - #49 - apiPath options assumes that the path is local. Remote file paths are not allowed as this CLI option 26 | - #51 - Generator won't handle enums 27 | - #8 - Issue with allOf in swagger definitions 28 | - #66 - Generated handler sets a `501` status code and generated tests check for `200` status code 29 | - #72 - Deprecate warning using generated code : body-parser deprecated bodyParser() usage 30 | 31 | # v2.0.2 32 | 33 | ## Bug fixes 34 | - #55 35 | - Minor fixes and upgrades - #57, #63, #64, #65 36 | - #12 37 | - #69 38 | - #73 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 PayPal 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-swaggerize 2 | 3 | [![Build Status](https://travis-ci.org/krakenjs/generator-swaggerize.svg?branch=master)](https://travis-ci.org/krakenjs/generator-swaggerize) 4 | [![NPM version](https://badge.fury.io/js/generator-swaggerize.png)](http://badge.fury.io/js/generator-swaggerize) 5 | 6 | 7 | Yeoman generator for swagger application with `swaggerize` tools. 8 | 9 | Generates projects for: 10 | - Express 11 | - Hapi 12 | - Restify 13 | 14 | See also: 15 | - [swaggerize-express](https://github.com/krakenjs/swaggerize-express) 16 | - [hapi-openapi](https://github.com/krakenjs/hapi-openapi) (formerly `swaggerize-hapi`) 17 | 18 | ### Usage 19 | 20 | Install yeoman's `yo` if you haven't already: 21 | 22 | ``` 23 | $ npm install -g yo 24 | ``` 25 | 26 | Install `generator-swaggerize`: 27 | 28 | ``` 29 | $ npm install -g generator-swaggerize 30 | ``` 31 | 32 | Create a project: 33 | 34 | ``` 35 | $ yo swaggerize 36 | ``` 37 | 38 | ### Generators 39 | 40 | - `yo swaggerize` 41 | 42 | Generates a new swaggerize application 43 | 44 | ``` 45 | 46 | $ yo swaggerize 47 | 48 | Swaggerize Generator 49 | Tell us a bit about your application 50 | ? Path (or URL) to swagger document: http://petstore.swagger.io/v2/swagger.json 51 | ? Framework: express 52 | ? What would you like to call this project: myapp 53 | ? Your name: Lorem Ipsum 54 | ? Your github user name: loremipsum 55 | ? Your email: loremipsum@awesome.com 56 | create .eslintrc 57 | create .gitignore 58 | create .npmignore 59 | create package.json 60 | create README.md 61 | . 62 | . 63 | . 64 | ``` 65 | 66 | If you want to generate (or regenerate) only a specific component, you can use `swaggerize` sub generators. 67 | 68 | - `yo swaggerize:data` 69 | 70 | Generates `data` providers based on `paths` and `responses` in swagger api document. 71 | This also generates the `config/swagger.json` (A copy of the swagger api document file input) and `security` authorize handlers based on `securityDefinitions`. 72 | 73 | - `yo swaggerize:handler` 74 | 75 | Generates `handlers` based on `paths` in swagger api document. (`data` providers are also generated as a pre step) 76 | 77 | - `yo swaggerize:test` 78 | 79 | Generates unit `tests` based on `paths`, `parameters` and `responses` in swagger api document. (`handlers` and `data` providers are also generated as a pre step) 80 | 81 | #### Project structure 82 | 83 | - `/config` - A copy of the swagger api document file input, will be generated at `/config/swagger.json`. 84 | - `/data` - Data providers for paths(routes). 85 | - `/security` - Authorize handlers for security schemes declared by `securityDefinitions`. 86 | - `/handlers` - Application paths (routes) based on swagger api `paths`. 87 | - `/tests` - Unit tests for paths(routes). 88 | 89 | Example: 90 | 91 | ``` 92 | ├── README.md 93 | ├── .eslintrc 94 | ├── .gitignore 95 | ├── .npmignore 96 | ├── config 97 | │ └── swagger.json 98 | ├── data 99 | │ ├── mockgen.js 100 | │ └── hellopath 101 | │ └── {id}.js 102 | ├── handlers 103 | │ └── hellopath 104 | │ └── {id}.js 105 | ├── package.json 106 | ├── security 107 | │ ├── hello_Oauth2.js 108 | │ └── hello_api_key.js 109 | ├── server.js 110 | └── tests 111 | └── hellopath 112 | └── {id}.js 113 | ``` 114 | 115 | ##### Handlers 116 | 117 | A handler file will be generated corresponding to every a `path` definition of the swagger api (`paths`). 118 | 119 | More details or handlers and routing: 120 | 121 | [swaggerize-express handlers](https://github.com/krakenjs/swaggerize-express#handlers-directory) 122 | 123 | [swaggerize-hapi handlers](https://github.com/krakenjs/swaggerize-hapi#handlers-directory) 124 | 125 | ##### Data providers 126 | 127 | A data file will be generated corresponding to every a `path` definition of the swagger api (`paths`). 128 | 129 | By default [Response Mock generator](https://github.com/subeeshcbabu/swagmock#responses) is used to provide the data based on the `responses` definition of swagger api. 130 | Developers should replace these default mock data generators with actual data feeds, based on the functionality. 131 | 132 | ##### Security authorize handlers 133 | 134 | A security authorize handler file will be generated corresponding to the declaration of the security schemes `securityDefinitions`. 135 | 136 | ##### Unit tests 137 | 138 | A unit test file will be generated corresponding to every a `path` definition of the swagger api (`paths`). 139 | 140 | By default [Request Mock generator](https://github.com/subeeshcbabu/swagmock#requests) is used to generator api requests based on the `parameters` definition of swagger api. 141 | 142 | #### CLI Options 143 | 144 | - `--framework` - specify the framework (`hapi`, `express`, or `restify`). 145 | - `--apiPath` - specify the path to the swagger document. 146 | - `--handlerPath` - specify the path to generate the handler files. By default `handlers` directory. 147 | - `--dataPath` - specify the path to generate the data files. By default `data` directory. 148 | - `--securityPath` - specify the path to generate the security authorize files. By default `security` directory. 149 | - `--testPath` - specify the path to generate the unit test files. By default `tests` directory. 150 | - `--skip-npm-install` - To skip the default `npm install` on the generated project. 151 | 152 | #### Prompts 153 | 154 | - `apiPath` - Path (or URL) to swagger document 155 | 156 | The path to the swagger api document. This path could be a local or remote URL. 157 | 158 | If there is no CLI option `--apiPath` specified, the generator will prompt for `apiPath`. The swagger api will be validated against the swagger schema and spec before proceeding with scaffolding process. 159 | 160 | - `framework` - The choice of framework to generate the application. 161 | 162 | There are three options - `express`, `hapi` and `restify`. If there is no CLI option `--framework` specified, the generator will prompt for `framework`. 163 | 164 | Also, generator checks the working directory for `package.json` dependencies, to find out whether the application already depends on, one of the framework options. If a match is found, that framework will be used as an option without prompting for the value. 165 | 166 | - `appName` - The name of the application 167 | 168 | By default the yeoman project root will be used as the name of the application, however, the prompt lets developers change this default. 169 | 170 | - `creatorName`, `githubUser` and `email` - Creator details to build the `package.json`. 171 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Generators = require('yeoman-generator'); 3 | var Path = require('path'); 4 | var _ = require('underscore.string'); 5 | var Util = require('../../lib/util'); 6 | var Prompt = require('../prompt'); 7 | 8 | module.exports = Generators.Base.extend({ 9 | constructor: function () { 10 | Generators.Base.apply(this, arguments); 11 | this.option('framework'); 12 | this.option('apiPath'); 13 | this.option('handlerPath'); 14 | this.option('testPath'); 15 | this.option('dataPath'); 16 | this.option('skip-npm-install'); 17 | }, 18 | initializing: { 19 | helloMsg: function () { 20 | this.log('Swaggerize Generator'); 21 | this.log('Tell us a bit about your application'); 22 | }, 23 | //Validate the apiPath option in the beginning itself. Prompt for user input if the option is an invalid path. 24 | apiPath: function () { 25 | var done = this.async(); 26 | this.apiPath = this.options.apiPath; 27 | if (this.apiPath) { 28 | Util.validateApi(this, done); 29 | } else { 30 | done(); 31 | } 32 | }, 33 | setDefaults: function () { 34 | Util.setDefaults(this); 35 | } 36 | }, 37 | prompting: function () { 38 | var done = this.async(); 39 | this.prompt(Prompt('app', this), function (answers) { 40 | var self = this; 41 | Object.keys(answers).forEach(function (prop) { 42 | if (answers[prop] !== null && answers[prop] !== undefined) { 43 | self[prop] = answers[prop]; 44 | } 45 | }); 46 | //parse and validate the Swagger API entered by the user. 47 | if (answers.apiPath) { 48 | Util.updateConfigPath(self); 49 | Util.validateApi(self, done); 50 | } else { 51 | done(); 52 | } 53 | }.bind(this)); 54 | }, 55 | configuring: function () { 56 | var oldRoot = this.destinationRoot(); 57 | //Set the destination root based on appname. 58 | if (this.appName && Path.basename(oldRoot) !== this.appName) { 59 | this.destinationRoot(Path.join(oldRoot, this.appName)); 60 | //Reset the defaults 61 | Util.setDefaults(this); 62 | } 63 | this.slugAppName = _.slugify(this.appName); 64 | }, 65 | writing: { 66 | app: function () { 67 | var self = this; 68 | //lint config and ignore files 69 | this.fs.copy( 70 | this.templatePath('.*'), 71 | this.destinationPath() 72 | ); 73 | //Package and Docs 74 | ['package.json', 'README.md'].forEach(function (file) { 75 | self.fs.copyTpl( 76 | self.templatePath(file), 77 | self.destinationPath(file), 78 | self 79 | ); 80 | }); 81 | //Server file 82 | this.fs.copyTpl( 83 | this.templatePath(Path.join(this.framework, 'server.js')), 84 | this.destinationPath('server.js'), 85 | this 86 | ); 87 | }, 88 | tests: function () { 89 | /** 90 | * Invoke the 'swaggerize:test' sub generator. 91 | * `test` will create `handlers`, `data` and `config` 92 | */ 93 | this.composeWith('swaggerize:test', { 94 | options: { 95 | api: this.api, 96 | refApi: this.refApi, 97 | apiPath: this.apiPath, 98 | apiConfigPath: this.apiConfigPath, 99 | handlerPath: this.handlerPath, 100 | testPath: this.testPath, 101 | dataPath: this.dataPath, 102 | securityPath: this.securityPath, 103 | framework: this.framework, 104 | securith: this.security 105 | } 106 | }, { 107 | local: require.resolve('../test') 108 | }); 109 | } 110 | }, 111 | install: function () { 112 | if (this.options['skip-npm-install']) { 113 | return; 114 | } 115 | this.npmInstall(); 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /generators/app/templates/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | templates 3 | -------------------------------------------------------------------------------- /generators/app/templates/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 4 6 | ], 7 | "quotes": [ 8 | 2, 9 | "single" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [ 16 | 2, 17 | "always" 18 | ] 19 | }, 20 | "env": { 21 | "es6": true, 22 | "node": true 23 | }, 24 | "extends": "eslint:recommended" 25 | } 26 | -------------------------------------------------------------------------------- /generators/app/templates/.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.orig 9 | 10 | work 11 | build 12 | pids 13 | logs 14 | results 15 | coverage 16 | lib-cov 17 | html-report 18 | xunit.xml 19 | node_modules 20 | npm-debug.log 21 | 22 | .project 23 | .idea 24 | .settings 25 | .iml 26 | *.sublime-workspace 27 | *.sublime-project 28 | 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | .AppleDouble 34 | .LSOverride 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | test/temp 39 | -------------------------------------------------------------------------------- /generators/app/templates/.npmignore: -------------------------------------------------------------------------------- 1 | # Automatically ignored per: 2 | # https://www.npmjs.org/doc/developers.html#Keeping-files-out-of-your-package 3 | # 4 | # .*.swp 5 | # ._* 6 | # .DS_Store 7 | # .git 8 | # .hg 9 | # .lock-wscript 10 | # .svn 11 | # .wafpickle-* 12 | # CVS 13 | # npm-debug.log 14 | # node_modules 15 | 16 | *.seed 17 | *.log 18 | *.csv 19 | *.dat 20 | *.out 21 | *.pid 22 | *.gz 23 | *.orig 24 | 25 | work 26 | build 27 | test 28 | pids 29 | logs 30 | results 31 | coverage 32 | lib-cov 33 | html-report 34 | xunit.xml 35 | 36 | .project 37 | .idea 38 | .settings 39 | .iml 40 | *.sublime-workspace 41 | *.sublime-project 42 | 43 | ehthumbs.db 44 | Icon? 45 | Thumbs.db 46 | .AppleDouble 47 | .LSOverride 48 | .Spotlight-V100 49 | .Trashes 50 | 51 | test/temp 52 | -------------------------------------------------------------------------------- /generators/app/templates/README.md: -------------------------------------------------------------------------------- 1 | # <%= slugAppName %> 2 | 3 | Swagger api [location](<%= apiPathRel.replace(/\\/g,'/') %>) 4 | -------------------------------------------------------------------------------- /generators/app/templates/express/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Http = require('http'); 4 | var Express = require('express'); 5 | var BodyParser = require('body-parser'); 6 | var Swaggerize = require('swaggerize-express'); 7 | var Path = require('path'); 8 | 9 | var App = Express(); 10 | 11 | var Server = Http.createServer(App); 12 | 13 | App.use(BodyParser.json()); 14 | App.use(BodyParser.urlencoded({ 15 | extended: true 16 | })); 17 | 18 | App.use(Swaggerize({ 19 | api: Path.resolve('<%=apiPathRel.replace(/\\/g,'/')%>'), 20 | handlers: Path.resolve('<%=handlerPath.replace(/\\/g,'/')%>')<%if (security) {%>, 21 | security: Path.resolve('<%=securityPath.replace(/\\/g,'/')%>')<%}%> 22 | })); 23 | 24 | Server.listen(8000, function () { 25 | App.swagger.api.host = this.address().address + ':' + this.address().port; 26 | /* eslint-disable no-console */ 27 | console.log('App running on %s:%d', this.address().address, this.address().port); 28 | /* eslint-disable no-console */ 29 | }); 30 | -------------------------------------------------------------------------------- /generators/app/templates/hapi/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('hapi'); 4 | const HapiOpenAPI = require('hapi-openapi'); 5 | const Path = require('path'); 6 | 7 | const init = async function() { 8 | const server = new Hapi.Server(); 9 | 10 | await server.register({ 11 | plugin: HapiOpenAPI, 12 | options: { 13 | api: Path.resolve('<%=apiPathRel.replace(/\\/g,'/')%>'), 14 | handlers: Path.resolve('<%=handlerPath.replace(/\\/g,'/')%>') 15 | } 16 | }); 17 | 18 | await server.start(); 19 | 20 | return server; 21 | }; 22 | 23 | init().then((server) => { 24 | server.plugins.openapi.setHost(server.info.host + ':' + server.info.port); 25 | 26 | server.log(['info'], `Server running on ${server.info.host}:${server.info.port}`); 27 | }); 28 | -------------------------------------------------------------------------------- /generators/app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= slugAppName %>", 3 | "description": "", 4 | "version": "1.0.0", 5 | "author": "<%= creatorName %> <<%= email %>>", 6 | "contributors": [], 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/<%= githubUser %>/<%= slugAppName %>.git" 10 | }, 11 | "bugs": "http://github.com/<%= githubUser %>/<%= slugAppName %>/issues", 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org" 14 | }, 15 | "dependencies": {<% if (framework === 'express') {%> 16 | "express": "^4.0.0", 17 | "body-parser": "^1.15.0", 18 | "swaggerize-express": "^4.0.0"<%}%><% if (framework === 'hapi') {%> 19 | "hapi": "^17.0.0", 20 | "boom": "^7.1.1", 21 | "hapi-openapi": "^1.0.0"<%}%><% if (framework === 'restify') {%> 22 | "swaggerize-restify": "^2.0.0", 23 | "restify": "^3.0.3"<%}%><% if (framework !== 'hapi') {%>, 24 | "swagmock": "~0.0.2"<%}%> 25 | }, 26 | "devDependencies": { 27 | "eslint": "^2", 28 | "tape": "^4", 29 | "istanbul": "~0.4.3",<% if (framework !== 'hapi') {%> 30 | "is-my-json-valid": "^2.13.1", 31 | "js-yaml": "^3.2.6", 32 | "supertest": "^1.2.0", 33 | "swagger-parser": "^3.4.1"<%}%><% if (framework === 'hapi') {%> 34 | "swagmock": "~0.0.2"<%}%> 35 | }, 36 | "scripts": { 37 | "test": "tape 'tests/**/*.js'", 38 | "cover": "istanbul cover tape -- 'tests/**/*.js'", 39 | "lint": "eslint .", 40 | "regenerate": "yo swaggerize:test --framework <%=framework%> --apiPath '<%=apiPathRel.replace(/\\/g,'/')%>'" 41 | }, 42 | "generator-swaggerize": { 43 | "version": "<%=generatorVersion%>" 44 | }, 45 | "main": "./server" 46 | } 47 | -------------------------------------------------------------------------------- /generators/app/templates/restify/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Restify = require('restify'); 4 | var Swaggerize = require('swaggerize-restify'); 5 | var Path = require('path'); 6 | 7 | var Server = Restify.createServer(); 8 | 9 | Server.use(Restify.bodyParser()); 10 | Server.use(Restify.queryParser()); 11 | 12 | Server.get('/api', function (req, res) { 13 | res.send(200); 14 | }); 15 | 16 | Swaggerize(Server, { 17 | api: Path.resolve('<%=apiPathRel.replace(/\\/g,'/')%>'), 18 | handlers: Path.resolve('<%=handlerPath.replace(/\\/g,'/')%>')<%if (security) {%>, 19 | security: Path.resolve('<%=securityPath.replace(/\\/g,'/')%>')<%}%> 20 | }); 21 | 22 | Server.listen(8000, function () { 23 | Server.swagger.api.host = Server.address().address + ':' + Server.address().port; 24 | /* eslint-disable no-console */ 25 | console.log('App running on %s:%d', Server.address().address, Server.address().port); 26 | /* eslint-disable no-console */ 27 | }); 28 | -------------------------------------------------------------------------------- /generators/data/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Generators = require('yeoman-generator'); 3 | var Path = require('path'); 4 | var Util = require('../../lib/util'); 5 | var RouteGen = require('../../lib/routegen'); 6 | var Prompt = require('../prompt'); 7 | var JsYaml = require('js-yaml'); 8 | 9 | module.exports = Generators.Base.extend({ 10 | constructor: function () { 11 | Generators.Base.apply(this, arguments); 12 | /* 13 | * Options : 14 | * --apiPath 15 | * --modelPath 16 | */ 17 | this.option('apiPath'); 18 | this.option('dataPath'); 19 | }, 20 | initializing: { 21 | //Validate the apiPath option in the beginning itself. Prompt for user input if the option is an invalid path. 22 | apiPath: function () { 23 | var done = this.async(); 24 | this.apiPath = this.options.apiPath; 25 | this.refApi = this.options.refApi; 26 | this.api = this.options.api; 27 | if ((!this.api || !this.refApi) && this.apiPath) { 28 | //If API is not passed as an option and the apiPath is valid, then, validate the api Spec. 29 | Util.validateApi(this, done); 30 | return; 31 | } 32 | done(); 33 | }, 34 | setDefaults: function () { 35 | Util.setDefaults(this); 36 | } 37 | }, 38 | prompting: function () { 39 | var done = this.async(); 40 | this.prompt(Prompt('data', this), function (answers) { 41 | var self = this; 42 | Object.keys(answers).forEach(function (prop) { 43 | if (answers[prop] !== null && answers[prop] !== undefined) { 44 | self[prop] = answers[prop]; 45 | } 46 | }); 47 | //parse and validate the Swagger API entered by the user. 48 | if (answers.apiPath) { 49 | Util.updateConfigPath(self); 50 | Util.validateApi(self, done); 51 | } else { 52 | done(); 53 | } 54 | }.bind(this)); 55 | }, 56 | writing: { 57 | config: function () { 58 | var apiContent; 59 | //Write to local config file only if the API is already validated 60 | //Dereferenced and resolved $ref objects cannot be used in the local copy. 61 | //So use `parse` API and then stringify the Objects to json format. 62 | if(this.refApi) { 63 | //Write the contents of the apiPath location to local config file. 64 | if (this.ymlApi) { 65 | apiContent = JsYaml.dump(this.refApi); 66 | } else { 67 | apiContent = JSON.stringify(this.refApi, null, 4); 68 | } 69 | this.write(this.apiConfigPath, apiContent); 70 | } 71 | }, 72 | security: function () { 73 | var self = this; 74 | var def = this.api.securityDefinitions; 75 | var securityPath; 76 | if (def && Object.keys(def).length > 0) { 77 | //Generate authorize handlers for securityDefinitions 78 | Object.keys(def).forEach(function (defName) { 79 | securityPath = Path.join(self.securityPath, defName + '.js'); 80 | self.fs.copyTpl( 81 | self.templatePath('security.js'), 82 | self.destinationPath(securityPath), 83 | { 84 | name: defName, 85 | type: def[defName].type, 86 | description: def[defName].description 87 | } 88 | ); 89 | }); 90 | } 91 | }, 92 | mockgen: function () { 93 | var tmpl = { 94 | apiConfigPath: Util.relative(this.destinationPath(this.mockgenPath), this.apiConfigPath) 95 | }; 96 | this.fs.copyTpl( 97 | this.templatePath('mockgen.js'), 98 | this.destinationPath(this.mockgenPath), 99 | tmpl 100 | ); 101 | }, 102 | data: function () { 103 | var self = this; 104 | var paths = this.api.paths; 105 | if (paths) { 106 | Object.keys(paths).forEach(function (path) { 107 | var pathStr = path.replace(/^\/|\/$/g, ''); 108 | var dataPath = Path.join(self.dataPath, pathStr + '.js'); 109 | var pathObj = paths[path]; 110 | var route; 111 | //Set the genFilePath path 112 | self.genFilePath = self.destinationPath(dataPath); 113 | //Generate the route template obj. 114 | route = RouteGen(self, path, pathObj); 115 | //Generate the data files. 116 | if (route.operations && route.operations.length > 0) { 117 | self.fs.copyTpl( 118 | self.templatePath('data.js'), 119 | self.genFilePath, 120 | route 121 | ); 122 | } 123 | }); 124 | } 125 | } 126 | } 127 | }); 128 | -------------------------------------------------------------------------------- /generators/data/templates/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Mockgen = require('<%=mockgenPath.replace(/\\/g,'/')%>'); 3 | /** 4 | * Operations on <%=path%> 5 | */ 6 | module.exports = { 7 | <%operations.forEach(function (operation, i) 8 | {%>/** 9 | * summary: <%=operation.summary%> 10 | * description: <%=operation.description%> 11 | * parameters: <%=operation.parameters%> 12 | * produces: <%=operation.produces%> 13 | * responses: <%=operation.responses.join(', ')%> 14 | * operationId: <%=operation.name%> 15 | */ 16 | <%=operation.method%>: { 17 | <%operation.responses && operation.responses.forEach(function (response, i) 18 | {%><%=response%>: function (req, res, callback) { 19 | /** 20 | * Using mock data generator module. 21 | * Replace this by actual data for the api. 22 | */ 23 | Mockgen().responses({ 24 | path: '<%=path%>', 25 | operation: '<%=operation.method%>', 26 | response: '<%=response%>' 27 | }, callback); 28 | }<%if (i < operation.responses.length - 1) {%>, 29 | <%}%><%})%> 30 | }<%if (i < operations.length - 1) {%>, 31 | <%}%><%});%> 32 | }; 33 | -------------------------------------------------------------------------------- /generators/data/templates/mockgen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Swagmock = require('swagmock'); 3 | var Path = require('path'); 4 | var apiPath = Path.resolve(__dirname, '<%=apiConfigPath.replace(/\\/g,'/')%>'); 5 | var mockgen; 6 | 7 | module.exports = function () { 8 | /** 9 | * Cached mock generator 10 | */ 11 | mockgen = mockgen || Swagmock(apiPath); 12 | return mockgen; 13 | }; 14 | -------------------------------------------------------------------------------- /generators/data/templates/security.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Authorize function for securityDefinitions:<%=name%> 4 | * type : <%=type%> 5 | * description: <%=description%> 6 | */ 7 | module.exports = function authorize(req, res, next) { 8 | //The context('this') for authorize will be bound to the 'securityDefinition' 9 | <%if (type === 'oauth2') { 10 | %>//this.authorizationUrl - The authorization URL for securityDefinitions:<%=name%> 11 | //this.scopes - The available scopes for the securityDefinitions:<%=name%> security scheme 12 | //this.flow - The flow used by the securityDefinitions:<%=name%> OAuth2 security scheme 13 | 14 | //req.requiredScopes - list of scope names required for the execution (defined as part of security requirement object). 15 | <%} else if (type === 'apiKey') { 16 | %>//this.name - The name of the header or query parameter to be used for securityDefinitions:<%=name%> apiKey security scheme. 17 | //this.in - The location of the API key ("query" or "header") for securityDefinitions:<%=name%> apiKey security scheme. 18 | <%}%> 19 | 20 | //Perform auth here 21 | 22 | next(); 23 | }; 24 | -------------------------------------------------------------------------------- /generators/handler/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Generators = require('yeoman-generator'); 3 | var Path = require('path'); 4 | var Util = require('../../lib/util'); 5 | var Frameworks = Util.Frameworks; 6 | var RouteGen = require('../../lib/routegen'); 7 | var Prompt = require('../prompt'); 8 | 9 | module.exports = Generators.Base.extend({ 10 | constructor: function () { 11 | Generators.Base.apply(this, arguments); 12 | /* 13 | * Options : 14 | * --framework 15 | * --apiPath 16 | * --handlerPath 17 | */ 18 | this.option('framework'); 19 | this.option('apiPath'); 20 | this.option('handlerPath'); 21 | }, 22 | initializing: { 23 | //Validate the apiPath option in the beginning itself. Prompt for user input if the option is an invalid path. 24 | apiPath: function () { 25 | var done = this.async(); 26 | this.apiPath = this.options.apiPath; 27 | this.api = this.options.api; 28 | this.refApi = this.options.refApi; 29 | if ((!this.api || !this.refApi) && this.apiPath) { 30 | //If API is not passed as an option and the apiPath is valid, then, validate the api Spec. 31 | Util.validateApi(this, done); 32 | return; 33 | } 34 | done(); 35 | }, 36 | setDefaults: function () { 37 | Util.setDefaults(this); 38 | } 39 | }, 40 | prompting: function () { 41 | var done = this.async(); 42 | this.prompt(Prompt('handler', this), function (answers) { 43 | var self = this; 44 | Object.keys(answers).forEach(function (prop) { 45 | if (answers[prop] !== null && answers[prop] !== undefined) { 46 | self[prop] = answers[prop]; 47 | } 48 | }); 49 | //parse and validate the Swagger API entered by the user. 50 | if (answers.apiPath) { 51 | Util.updateConfigPath(self); 52 | Util.validateApi(self, done); 53 | } else { 54 | done(); 55 | } 56 | }.bind(this)); 57 | }, 58 | configuring: function () { 59 | var done = this.async(); 60 | if (Frameworks.indexOf(this.framework) === -1) { 61 | done(new Error('Invalid framework ' + this.framework + '. Framework should be one of these : ' + Frameworks)); 62 | } else { 63 | done(); 64 | } 65 | }, 66 | writing: { 67 | data: function () { 68 | this.composeWith('swaggerize:data', { 69 | options: { 70 | api: this.api, 71 | refApi: this.refApi, 72 | apiPath: this.apiPath, 73 | dataPath: this.dataPath, 74 | securityPath: this.securityPath, 75 | apiConfigPath: this.apiConfigPath 76 | } 77 | }, { 78 | local: require.resolve('../data') 79 | }); 80 | }, 81 | handlers: function () { 82 | var self = this; 83 | var paths = this.api.paths; 84 | if (paths) { 85 | Object.keys(paths).forEach(function (path) { 86 | var pathStr = path.replace(/^\/|\/$/g, ''); 87 | var handlerPath = Path.join(self.handlerPath, pathStr + '.js'); 88 | var pathObj = paths[path]; 89 | var route; 90 | /* 91 | * Schema Extensions for Handlers: (x-handler) 92 | * An alternative to automatically determining handlers based on a directory structure, 93 | * handlers can be specified using x-handler 94 | */ 95 | if (pathObj['x-handler']) { 96 | handlerPath = pathObj['x-handler']; 97 | } 98 | //Set the genFilePath path 99 | self.genFilePath = self.destinationPath(handlerPath); 100 | //Generate the route template obj. 101 | route = RouteGen(self, path, pathObj); 102 | 103 | if (route.operations && route.operations.length > 0) { 104 | self.fs.copyTpl( 105 | self.templatePath(Path.join(self.framework, 'handler.js')), 106 | self.genFilePath, 107 | route 108 | ); 109 | } 110 | }); 111 | } 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /generators/handler/templates/express/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var dataProvider = require('<%=dataPath.replace(/\\/g,'/')%>'); 3 | /** 4 | * Operations on <%=path%> 5 | */ 6 | module.exports = { 7 | <%operations.forEach(function (operation, i) 8 | {%>/** 9 | * summary: <%=operation.summary%> 10 | * description: <%=operation.description%> 11 | * parameters: <%=operation.parameters%> 12 | * produces: <%=operation.produces%> 13 | * responses: <%=operation.responses.join(', ')%> 14 | */ 15 | <%=operation.method%>: function <%=operation.name%>(req, res, next) { 16 | <%if (operation.responses.length > 0) { 17 | var resp = operation.responses[0]; 18 | var statusStr = (resp === 'default') ? 200 : resp; 19 | %>/** 20 | * Get the data for response <%=resp%> 21 | * For response `default` status 200 is used. 22 | */ 23 | var status = <%=statusStr%>; 24 | var provider = dataProvider['<%=operation.method%>']['<%=resp%>']; 25 | provider(req, res, function (err, data) { 26 | if (err) { 27 | next(err); 28 | return; 29 | } 30 | res.status(status).send(data && data.responses); 31 | });<%} else {%> 32 | var status = 501; 33 | var data = {}; 34 | res.status(status).send(data); 35 | <%}%> 36 | }<%if (i < operations.length - 1) {%>, 37 | <%}%><%});%> 38 | }; 39 | -------------------------------------------------------------------------------- /generators/handler/templates/hapi/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | 5 | /** 6 | * Operations on <%=path%> 7 | */ 8 | module.exports = { 9 | <%operations.forEach(function (operation, i) 10 | {%>/** 11 | * summary: <%=operation.summary%> 12 | * description: <%=operation.description%> 13 | * parameters: <%=operation.parameters%> 14 | * produces: <%=operation.produces%> 15 | * responses: <%=operation.responses.join(', ')%> 16 | */ 17 | <%=operation.method%>: function <%=operation.name%>(request, h) { 18 | return Boom.notImplemented(); 19 | }<%if (i < operations.length - 1) {%>, 20 | <%}%><%});%> 21 | }; 22 | -------------------------------------------------------------------------------- /generators/handler/templates/restify/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var dataProvider = require('<%=dataPath.replace(/\\/g,'/')%>'); 3 | /** 4 | * Operations on <%=path%> 5 | */ 6 | module.exports = { 7 | <%operations.forEach(function (operation, i) 8 | {%>/** 9 | * summary: <%=operation.summary%> 10 | * description: <%=operation.description%> 11 | * parameters: <%=operation.parameters%> 12 | * produces: <%=operation.produces%> 13 | * responses: <%=operation.responses.join(', ')%> 14 | */ 15 | <%=operation.method%>: function <%=operation.name%>(req, res, next) { 16 | <%if (operation.responses.length > 0) { 17 | var resp = operation.responses[0]; 18 | var statusStr = (resp === 'default') ? 200 : resp; 19 | %>/** 20 | * Get the data for response <%=resp%> 21 | * For response `default` status 200 is used. 22 | */ 23 | var status = <%=statusStr%>; 24 | var provider = dataProvider['<%=operation.method%>']['<%=resp%>']; 25 | provider(req, res, function (err, data) { 26 | if (err) { 27 | next(err); 28 | return; 29 | } 30 | res.send(status, data && data.responses); 31 | next(); 32 | });<%} else {%> 33 | var status = 501; 34 | var data = {}; 35 | res.send(status, data); 36 | next(); 37 | <%}%> 38 | }<%if (i < operations.length - 1) {%>, 39 | <%}%><%});%> 40 | }; 41 | -------------------------------------------------------------------------------- /generators/prompt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Util = require('../lib/util'); 3 | var Frameworks = Util.Frameworks; 4 | 5 | module.exports = function prompt(name, generator) { 6 | 7 | var validate = function (propName) { 8 | return !!propName; 9 | }; 10 | 11 | var apiPath = [{ 12 | name: 'apiPath', 13 | message: 'Path (or URL) to swagger document:', 14 | required: true, 15 | when: function () { 16 | return !generator.apiPath; 17 | }, 18 | default: generator.apiPath, 19 | validate: validate 20 | }]; 21 | 22 | var framework = [{ 23 | type: 'list', 24 | name: 'framework', 25 | message: 'Framework:', 26 | default: generator.framework, 27 | when: function () { 28 | return !generator.framework; 29 | }, 30 | choices: Frameworks.map(function (framework) { 31 | return { 32 | name: framework, 33 | value: framework 34 | }; 35 | }) 36 | }]; 37 | 38 | var app = [ 39 | { 40 | name: 'appName', 41 | message: 'What would you like to call this project:', 42 | default: generator.appName, // Default to current folder name 43 | validate: validate 44 | }, 45 | { 46 | name: 'creatorName', 47 | message: 'Your name:', 48 | validate: validate 49 | }, 50 | { 51 | name: 'githubUser', 52 | message: 'Your github user name:', 53 | validate: validate 54 | }, 55 | { 56 | name: 'email', 57 | message: 'Your email:', 58 | validate: validate 59 | } 60 | ]; 61 | 62 | var propmts = { 63 | data : function () { 64 | return apiPath; 65 | }, 66 | handler: function () { 67 | return apiPath.concat(framework); 68 | }, 69 | test: function () { 70 | return apiPath.concat(framework); 71 | }, 72 | app: function () { 73 | return apiPath.concat(framework).concat(app); 74 | } 75 | }; 76 | 77 | return propmts[name](); 78 | }; 79 | -------------------------------------------------------------------------------- /generators/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Generators = require('yeoman-generator'); 3 | var Path = require('path'); 4 | var Util = require('../../lib/util'); 5 | var Frameworks = Util.Frameworks; 6 | var RouteGen = require('../../lib/routegen'); 7 | var Prompt = require('../prompt'); 8 | 9 | module.exports = Generators.Base.extend({ 10 | constructor: function () { 11 | Generators.Base.apply(this, arguments); 12 | /* 13 | * Options : 14 | * --framework 15 | * --apiPath 16 | * --handlerPath 17 | * --testPath 18 | */ 19 | this.option('framework'); 20 | this.option('apiPath'); 21 | this.option('handlerPath'); 22 | this.option('testPath'); 23 | }, 24 | initializing: { 25 | //Validate the apiPath option in the beginning itself. Prompt for user input if the option is an invalid path. 26 | apiPath: function () { 27 | var done = this.async(); 28 | this.apiPath = this.options.apiPath; 29 | this.api = this.options.api; 30 | this.refApi = this.options.refApi; 31 | if ((!this.api || !this.refApi) && this.apiPath) { 32 | //If API is not passed as an option and the apiPath is valid, then, validate the api Spec. 33 | Util.validateApi(this, done); 34 | return; 35 | } 36 | done(); 37 | }, 38 | setDefaults: function () { 39 | Util.setDefaults(this); 40 | } 41 | }, 42 | prompting: function () { 43 | var done = this.async(); 44 | this.prompt(Prompt('test', this), function (answers) { 45 | var self = this; 46 | Object.keys(answers).forEach(function (prop) { 47 | if (answers[prop] !== null && answers[prop] !== undefined) { 48 | self[prop] = answers[prop]; 49 | } 50 | }); 51 | 52 | //parse and validate the Swagger API entered by the user. 53 | if (answers.apiPath) { 54 | Util.updateConfigPath(self); 55 | Util.validateApi(self, done); 56 | } else { 57 | done(); 58 | } 59 | 60 | }.bind(this)); 61 | }, 62 | configuring: function () { 63 | var done = this.async(); 64 | if (Frameworks.indexOf(this.framework) === -1) { 65 | done(new Error('Invalid framework ' + this.framework + '. Framework should be one of these : ' + Frameworks)); 66 | } else { 67 | done(); 68 | } 69 | }, 70 | writing: { 71 | data: function () { 72 | this.composeWith('swaggerize:handler', { 73 | options: { 74 | api: this.api, 75 | refApi: this.refApi, 76 | apiPath: this.apiPath, 77 | handlerPath: this.handlerPath, 78 | dataPath: this.dataPath, 79 | apiConfigPath: this.apiConfigPath, 80 | securityPath: this.securityPath, 81 | framework: this.framework 82 | } 83 | }, { 84 | local: require.resolve('../handler') 85 | }); 86 | }, 87 | tests: function () { 88 | var self = this; 89 | var paths = this.api.paths; 90 | if (paths) { 91 | Object.keys(paths).forEach(function (path) { 92 | var pathStr = path.replace(/^\/|\/$/g, ''); 93 | var testPath = Path.join(self.testPath, pathStr + '.js'); 94 | var pathObj = paths[path]; 95 | var route; 96 | //Set the genFilePath path 97 | self.genFilePath = self.destinationPath(testPath); 98 | //Generate the route template obj. 99 | route = RouteGen(self, path, pathObj); 100 | if (route.operations && route.operations.length > 0) { 101 | self.fs.copyTpl( 102 | self.templatePath(Path.join(self.framework, 'test.js')), 103 | self.genFilePath, 104 | route 105 | ); 106 | } 107 | }); 108 | } 109 | } 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /generators/test/templates/express/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Express = require('express'); 4 | var BodyParser = require('body-parser'); 5 | var Swaggerize = require('swaggerize-express'); 6 | var Path = require('path'); 7 | var Request = require('supertest'); 8 | var Mockgen = require('<%=mockgenPath.replace(/\\/g,'/')%>'); 9 | var Parser = require('swagger-parser'); 10 | /** 11 | * Test for <%=path%> 12 | */ 13 | Test('<%=path%>', function (t) { 14 | var apiPath = Path.resolve(__dirname, '<%=apiPathRel.replace(/\\/g,'/')%>'); 15 | var App = Express(); 16 | App.use(BodyParser.json()); 17 | App.use(BodyParser.urlencoded({ 18 | extended: true 19 | })); 20 | App.use(Swaggerize({ 21 | api: apiPath, 22 | handlers: Path.resolve(__dirname, '<%=handlerDir.replace(/\\/g,'/')%>')<%if (security) {%>, 23 | security: Path.resolve(__dirname, '<%=securityPath.replace(/\\/g,'/')%>')<%}%> 24 | })); 25 | Parser.validate(apiPath, function (err, api) { 26 | t.error(err, 'No parse error'); 27 | t.ok(api, 'Valid swagger api'); 28 | <%operations.forEach(function (operation, i) { 29 | var mt = operation.method.toLowerCase(); 30 | %>/** 31 | * summary: <%=operation.summary%> 32 | * description: <%=operation.description%> 33 | * parameters: <%=operation.parameters%> 34 | * produces: <%=operation.produces%> 35 | * responses: <%=operation.responses.join(', ')%> 36 | */ 37 | t.test('test <%=operation.name%> <%=operation.method%> operation', function (t) { 38 | Mockgen().requests({ 39 | path: '<%=path%>', 40 | operation: '<%=operation.method%>' 41 | }, function (err, mock) { 42 | var request; 43 | t.error(err); 44 | t.ok(mock); 45 | t.ok(mock.request); 46 | //Get the resolved path from mock request 47 | //Mock request Path templates({}) are resolved using path parameters 48 | request = Request(App) 49 | .<%=mt%>('<%=basePath%>' + mock.request.path); 50 | if (mock.request.body) { 51 | //Send the request body 52 | request = request.send(mock.request.body); 53 | } else if (mock.request.formData){ 54 | //Send the request form data 55 | request = request.send(mock.request.formData); 56 | //Set the Content-Type as application/x-www-form-urlencoded 57 | request = request.set('Content-Type', 'application/x-www-form-urlencoded'); 58 | } 59 | // If headers are present, set the headers. 60 | if (mock.request.headers && mock.request.headers.length > 0) { 61 | Object.keys(mock.request.headers).forEach(function (headerName) { 62 | request = request.set(headerName, mock.request.headers[headerName]); 63 | }); 64 | } 65 | request.end(function (err, res) { 66 | t.error(err, 'No error'); 67 | <% if (operation.response) { 68 | %>t.ok(res.statusCode === <%=(operation.response === 'default')?200:operation.response%>, 'Ok response status');<%}%> 69 | <% if (operation.validateResp) { 70 | %>var Validator = require('is-my-json-valid'); 71 | var validate = Validator(api.paths['<%=path%>']['<%=operation.method%>']['responses']['<%=operation.response%>']['schema']); 72 | var response = res.body; 73 | if (Object.keys(response).length <= 0) { 74 | response = res.text; 75 | } 76 | t.ok(validate(response), 'Valid response'); 77 | t.error(validate.errors, 'No validation errors'); 78 | <%}%>t.end(); 79 | }); 80 | }); 81 | });<%})%> 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /generators/test/templates/hapi/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Test = require('tape'); 4 | const Hapi = require('hapi'); 5 | const HapiOpenAPI = require('hapi-openapi'); 6 | const Path = require('path'); 7 | const Mockgen = require('<%=mockgenPath.replace(/\\/g,'/')%>'); 8 | 9 | /** 10 | * Test for <%=path%> 11 | */ 12 | Test('<%=path%>', function (t) { 13 | 14 | <%operations.forEach(function (operation, i) { 15 | const mt = operation.method.toLowerCase(); 16 | %>/** 17 | * summary: <%=operation.summary%> 18 | * description: <%=operation.description%> 19 | * parameters: <%=operation.parameters%> 20 | * produces: <%=operation.produces%> 21 | * responses: <%=operation.responses.join(', ')%> 22 | */ 23 | t.test('test <%=operation.name%> <%=operation.method%> operation', async function (t) { 24 | 25 | const server = new Hapi.Server(); 26 | 27 | await server.register({ 28 | plugin: HapiOpenAPI, 29 | options: { 30 | api: Path.resolve(__dirname, '<%=apiPathRel.replace(/\\/g,'/')%>'), 31 | handlers: Path.join(__dirname, '<%=handlerDir.replace(/\\/g,'/')%>'), 32 | outputvalidation: true 33 | } 34 | }); 35 | 36 | const requests = new Promise((resolve, reject) => { 37 | Mockgen().requests({ 38 | path: '<%=path%>', 39 | operation: '<%=operation.method%>' 40 | }, function (error, mock) { 41 | return error ? reject(error) : resolve(mock); 42 | }); 43 | }); 44 | 45 | const mock = await requests; 46 | 47 | t.ok(mock); 48 | t.ok(mock.request); 49 | //Get the resolved path from mock request 50 | //Mock request Path templates({}) are resolved using path parameters 51 | const options = { 52 | method: '<%=mt%>', 53 | url: '<%=basePath%>' + mock.request.path 54 | }; 55 | if (mock.request.body) { 56 | //Send the request body 57 | options.payload = mock.request.body; 58 | } else if (mock.request.formData) { 59 | //Send the request form data 60 | options.payload = mock.request.formData; 61 | //Set the Content-Type as application/x-www-form-urlencoded 62 | options.headers = options.headers || {}; 63 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 64 | } 65 | // If headers are present, set the headers. 66 | if (mock.request.headers && mock.request.headers.length > 0) { 67 | options.headers = mock.request.headers; 68 | } 69 | 70 | const response = await server.inject(options); 71 | 72 | t.equal(response.statusCode, <%=(operation.response === 'default') ? 200 : operation.response%>, 'Ok response status'); 73 | t.end(); 74 | 75 | }); 76 | <%})%> 77 | }); 78 | -------------------------------------------------------------------------------- /generators/test/templates/restify/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Restify = require('restify'); 4 | var Swaggerize = require('swaggerize-restify'); 5 | var Path = require('path'); 6 | var Request = require('supertest'); 7 | var Mockgen = require('<%=mockgenPath.replace(/\\/g,'/')%>'); 8 | var Parser = require('swagger-parser'); 9 | /** 10 | * Test for <%=path%> 11 | */ 12 | Test('<%=path%>', function (t) { 13 | var apiPath = Path.resolve(__dirname, '<%=apiPathRel.replace(/\\/g,'/')%>'); 14 | var server = Restify.createServer(); 15 | server.use(Restify.bodyParser()); 16 | server.use(Restify.queryParser()); 17 | Swaggerize(server, { 18 | api: apiPath, 19 | handlers: Path.resolve(__dirname, '<%=handlerDir.replace(/\\/g,'/')%>')<%if (security) {%>, 20 | security: Path.resolve(__dirname, '<%=securityPath.replace(/\\/g,'/')%>')<%}%> 21 | }); 22 | Parser.validate(apiPath, function (err, api) { 23 | t.error(err, 'No parse error'); 24 | t.ok(api, 'Valid swagger api'); 25 | <%operations.forEach(function (operation, i) { 26 | var mt = operation.method.toLowerCase(); 27 | %>/** 28 | * summary: <%=operation.summary%> 29 | * description: <%=operation.description%> 30 | * parameters: <%=operation.parameters%> 31 | * produces: <%=operation.produces%> 32 | * responses: <%=operation.responses.join(', ')%> 33 | */ 34 | t.test('test <%=operation.name%> <%=operation.method%> operation', function (t) { 35 | Mockgen().requests({ 36 | path: '<%=path%>', 37 | operation: '<%=operation.method%>' 38 | }, function (err, mock) { 39 | var request; 40 | t.error(err); 41 | t.ok(mock); 42 | t.ok(mock.request); 43 | //Get the resolved path from mock request 44 | //Mock request Path templates({}) are resolved using path parameters 45 | request = Request(server) 46 | .<%=mt%>('<%=basePath%>' + mock.request.path); 47 | if (mock.request.body) { 48 | //Send the request body 49 | request = request.send(mock.request.body); 50 | } else if (mock.request.formData) { 51 | //Send the request form data 52 | request = request.send(mock.request.formData); 53 | //Set the Content-Type as application/x-www-form-urlencoded 54 | request = request.set('Content-Type', 'application/x-www-form-urlencoded'); 55 | } 56 | // If headers are present, set the headers. 57 | if (mock.request.headers && mock.request.headers.length > 0) { 58 | Object.keys(mock.request.headers).forEach(function (headerName) { 59 | request = request.set(headerName, mock.request.headers[headerName]); 60 | }); 61 | } 62 | request.end(function (err, res) { 63 | t.error(err, 'No error'); 64 | <% if (operation.response) { 65 | %>t.ok(res.statusCode === <%=(operation.response === 'default')?200:operation.response%>, 'Ok response status');<%}%> 66 | <% if (operation.validateResp) { 67 | %>var Validator = require('is-my-json-valid'); 68 | var validate = Validator(api.paths['<%=path%>']['<%=operation.method%>']['responses']['<%=operation.response%>']['schema']); 69 | var response = res.body; 70 | if (Object.keys(response).length <= 0) { 71 | response = res.text; 72 | } 73 | t.ok(validate(response), 'Valid response'); 74 | t.error(validate.errors, 'No validation errors'); 75 | <%}%>t.end(); 76 | }); 77 | }); 78 | });<%})%> 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /lib/routegen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Util = require('./util'); 3 | var Path = require('path'); 4 | var operationType = Util.operationType; 5 | 6 | module.exports = function routegen (generator, path, pathObj) { 7 | 8 | var pathStr = path.replace(/^\/|\/$/g, ''); 9 | var mockgenPath = Path.join(generator.dataPath, 'mockgen.js'); 10 | var dataPath = Path.join(generator.dataPath, pathStr + '.js'); 11 | var route = { 12 | basePath: (generator.api.basePath && generator.api.basePath !== '/') ? generator.api.basePath : '', 13 | path: path, 14 | apiPathRel: Util.relative(generator.genFilePath, generator.apiConfigPath), 15 | mockgenPath: Util.relative(generator.genFilePath, generator.destinationPath(mockgenPath)), 16 | dataPath: Util.relative(generator.genFilePath, generator.destinationPath(dataPath)), 17 | handlerDir: Util.relative(generator.genFilePath, generator.destinationPath(generator.handlerPath)), 18 | operations: [], 19 | security: generator.security, 20 | securityPath: Util.relative(generator.genFilePath, generator.destinationPath(generator.securityPath)) 21 | }; 22 | 23 | Object.keys(pathObj).forEach(function (method) { 24 | var commonParams = []; 25 | var operationObj = pathObj[method]; 26 | method = method.toLowerCase(); 27 | if (method === 'parameters') { 28 | /* 29 | * A list of parameters that are applicable for all the operations described under this path. 30 | * These parameters can be overridden at the operation level, but cannot be removed there. 31 | * The list MUST NOT include duplicated parameters 32 | */ 33 | commonParams = operationObj; 34 | } else if (operationType.indexOf(method) !== -1) { 35 | /* 36 | * The operation for the Path. get, post. put etc. 37 | */ 38 | var parameters = commonParams; 39 | var validateResp = false; 40 | var response; 41 | var responses = operationObj.responses; 42 | var respArr = responses ? Object.keys(responses): []; 43 | if (respArr.length > 0 ) { 44 | //Sort the array to maintain the order of keys. 45 | //Use the first response as the default response 46 | response = respArr.sort()[0]; 47 | if(responses[response] && responses[response].schema) { 48 | validateResp = true; 49 | } 50 | } 51 | if (operationObj.parameters) { 52 | parameters = commonParams.concat(operationObj.parameters); 53 | } 54 | 55 | route.operations.push({ 56 | name: operationObj.operationId, 57 | description: operationObj.description, 58 | summary: operationObj.summary, 59 | method: method, 60 | parameters: parameters && parameters.map(function (p) { return p.name; }).join(', '), 61 | produces: operationObj.produces && operationObj.produces.join(', '), 62 | responses: respArr, 63 | response: response, 64 | validateResp: validateResp 65 | }); 66 | } 67 | }); 68 | 69 | return route; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/util/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Path = require('path'); 3 | var Fs = require('fs'); 4 | var Parser = require('swagger-parser'); 5 | var Pkg = require('../../package.json'); 6 | /** 7 | * List of supported frameworks 8 | */ 9 | var Frameworks = [ 10 | 'express', 11 | 'hapi', 12 | 'restify' 13 | ]; 14 | 15 | /** 16 | * List of supported operations 17 | */ 18 | var operationType = [ 19 | 'get', 20 | 'post', 21 | 'put', 22 | 'delete', 23 | 'head', 24 | 'options', 25 | 'patch' 26 | ]; 27 | 28 | module.exports = { 29 | Frameworks: Frameworks, 30 | relative: buildRelativePath, 31 | operationType: operationType, 32 | appFramework: appFramework, 33 | setDefaults: setDefaults, 34 | validateApi: validateApi, 35 | updateConfigPath: updateConfigPath 36 | }; 37 | /** 38 | * Build relative path for module import. 39 | */ 40 | function buildRelativePath (from, to) { 41 | var dirname = Path.dirname(from); 42 | var relative = Path.relative(dirname, to); 43 | if (startsWith(relative, '.' + Path.sep) || startsWith(relative, '..' + Path.sep)) { 44 | //Path is not originating from dirname 45 | return relative; 46 | } else { 47 | //Path is originating from dirname. Prefix `./` for dirname 48 | return '.' + Path.sep + relative; 49 | } 50 | } 51 | /** 52 | * Find the existing framework dependency of the application 53 | */ 54 | function appFramework (basePath) { 55 | var pkgPath = Path.resolve(basePath, 'package.json'); 56 | var appPkg; 57 | var appFramework; 58 | var framework; 59 | if (Fs.existsSync(pkgPath)) { 60 | appPkg = require(pkgPath); 61 | for (var i in Frameworks) { 62 | framework = Frameworks[i]; 63 | if (appPkg.dependencies && Object.keys(appPkg.dependencies).indexOf(framework) !== -1) { 64 | appFramework = framework; 65 | break; 66 | } 67 | } 68 | } 69 | return appFramework; 70 | } 71 | 72 | function setDefaults (generator) { 73 | /** 74 | * Assume that this.destinationRoot() is the base path and direcory name is the appname default. 75 | */ 76 | var basePath = generator.destinationRoot(); 77 | updateConfigPath(generator); 78 | generator.appName = Path.basename(basePath); 79 | generator.framework = generator.options.framework || generator.framework || appFramework(basePath); 80 | generator.handlerPath = generator.options.handlerPath || '.' + Path.sep + 'handlers'; 81 | generator.testPath = generator.options.testPath || '.' + Path.sep + 'tests'; 82 | generator.dataPath = generator.options.dataPath || '.' + Path.sep + 'data'; 83 | generator.mockgenPath = Path.join(generator.dataPath, 'mockgen.js'); 84 | generator.securityPath = generator.options.securityPath || '.' + Path.sep + 'security'; 85 | generator.security = false; 86 | if (generator.api 87 | && generator.api.securityDefinitions 88 | && Object.keys(generator.api.securityDefinitions).length > 0) { 89 | generator.security = true; 90 | } 91 | generator.generatorVersion = Pkg.version; 92 | } 93 | 94 | /** 95 | * Update the apiConfigPath values based on options or user prompts 96 | */ 97 | function updateConfigPath (generator) { 98 | var ext = '.json'; 99 | generator.ymlApi = false; 100 | if (generator.apiPath 101 | && ('.yml' === Path.extname(generator.apiPath) || '.yaml' === Path.extname(generator.apiPath))) { 102 | ext = Path.extname(generator.apiPath); 103 | generator.ymlApi = true; 104 | } 105 | generator.apiPathRel = '.' + Path.sep + 'config' + Path.sep + 'swagger' + ext; 106 | generator.apiConfigPath = generator.options.apiConfigPath || Path.join(generator.destinationPath(), generator.apiPathRel); 107 | } 108 | 109 | function startsWith(str, substr) { 110 | if (str.substr(0, substr.length) === substr) { 111 | return true; 112 | } 113 | return false; 114 | } 115 | 116 | function validateApi (generator, done) { 117 | //Validate the api and deference $ref schemas. 118 | Parser.validate(generator.apiPath, function (error, api) { 119 | if (error) { 120 | done(error); 121 | return; 122 | } 123 | generator.api = api; 124 | //Dereferenced and resolved $ref objects cannot be used in the local copy of the swagger api. 125 | //So use `parse` API to save the api with references. 126 | Parser.parse(generator.apiPath, function (error, refApi) { 127 | if (error) { 128 | done(error); 129 | return; 130 | } 131 | generator.refApi = refApi; 132 | done(); 133 | }); 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-swaggerize", 3 | "version": "4.1.0", 4 | "description": "Yeoman generator for openAPI(swagger) application", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test/*.js", 8 | "lint": "eslint .", 9 | "cover": "istanbul cover tape -- test/*.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/krakenjs/generator-swaggerize.git" 14 | }, 15 | "keywords": [ 16 | "yeoman-generator", 17 | "yeoman", 18 | "generator", 19 | "yo", 20 | "swagger", 21 | "swaggerize", 22 | "express", 23 | "expressjs", 24 | "hapi", 25 | "hapijs", 26 | "restify", 27 | "node", 28 | "node.js", 29 | "rest", 30 | "restful", 31 | "service", 32 | "api", 33 | "oadf", 34 | "oai", 35 | "openapi" 36 | ], 37 | "author": "Trevor Livingston ", 38 | "contributors": [ 39 | "Trevor Livingston ", 40 | "Subeesh Chothendavida " 41 | ], 42 | "license": "Apache-2.0", 43 | "bugs": { 44 | "url": "https://github.com/krakenjs/generator-swaggerize/issues" 45 | }, 46 | "homepage": "https://github.com/krakenjs/generator-swaggerize#readme", 47 | "devDependencies": { 48 | "eslint": "^2", 49 | "istanbul": "^0.4.3", 50 | "tape": "^4.9.0", 51 | "yeoman-assert": "^2.2.1", 52 | "yeoman-test": "^1.4.0" 53 | }, 54 | "dependencies": { 55 | "js-yaml": "^3.6.1", 56 | "swagger-parser": "^3.4.1", 57 | "underscore.string": "^3.3.4", 58 | "yeoman-generator": "^0.21.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Helpers = require('yeoman-test'); 4 | var Path = require('path'); 5 | var Util = require('./util'); 6 | var TestSuite = require('./util/testsuite'); 7 | 8 | Test('** app generator **', function (t) { 9 | t.comment('\n'); 10 | t.test('scaffold app (with no options)', function (t) { 11 | Helpers.run(Path.join( __dirname, '../generators/app')) 12 | .withPrompts(Util.prompt('app')) 13 | .on('error', function (error) { 14 | t.error(error); 15 | t.end(); 16 | }) 17 | .on('end', function () { 18 | TestSuite('app')(t, Util.options()); 19 | t.pass(); 20 | t.end(); 21 | }); 22 | }); 23 | 24 | t.test('scaffold app with options', function (t) { 25 | var options = { 26 | framework: 'hapi', 27 | apiPath: Path.join(__dirname, './fixture/petstore.json') 28 | }; 29 | Helpers.run(Path.join( __dirname, '../generators/app')) 30 | .withOptions(options) 31 | .withPrompts(Util.prompt('app')) 32 | .on('error', function (error) { 33 | t.error(error); 34 | t.end(); 35 | }) 36 | .on('end', function () { 37 | TestSuite('app')(t, Util.options(options), true); 38 | t.pass(); 39 | t.end(); 40 | }); 41 | }); 42 | 43 | t.test('scaffold app yml file', function (t) { 44 | var options = { 45 | framework: 'hapi', 46 | apiPath: Path.join(__dirname, './fixture/uber.yaml') 47 | }; 48 | Helpers.run(Path.join( __dirname, '../generators/app')) 49 | .withOptions(options) 50 | .withPrompts(Util.prompt('app')) 51 | .on('error', function (error) { 52 | t.error(error); 53 | t.end(); 54 | }) 55 | .on('end', function () { 56 | TestSuite('app')(t, Util.options(options)); 57 | t.pass(); 58 | t.end(); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Helpers = require('yeoman-test'); 4 | var Path = require('path'); 5 | var Util = require('./util'); 6 | var TestSuite = require('./util/testsuite'); 7 | 8 | Test('** data generator **', function (t) { 9 | t.comment('\n'); 10 | t.test('scaffold data (with no options)', function (t) { 11 | Helpers.run(Path.join( __dirname, '../generators/data')) 12 | .withPrompts(Util.prompt('data')) 13 | .on('error', function (error) { 14 | t.error(error); 15 | t.end(); 16 | }) 17 | .on('end', function () { 18 | TestSuite('data')(t, Util.options()); 19 | t.pass(); 20 | t.end(); 21 | }); 22 | }); 23 | 24 | t.test('scaffold data with options', function (t) { 25 | var options = { 26 | dataPath: 'mydata',//Custom data path 27 | apiPath: Path.join(__dirname, './fixture/petstore.json'), 28 | securityPath: 'mysecurity' 29 | }; 30 | Helpers.run(Path.join( __dirname, '../generators/data')) 31 | .withOptions(options) 32 | .withPrompts(Util.prompt('data')) 33 | .on('error', function (error) { 34 | t.error(error); 35 | t.end(); 36 | }) 37 | .on('end', function () { 38 | TestSuite('data')(t, Util.options(options)); 39 | t.pass(); 40 | t.end(); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/fixture/petstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.0", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "email": "apiteam@swagger.io" 10 | }, 11 | "license": { 12 | "name": "Apache 2.0", 13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 14 | } 15 | }, 16 | "host": "petstore.swagger.io", 17 | "basePath": "/v2", 18 | "tags": [{ 19 | "name": "pet", 20 | "description": "Everything about your Pets", 21 | "externalDocs": { 22 | "description": "Find out more", 23 | "url": "http://swagger.io" 24 | } 25 | }, { 26 | "name": "store", 27 | "description": "Access to Petstore orders" 28 | }, { 29 | "name": "user", 30 | "description": "Operations about user", 31 | "externalDocs": { 32 | "description": "Find out more about our store", 33 | "url": "http://swagger.io" 34 | } 35 | }], 36 | "schemes": ["http"], 37 | "paths": { 38 | "/pet": { 39 | "post": { 40 | "tags": ["pet"], 41 | "summary": "Add a new pet to the store", 42 | "description": "", 43 | "operationId": "addPet", 44 | "consumes": ["application/json", "application/xml"], 45 | "produces": ["application/xml", "application/json"], 46 | "parameters": [{ 47 | "in": "body", 48 | "name": "body", 49 | "description": "Pet object that needs to be added to the store", 50 | "required": true, 51 | "schema": { 52 | "$ref": "#/definitions/Pet" 53 | } 54 | }], 55 | "responses": { 56 | "405": { 57 | "description": "Invalid input" 58 | } 59 | }, 60 | "security": [{ 61 | "petstore_auth": ["write:pets", "read:pets"] 62 | }] 63 | }, 64 | "put": { 65 | "tags": ["pet"], 66 | "summary": "Update an existing pet", 67 | "description": "", 68 | "operationId": "updatePet", 69 | "consumes": ["application/json", "application/xml"], 70 | "produces": ["application/xml", "application/json"], 71 | "parameters": [{ 72 | "in": "body", 73 | "name": "body", 74 | "description": "Pet object that needs to be added to the store", 75 | "required": true, 76 | "schema": { 77 | "$ref": "#/definitions/Pet" 78 | } 79 | }], 80 | "responses": { 81 | "400": { 82 | "description": "Invalid ID supplied" 83 | }, 84 | "404": { 85 | "description": "Pet not found" 86 | }, 87 | "405": { 88 | "description": "Validation exception" 89 | } 90 | }, 91 | "security": [{ 92 | "petstore_auth": ["write:pets", "read:pets"] 93 | }] 94 | } 95 | }, 96 | "/pet/findByStatus": { 97 | "get": { 98 | "tags": ["pet"], 99 | "summary": "Finds Pets by status", 100 | "description": "Multiple status values can be provided with comma separated strings", 101 | "operationId": "findPetsByStatus", 102 | "produces": ["application/xml", "application/json"], 103 | "parameters": [{ 104 | "name": "status", 105 | "in": "query", 106 | "description": "Status values that need to be considered for filter", 107 | "required": true, 108 | "type": "array", 109 | "items": { 110 | "type": "string", 111 | "enum": ["available", "pending", "sold"], 112 | "default": "available" 113 | }, 114 | "collectionFormat": "multi" 115 | }], 116 | "responses": { 117 | "200": { 118 | "description": "successful operation", 119 | "schema": { 120 | "type": "array", 121 | "items": { 122 | "$ref": "#/definitions/Pet" 123 | } 124 | } 125 | }, 126 | "400": { 127 | "description": "Invalid status value" 128 | } 129 | }, 130 | "security": [{ 131 | "petstore_auth": ["write:pets", "read:pets"] 132 | }] 133 | } 134 | }, 135 | "/pet/findByTags": { 136 | "get": { 137 | "tags": ["pet"], 138 | "summary": "Finds Pets by tags", 139 | "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 140 | "operationId": "findPetsByTags", 141 | "produces": ["application/xml", "application/json"], 142 | "parameters": [{ 143 | "name": "tags", 144 | "in": "query", 145 | "description": "Tags to filter by", 146 | "required": true, 147 | "type": "array", 148 | "items": { 149 | "type": "string" 150 | }, 151 | "collectionFormat": "multi" 152 | }], 153 | "responses": { 154 | "200": { 155 | "description": "successful operation", 156 | "schema": { 157 | "type": "array", 158 | "items": { 159 | "$ref": "#/definitions/Pet" 160 | } 161 | } 162 | }, 163 | "400": { 164 | "description": "Invalid tag value" 165 | } 166 | }, 167 | "security": [{ 168 | "petstore_auth": ["write:pets", "read:pets"] 169 | }], 170 | "deprecated": true 171 | } 172 | }, 173 | "/pet/{petId}": { 174 | "get": { 175 | "tags": ["pet"], 176 | "summary": "Find pet by ID", 177 | "description": "Returns a single pet", 178 | "operationId": "getPetById", 179 | "produces": ["application/xml", "application/json"], 180 | "parameters": [{ 181 | "name": "petId", 182 | "in": "path", 183 | "description": "ID of pet to return", 184 | "required": true, 185 | "type": "integer", 186 | "format": "int64" 187 | }], 188 | "responses": { 189 | "200": { 190 | "description": "successful operation", 191 | "schema": { 192 | "$ref": "#/definitions/Pet" 193 | } 194 | }, 195 | "400": { 196 | "description": "Invalid ID supplied" 197 | }, 198 | "404": { 199 | "description": "Pet not found" 200 | } 201 | }, 202 | "security": [{ 203 | "api_key": [] 204 | }] 205 | }, 206 | "post": { 207 | "tags": ["pet"], 208 | "summary": "Updates a pet in the store with form data", 209 | "description": "", 210 | "operationId": "updatePetWithForm", 211 | "consumes": ["application/x-www-form-urlencoded"], 212 | "produces": ["application/xml", "application/json"], 213 | "parameters": [{ 214 | "name": "petId", 215 | "in": "path", 216 | "description": "ID of pet that needs to be updated", 217 | "required": true, 218 | "type": "integer", 219 | "format": "int64" 220 | }, { 221 | "name": "name", 222 | "in": "formData", 223 | "description": "Updated name of the pet", 224 | "required": false, 225 | "type": "string" 226 | }, { 227 | "name": "status", 228 | "in": "formData", 229 | "description": "Updated status of the pet", 230 | "required": false, 231 | "type": "string" 232 | }], 233 | "responses": { 234 | "405": { 235 | "description": "Invalid input" 236 | } 237 | }, 238 | "security": [{ 239 | "petstore_auth": ["write:pets", "read:pets"] 240 | }] 241 | }, 242 | "delete": { 243 | "tags": ["pet"], 244 | "summary": "Deletes a pet", 245 | "description": "", 246 | "operationId": "deletePet", 247 | "produces": ["application/xml", "application/json"], 248 | "parameters": [{ 249 | "name": "api_key", 250 | "in": "header", 251 | "required": false, 252 | "type": "string" 253 | }, { 254 | "name": "petId", 255 | "in": "path", 256 | "description": "Pet id to delete", 257 | "required": true, 258 | "type": "integer", 259 | "format": "int64" 260 | }], 261 | "responses": { 262 | "400": { 263 | "description": "Invalid ID supplied" 264 | }, 265 | "404": { 266 | "description": "Pet not found" 267 | } 268 | }, 269 | "security": [{ 270 | "petstore_auth": ["write:pets", "read:pets"] 271 | }] 272 | } 273 | }, 274 | "/pet/{petId}/uploadImage": { 275 | "post": { 276 | "tags": ["pet"], 277 | "summary": "uploads an image", 278 | "description": "", 279 | "operationId": "uploadFile", 280 | "consumes": ["multipart/form-data"], 281 | "produces": ["application/json"], 282 | "parameters": [{ 283 | "name": "petId", 284 | "in": "path", 285 | "description": "ID of pet to update", 286 | "required": true, 287 | "type": "integer", 288 | "format": "int64" 289 | }, { 290 | "name": "additionalMetadata", 291 | "in": "formData", 292 | "description": "Additional data to pass to server", 293 | "required": false, 294 | "type": "string" 295 | }, { 296 | "name": "file", 297 | "in": "formData", 298 | "description": "file to upload", 299 | "required": false, 300 | "type": "file" 301 | }], 302 | "responses": { 303 | "200": { 304 | "description": "successful operation", 305 | "schema": { 306 | "$ref": "#/definitions/ApiResponse" 307 | } 308 | } 309 | }, 310 | "security": [{ 311 | "petstore_auth": ["write:pets", "read:pets"] 312 | }] 313 | } 314 | }, 315 | "/store/inventory": { 316 | "get": { 317 | "tags": ["store"], 318 | "summary": "Returns pet inventories by status", 319 | "description": "Returns a map of status codes to quantities", 320 | "operationId": "getInventory", 321 | "produces": ["application/json"], 322 | "parameters": [], 323 | "responses": { 324 | "200": { 325 | "description": "successful operation", 326 | "schema": { 327 | "type": "object", 328 | "additionalProperties": { 329 | "type": "integer", 330 | "format": "int32" 331 | } 332 | } 333 | } 334 | }, 335 | "security": [{ 336 | "api_key": [] 337 | }] 338 | } 339 | }, 340 | "/store/order": { 341 | "post": { 342 | "tags": ["store"], 343 | "summary": "Place an order for a pet", 344 | "description": "", 345 | "operationId": "placeOrder", 346 | "produces": ["application/xml", "application/json"], 347 | "parameters": [{ 348 | "in": "body", 349 | "name": "body", 350 | "description": "order placed for purchasing the pet", 351 | "required": true, 352 | "schema": { 353 | "$ref": "#/definitions/Order" 354 | } 355 | }], 356 | "responses": { 357 | "200": { 358 | "description": "successful operation", 359 | "schema": { 360 | "$ref": "#/definitions/Order" 361 | } 362 | }, 363 | "400": { 364 | "description": "Invalid Order" 365 | } 366 | } 367 | } 368 | }, 369 | "/store/order/{orderId}": { 370 | "get": { 371 | "tags": ["store"], 372 | "summary": "Find purchase order by ID", 373 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 374 | "operationId": "getOrderById", 375 | "produces": ["application/xml", "application/json"], 376 | "parameters": [{ 377 | "name": "orderId", 378 | "in": "path", 379 | "description": "ID of pet that needs to be fetched", 380 | "required": true, 381 | "type": "integer", 382 | "maximum": 10.0, 383 | "minimum": 1.0, 384 | "format": "int64" 385 | }], 386 | "responses": { 387 | "200": { 388 | "description": "successful operation", 389 | "schema": { 390 | "$ref": "#/definitions/Order" 391 | } 392 | }, 393 | "400": { 394 | "description": "Invalid ID supplied" 395 | }, 396 | "404": { 397 | "description": "Order not found" 398 | } 399 | } 400 | }, 401 | "delete": { 402 | "tags": ["store"], 403 | "summary": "Delete purchase order by ID", 404 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 405 | "operationId": "deleteOrder", 406 | "produces": ["application/xml", "application/json"], 407 | "parameters": [{ 408 | "name": "orderId", 409 | "in": "path", 410 | "description": "ID of the order that needs to be deleted", 411 | "required": true, 412 | "type": "integer", 413 | "minimum": 1.0, 414 | "format": "int64" 415 | }], 416 | "responses": { 417 | "400": { 418 | "description": "Invalid ID supplied" 419 | }, 420 | "404": { 421 | "description": "Order not found" 422 | } 423 | } 424 | } 425 | }, 426 | "/user": { 427 | "post": { 428 | "tags": ["user"], 429 | "summary": "Create user", 430 | "description": "This can only be done by the logged in user.", 431 | "operationId": "createUser", 432 | "produces": ["application/xml", "application/json"], 433 | "parameters": [{ 434 | "in": "body", 435 | "name": "body", 436 | "description": "Created user object", 437 | "required": true, 438 | "schema": { 439 | "$ref": "#/definitions/User" 440 | } 441 | }], 442 | "responses": { 443 | "default": { 444 | "description": "successful operation" 445 | } 446 | } 447 | } 448 | }, 449 | "/user/createWithArray": { 450 | "post": { 451 | "tags": ["user"], 452 | "summary": "Creates list of users with given input array", 453 | "description": "", 454 | "operationId": "createUsersWithArrayInput", 455 | "produces": ["application/xml", "application/json"], 456 | "parameters": [{ 457 | "in": "body", 458 | "name": "body", 459 | "description": "List of user object", 460 | "required": true, 461 | "schema": { 462 | "type": "array", 463 | "items": { 464 | "$ref": "#/definitions/User" 465 | } 466 | } 467 | }], 468 | "responses": { 469 | "default": { 470 | "description": "successful operation" 471 | } 472 | } 473 | } 474 | }, 475 | "/user/createWithList": { 476 | "post": { 477 | "tags": ["user"], 478 | "summary": "Creates list of users with given input array", 479 | "description": "", 480 | "operationId": "createUsersWithListInput", 481 | "produces": ["application/xml", "application/json"], 482 | "parameters": [{ 483 | "in": "body", 484 | "name": "body", 485 | "description": "List of user object", 486 | "required": true, 487 | "schema": { 488 | "type": "array", 489 | "items": { 490 | "$ref": "#/definitions/User" 491 | } 492 | } 493 | }], 494 | "responses": { 495 | "default": { 496 | "description": "successful operation" 497 | } 498 | } 499 | } 500 | }, 501 | "/user/login": { 502 | "get": { 503 | "tags": ["user"], 504 | "summary": "Logs user into the system", 505 | "description": "", 506 | "operationId": "loginUser", 507 | "produces": ["application/xml", "application/json"], 508 | "parameters": [{ 509 | "name": "username", 510 | "in": "query", 511 | "description": "The user name for login", 512 | "required": true, 513 | "type": "string" 514 | }, { 515 | "name": "password", 516 | "in": "query", 517 | "description": "The password for login in clear text", 518 | "required": true, 519 | "type": "string" 520 | }], 521 | "responses": { 522 | "200": { 523 | "description": "successful operation", 524 | "schema": { 525 | "type": "string" 526 | }, 527 | "headers": { 528 | "X-Rate-Limit": { 529 | "type": "integer", 530 | "format": "int32", 531 | "description": "calls per hour allowed by the user" 532 | }, 533 | "X-Expires-After": { 534 | "type": "string", 535 | "format": "date-time", 536 | "description": "date in UTC when token expires" 537 | } 538 | } 539 | }, 540 | "400": { 541 | "description": "Invalid username/password supplied" 542 | } 543 | } 544 | } 545 | }, 546 | "/user/logout": { 547 | "get": { 548 | "tags": ["user"], 549 | "summary": "Logs out current logged in user session", 550 | "description": "", 551 | "operationId": "logoutUser", 552 | "produces": ["application/xml", "application/json"], 553 | "parameters": [], 554 | "responses": { 555 | "default": { 556 | "description": "successful operation" 557 | } 558 | } 559 | } 560 | }, 561 | "/user/{username}": { 562 | "get": { 563 | "tags": ["user"], 564 | "summary": "Get user by user name", 565 | "description": "", 566 | "operationId": "getUserByName", 567 | "produces": ["application/xml", "application/json"], 568 | "parameters": [{ 569 | "name": "username", 570 | "in": "path", 571 | "description": "The name that needs to be fetched. Use user1 for testing. ", 572 | "required": true, 573 | "type": "string" 574 | }], 575 | "responses": { 576 | "200": { 577 | "description": "successful operation", 578 | "schema": { 579 | "$ref": "#/definitions/User" 580 | } 581 | }, 582 | "400": { 583 | "description": "Invalid username supplied" 584 | }, 585 | "404": { 586 | "description": "User not found" 587 | } 588 | } 589 | }, 590 | "put": { 591 | "tags": ["user"], 592 | "summary": "Updated user", 593 | "description": "This can only be done by the logged in user.", 594 | "operationId": "updateUser", 595 | "produces": ["application/xml", "application/json"], 596 | "parameters": [{ 597 | "name": "username", 598 | "in": "path", 599 | "description": "name that need to be updated", 600 | "required": true, 601 | "type": "string" 602 | }, { 603 | "in": "body", 604 | "name": "body", 605 | "description": "Updated user object", 606 | "required": true, 607 | "schema": { 608 | "$ref": "#/definitions/User" 609 | } 610 | }], 611 | "responses": { 612 | "400": { 613 | "description": "Invalid user supplied" 614 | }, 615 | "404": { 616 | "description": "User not found" 617 | } 618 | } 619 | }, 620 | "delete": { 621 | "tags": ["user"], 622 | "summary": "Delete user", 623 | "description": "This can only be done by the logged in user.", 624 | "operationId": "deleteUser", 625 | "produces": ["application/xml", "application/json"], 626 | "parameters": [{ 627 | "name": "username", 628 | "in": "path", 629 | "description": "The name that needs to be deleted", 630 | "required": true, 631 | "type": "string" 632 | }], 633 | "responses": { 634 | "400": { 635 | "description": "Invalid username supplied" 636 | }, 637 | "404": { 638 | "description": "User not found" 639 | } 640 | } 641 | } 642 | } 643 | }, 644 | "securityDefinitions": { 645 | "petstore_auth": { 646 | "type": "oauth2", 647 | "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", 648 | "flow": "implicit", 649 | "scopes": { 650 | "write:pets": "modify pets in your account", 651 | "read:pets": "read your pets" 652 | } 653 | }, 654 | "api_key": { 655 | "type": "apiKey", 656 | "name": "api_key", 657 | "in": "header" 658 | } 659 | }, 660 | "definitions": { 661 | "Order": { 662 | "type": "object", 663 | "properties": { 664 | "id": { 665 | "type": "integer", 666 | "format": "int64" 667 | }, 668 | "petId": { 669 | "type": "integer", 670 | "format": "int64" 671 | }, 672 | "quantity": { 673 | "type": "integer", 674 | "format": "int32" 675 | }, 676 | "shipDate": { 677 | "type": "string", 678 | "format": "date-time" 679 | }, 680 | "status": { 681 | "type": "string", 682 | "description": "Order Status", 683 | "enum": ["placed", "approved", "delivered"] 684 | }, 685 | "complete": { 686 | "type": "boolean", 687 | "default": false 688 | } 689 | }, 690 | "xml": { 691 | "name": "Order" 692 | } 693 | }, 694 | "Category": { 695 | "type": "object", 696 | "properties": { 697 | "id": { 698 | "type": "integer", 699 | "format": "int64" 700 | }, 701 | "name": { 702 | "type": "string" 703 | } 704 | }, 705 | "xml": { 706 | "name": "Category" 707 | } 708 | }, 709 | "User": { 710 | "type": "object", 711 | "properties": { 712 | "id": { 713 | "type": "integer", 714 | "format": "int64" 715 | }, 716 | "username": { 717 | "type": "string" 718 | }, 719 | "firstName": { 720 | "type": "string" 721 | }, 722 | "lastName": { 723 | "type": "string" 724 | }, 725 | "email": { 726 | "type": "string" 727 | }, 728 | "password": { 729 | "type": "string" 730 | }, 731 | "phone": { 732 | "type": "string" 733 | }, 734 | "userStatus": { 735 | "type": "integer", 736 | "format": "int32", 737 | "description": "User Status" 738 | } 739 | }, 740 | "xml": { 741 | "name": "User" 742 | } 743 | }, 744 | "Tag": { 745 | "type": "object", 746 | "properties": { 747 | "id": { 748 | "type": "integer", 749 | "format": "int64" 750 | }, 751 | "name": { 752 | "type": "string" 753 | } 754 | }, 755 | "xml": { 756 | "name": "Tag" 757 | } 758 | }, 759 | "Pet": { 760 | "type": "object", 761 | "required": ["name", "photoUrls"], 762 | "properties": { 763 | "id": { 764 | "type": "integer", 765 | "format": "int64" 766 | }, 767 | "category": { 768 | "$ref": "#/definitions/Category" 769 | }, 770 | "name": { 771 | "type": "string", 772 | "example": "doggie" 773 | }, 774 | "photoUrls": { 775 | "type": "array", 776 | "xml": { 777 | "name": "photoUrl", 778 | "wrapped": true 779 | }, 780 | "items": { 781 | "type": "string" 782 | } 783 | }, 784 | "tags": { 785 | "type": "array", 786 | "xml": { 787 | "name": "tag", 788 | "wrapped": true 789 | }, 790 | "items": { 791 | "$ref": "#/definitions/Tag" 792 | } 793 | }, 794 | "status": { 795 | "type": "string", 796 | "description": "pet status in the store", 797 | "enum": ["available", "pending", "sold"] 798 | } 799 | }, 800 | "xml": { 801 | "name": "Pet" 802 | } 803 | }, 804 | "ApiResponse": { 805 | "type": "object", 806 | "properties": { 807 | "code": { 808 | "type": "integer", 809 | "format": "int32" 810 | }, 811 | "type": { 812 | "type": "string" 813 | }, 814 | "message": { 815 | "type": "string" 816 | } 817 | } 818 | } 819 | }, 820 | "externalDocs": { 821 | "description": "Find out more about Swagger", 822 | "url": "http://swagger.io" 823 | } 824 | } 825 | -------------------------------------------------------------------------------- /test/fixture/petstore_no_security.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.0", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "email": "apiteam@swagger.io" 10 | }, 11 | "license": { 12 | "name": "Apache 2.0", 13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 14 | } 15 | }, 16 | "host": "petstore.swagger.io", 17 | "basePath": "/v2", 18 | "tags": [{ 19 | "name": "pet", 20 | "description": "Everything about your Pets", 21 | "externalDocs": { 22 | "description": "Find out more", 23 | "url": "http://swagger.io" 24 | } 25 | }, { 26 | "name": "store", 27 | "description": "Access to Petstore orders" 28 | }, { 29 | "name": "user", 30 | "description": "Operations about user", 31 | "externalDocs": { 32 | "description": "Find out more about our store", 33 | "url": "http://swagger.io" 34 | } 35 | }], 36 | "schemes": ["http"], 37 | "paths": { 38 | "/pet": { 39 | "post": { 40 | "tags": ["pet"], 41 | "summary": "Add a new pet to the store", 42 | "description": "", 43 | "operationId": "addPet", 44 | "consumes": ["application/json", "application/xml"], 45 | "produces": ["application/xml", "application/json"], 46 | "parameters": [{ 47 | "in": "body", 48 | "name": "body", 49 | "description": "Pet object that needs to be added to the store", 50 | "required": true, 51 | "schema": { 52 | "$ref": "#/definitions/Pet" 53 | } 54 | }], 55 | "responses": { 56 | "405": { 57 | "description": "Invalid input" 58 | } 59 | } 60 | }, 61 | "put": { 62 | "tags": ["pet"], 63 | "summary": "Update an existing pet", 64 | "description": "", 65 | "operationId": "updatePet", 66 | "consumes": ["application/json", "application/xml"], 67 | "produces": ["application/xml", "application/json"], 68 | "parameters": [{ 69 | "in": "body", 70 | "name": "body", 71 | "description": "Pet object that needs to be added to the store", 72 | "required": true, 73 | "schema": { 74 | "$ref": "#/definitions/Pet" 75 | } 76 | }], 77 | "responses": { 78 | "400": { 79 | "description": "Invalid ID supplied" 80 | }, 81 | "404": { 82 | "description": "Pet not found" 83 | }, 84 | "405": { 85 | "description": "Validation exception" 86 | } 87 | } 88 | } 89 | }, 90 | "/pet/findByStatus": { 91 | "get": { 92 | "tags": ["pet"], 93 | "summary": "Finds Pets by status", 94 | "description": "Multiple status values can be provided with comma separated strings", 95 | "operationId": "findPetsByStatus", 96 | "produces": ["application/xml", "application/json"], 97 | "parameters": [{ 98 | "name": "status", 99 | "in": "query", 100 | "description": "Status values that need to be considered for filter", 101 | "required": true, 102 | "type": "array", 103 | "items": { 104 | "type": "string", 105 | "enum": ["available", "pending", "sold"], 106 | "default": "available" 107 | }, 108 | "collectionFormat": "multi" 109 | }], 110 | "responses": { 111 | "200": { 112 | "description": "successful operation", 113 | "schema": { 114 | "type": "array", 115 | "items": { 116 | "$ref": "#/definitions/Pet" 117 | } 118 | } 119 | }, 120 | "400": { 121 | "description": "Invalid status value" 122 | } 123 | } 124 | } 125 | }, 126 | "/pet/findByTags": { 127 | "get": { 128 | "tags": ["pet"], 129 | "summary": "Finds Pets by tags", 130 | "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 131 | "operationId": "findPetsByTags", 132 | "produces": ["application/xml", "application/json"], 133 | "parameters": [{ 134 | "name": "tags", 135 | "in": "query", 136 | "description": "Tags to filter by", 137 | "required": true, 138 | "type": "array", 139 | "items": { 140 | "type": "string" 141 | }, 142 | "collectionFormat": "multi" 143 | }], 144 | "responses": { 145 | "200": { 146 | "description": "successful operation", 147 | "schema": { 148 | "type": "array", 149 | "items": { 150 | "$ref": "#/definitions/Pet" 151 | } 152 | } 153 | }, 154 | "400": { 155 | "description": "Invalid tag value" 156 | } 157 | }, 158 | "deprecated": true 159 | } 160 | }, 161 | "/pet/{petId}": { 162 | "get": { 163 | "tags": ["pet"], 164 | "summary": "Find pet by ID", 165 | "description": "Returns a single pet", 166 | "operationId": "getPetById", 167 | "produces": ["application/xml", "application/json"], 168 | "parameters": [{ 169 | "name": "petId", 170 | "in": "path", 171 | "description": "ID of pet to return", 172 | "required": true, 173 | "type": "integer", 174 | "format": "int64" 175 | }], 176 | "responses": { 177 | "200": { 178 | "description": "successful operation", 179 | "schema": { 180 | "$ref": "#/definitions/Pet" 181 | } 182 | }, 183 | "400": { 184 | "description": "Invalid ID supplied" 185 | }, 186 | "404": { 187 | "description": "Pet not found" 188 | } 189 | } 190 | }, 191 | "post": { 192 | "tags": ["pet"], 193 | "summary": "Updates a pet in the store with form data", 194 | "description": "", 195 | "operationId": "updatePetWithForm", 196 | "consumes": ["application/x-www-form-urlencoded"], 197 | "produces": ["application/xml", "application/json"], 198 | "parameters": [{ 199 | "name": "petId", 200 | "in": "path", 201 | "description": "ID of pet that needs to be updated", 202 | "required": true, 203 | "type": "integer", 204 | "format": "int64" 205 | }, { 206 | "name": "name", 207 | "in": "formData", 208 | "description": "Updated name of the pet", 209 | "required": false, 210 | "type": "string" 211 | }, { 212 | "name": "status", 213 | "in": "formData", 214 | "description": "Updated status of the pet", 215 | "required": false, 216 | "type": "string" 217 | }], 218 | "responses": { 219 | "405": { 220 | "description": "Invalid input" 221 | } 222 | } 223 | }, 224 | "delete": { 225 | "tags": ["pet"], 226 | "summary": "Deletes a pet", 227 | "description": "", 228 | "operationId": "deletePet", 229 | "produces": ["application/xml", "application/json"], 230 | "parameters": [{ 231 | "name": "api_key", 232 | "in": "header", 233 | "required": false, 234 | "type": "string" 235 | }, { 236 | "name": "petId", 237 | "in": "path", 238 | "description": "Pet id to delete", 239 | "required": true, 240 | "type": "integer", 241 | "format": "int64" 242 | }], 243 | "responses": { 244 | "400": { 245 | "description": "Invalid ID supplied" 246 | }, 247 | "404": { 248 | "description": "Pet not found" 249 | } 250 | } 251 | } 252 | }, 253 | "/pet/{petId}/uploadImage": { 254 | "post": { 255 | "tags": ["pet"], 256 | "summary": "uploads an image", 257 | "description": "", 258 | "operationId": "uploadFile", 259 | "consumes": ["multipart/form-data"], 260 | "produces": ["application/json"], 261 | "parameters": [{ 262 | "name": "petId", 263 | "in": "path", 264 | "description": "ID of pet to update", 265 | "required": true, 266 | "type": "integer", 267 | "format": "int64" 268 | }, { 269 | "name": "additionalMetadata", 270 | "in": "formData", 271 | "description": "Additional data to pass to server", 272 | "required": false, 273 | "type": "string" 274 | }, { 275 | "name": "file", 276 | "in": "formData", 277 | "description": "file to upload", 278 | "required": false, 279 | "type": "file" 280 | }], 281 | "responses": { 282 | "200": { 283 | "description": "successful operation", 284 | "schema": { 285 | "$ref": "#/definitions/ApiResponse" 286 | } 287 | } 288 | } 289 | } 290 | }, 291 | "/store/inventory": { 292 | "get": { 293 | "tags": ["store"], 294 | "summary": "Returns pet inventories by status", 295 | "description": "Returns a map of status codes to quantities", 296 | "operationId": "getInventory", 297 | "produces": ["application/json"], 298 | "parameters": [], 299 | "responses": { 300 | "200": { 301 | "description": "successful operation", 302 | "schema": { 303 | "type": "object", 304 | "additionalProperties": { 305 | "type": "integer", 306 | "format": "int32" 307 | } 308 | } 309 | } 310 | } 311 | } 312 | }, 313 | "/store/order": { 314 | "post": { 315 | "tags": ["store"], 316 | "summary": "Place an order for a pet", 317 | "description": "", 318 | "operationId": "placeOrder", 319 | "produces": ["application/xml", "application/json"], 320 | "parameters": [{ 321 | "in": "body", 322 | "name": "body", 323 | "description": "order placed for purchasing the pet", 324 | "required": true, 325 | "schema": { 326 | "$ref": "#/definitions/Order" 327 | } 328 | }], 329 | "responses": { 330 | "200": { 331 | "description": "successful operation", 332 | "schema": { 333 | "$ref": "#/definitions/Order" 334 | } 335 | }, 336 | "400": { 337 | "description": "Invalid Order" 338 | } 339 | } 340 | } 341 | }, 342 | "/store/order/{orderId}": { 343 | "get": { 344 | "tags": ["store"], 345 | "summary": "Find purchase order by ID", 346 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 347 | "operationId": "getOrderById", 348 | "produces": ["application/xml", "application/json"], 349 | "parameters": [{ 350 | "name": "orderId", 351 | "in": "path", 352 | "description": "ID of pet that needs to be fetched", 353 | "required": true, 354 | "type": "integer", 355 | "maximum": 10.0, 356 | "minimum": 1.0, 357 | "format": "int64" 358 | }], 359 | "responses": { 360 | "200": { 361 | "description": "successful operation", 362 | "schema": { 363 | "$ref": "#/definitions/Order" 364 | } 365 | }, 366 | "400": { 367 | "description": "Invalid ID supplied" 368 | }, 369 | "404": { 370 | "description": "Order not found" 371 | } 372 | } 373 | }, 374 | "delete": { 375 | "tags": ["store"], 376 | "summary": "Delete purchase order by ID", 377 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 378 | "operationId": "deleteOrder", 379 | "produces": ["application/xml", "application/json"], 380 | "parameters": [{ 381 | "name": "orderId", 382 | "in": "path", 383 | "description": "ID of the order that needs to be deleted", 384 | "required": true, 385 | "type": "integer", 386 | "minimum": 1.0, 387 | "format": "int64" 388 | }], 389 | "responses": { 390 | "400": { 391 | "description": "Invalid ID supplied" 392 | }, 393 | "404": { 394 | "description": "Order not found" 395 | } 396 | } 397 | } 398 | }, 399 | "/user": { 400 | "post": { 401 | "tags": ["user"], 402 | "summary": "Create user", 403 | "description": "This can only be done by the logged in user.", 404 | "operationId": "createUser", 405 | "produces": ["application/xml", "application/json"], 406 | "parameters": [{ 407 | "in": "body", 408 | "name": "body", 409 | "description": "Created user object", 410 | "required": true, 411 | "schema": { 412 | "$ref": "#/definitions/User" 413 | } 414 | }], 415 | "responses": { 416 | "default": { 417 | "description": "successful operation" 418 | } 419 | } 420 | } 421 | }, 422 | "/user/createWithArray": { 423 | "post": { 424 | "tags": ["user"], 425 | "summary": "Creates list of users with given input array", 426 | "description": "", 427 | "operationId": "createUsersWithArrayInput", 428 | "produces": ["application/xml", "application/json"], 429 | "parameters": [{ 430 | "in": "body", 431 | "name": "body", 432 | "description": "List of user object", 433 | "required": true, 434 | "schema": { 435 | "type": "array", 436 | "items": { 437 | "$ref": "#/definitions/User" 438 | } 439 | } 440 | }], 441 | "responses": { 442 | "default": { 443 | "description": "successful operation" 444 | } 445 | } 446 | } 447 | }, 448 | "/user/createWithList": { 449 | "post": { 450 | "tags": ["user"], 451 | "summary": "Creates list of users with given input array", 452 | "description": "", 453 | "operationId": "createUsersWithListInput", 454 | "produces": ["application/xml", "application/json"], 455 | "parameters": [{ 456 | "in": "body", 457 | "name": "body", 458 | "description": "List of user object", 459 | "required": true, 460 | "schema": { 461 | "type": "array", 462 | "items": { 463 | "$ref": "#/definitions/User" 464 | } 465 | } 466 | }], 467 | "responses": { 468 | "default": { 469 | "description": "successful operation" 470 | } 471 | } 472 | } 473 | }, 474 | "/user/login": { 475 | "get": { 476 | "tags": ["user"], 477 | "summary": "Logs user into the system", 478 | "description": "", 479 | "operationId": "loginUser", 480 | "produces": ["application/xml", "application/json"], 481 | "parameters": [{ 482 | "name": "username", 483 | "in": "query", 484 | "description": "The user name for login", 485 | "required": true, 486 | "type": "string" 487 | }, { 488 | "name": "password", 489 | "in": "query", 490 | "description": "The password for login in clear text", 491 | "required": true, 492 | "type": "string" 493 | }], 494 | "responses": { 495 | "200": { 496 | "description": "successful operation", 497 | "schema": { 498 | "type": "string" 499 | }, 500 | "headers": { 501 | "X-Rate-Limit": { 502 | "type": "integer", 503 | "format": "int32", 504 | "description": "calls per hour allowed by the user" 505 | }, 506 | "X-Expires-After": { 507 | "type": "string", 508 | "format": "date-time", 509 | "description": "date in UTC when token expires" 510 | } 511 | } 512 | }, 513 | "400": { 514 | "description": "Invalid username/password supplied" 515 | } 516 | } 517 | } 518 | }, 519 | "/user/logout": { 520 | "get": { 521 | "tags": ["user"], 522 | "summary": "Logs out current logged in user session", 523 | "description": "", 524 | "operationId": "logoutUser", 525 | "produces": ["application/xml", "application/json"], 526 | "parameters": [], 527 | "responses": { 528 | "default": { 529 | "description": "successful operation" 530 | } 531 | } 532 | } 533 | }, 534 | "/user/{username}": { 535 | "get": { 536 | "tags": ["user"], 537 | "summary": "Get user by user name", 538 | "description": "", 539 | "operationId": "getUserByName", 540 | "produces": ["application/xml", "application/json"], 541 | "parameters": [{ 542 | "name": "username", 543 | "in": "path", 544 | "description": "The name that needs to be fetched. Use user1 for testing. ", 545 | "required": true, 546 | "type": "string" 547 | }], 548 | "responses": { 549 | "200": { 550 | "description": "successful operation", 551 | "schema": { 552 | "$ref": "#/definitions/User" 553 | } 554 | }, 555 | "400": { 556 | "description": "Invalid username supplied" 557 | }, 558 | "404": { 559 | "description": "User not found" 560 | } 561 | } 562 | }, 563 | "put": { 564 | "tags": ["user"], 565 | "summary": "Updated user", 566 | "description": "This can only be done by the logged in user.", 567 | "operationId": "updateUser", 568 | "produces": ["application/xml", "application/json"], 569 | "parameters": [{ 570 | "name": "username", 571 | "in": "path", 572 | "description": "name that need to be updated", 573 | "required": true, 574 | "type": "string" 575 | }, { 576 | "in": "body", 577 | "name": "body", 578 | "description": "Updated user object", 579 | "required": true, 580 | "schema": { 581 | "$ref": "#/definitions/User" 582 | } 583 | }], 584 | "responses": { 585 | "400": { 586 | "description": "Invalid user supplied" 587 | }, 588 | "404": { 589 | "description": "User not found" 590 | } 591 | } 592 | }, 593 | "delete": { 594 | "tags": ["user"], 595 | "summary": "Delete user", 596 | "description": "This can only be done by the logged in user.", 597 | "operationId": "deleteUser", 598 | "produces": ["application/xml", "application/json"], 599 | "parameters": [{ 600 | "name": "username", 601 | "in": "path", 602 | "description": "The name that needs to be deleted", 603 | "required": true, 604 | "type": "string" 605 | }], 606 | "responses": { 607 | "400": { 608 | "description": "Invalid username supplied" 609 | }, 610 | "404": { 611 | "description": "User not found" 612 | } 613 | } 614 | } 615 | } 616 | }, 617 | "definitions": { 618 | "Order": { 619 | "type": "object", 620 | "properties": { 621 | "id": { 622 | "type": "integer", 623 | "format": "int64" 624 | }, 625 | "petId": { 626 | "type": "integer", 627 | "format": "int64" 628 | }, 629 | "quantity": { 630 | "type": "integer", 631 | "format": "int32" 632 | }, 633 | "shipDate": { 634 | "type": "string", 635 | "format": "date-time" 636 | }, 637 | "status": { 638 | "type": "string", 639 | "description": "Order Status", 640 | "enum": ["placed", "approved", "delivered"] 641 | }, 642 | "complete": { 643 | "type": "boolean", 644 | "default": false 645 | } 646 | }, 647 | "xml": { 648 | "name": "Order" 649 | } 650 | }, 651 | "Category": { 652 | "type": "object", 653 | "properties": { 654 | "id": { 655 | "type": "integer", 656 | "format": "int64" 657 | }, 658 | "name": { 659 | "type": "string" 660 | } 661 | }, 662 | "xml": { 663 | "name": "Category" 664 | } 665 | }, 666 | "User": { 667 | "type": "object", 668 | "properties": { 669 | "id": { 670 | "type": "integer", 671 | "format": "int64" 672 | }, 673 | "username": { 674 | "type": "string" 675 | }, 676 | "firstName": { 677 | "type": "string" 678 | }, 679 | "lastName": { 680 | "type": "string" 681 | }, 682 | "email": { 683 | "type": "string" 684 | }, 685 | "password": { 686 | "type": "string" 687 | }, 688 | "phone": { 689 | "type": "string" 690 | }, 691 | "userStatus": { 692 | "type": "integer", 693 | "format": "int32", 694 | "description": "User Status" 695 | } 696 | }, 697 | "xml": { 698 | "name": "User" 699 | } 700 | }, 701 | "Tag": { 702 | "type": "object", 703 | "properties": { 704 | "id": { 705 | "type": "integer", 706 | "format": "int64" 707 | }, 708 | "name": { 709 | "type": "string" 710 | } 711 | }, 712 | "xml": { 713 | "name": "Tag" 714 | } 715 | }, 716 | "Pet": { 717 | "type": "object", 718 | "required": ["name", "photoUrls"], 719 | "properties": { 720 | "id": { 721 | "type": "integer", 722 | "format": "int64" 723 | }, 724 | "category": { 725 | "$ref": "#/definitions/Category" 726 | }, 727 | "name": { 728 | "type": "string", 729 | "example": "doggie" 730 | }, 731 | "photoUrls": { 732 | "type": "array", 733 | "xml": { 734 | "name": "photoUrl", 735 | "wrapped": true 736 | }, 737 | "items": { 738 | "type": "string" 739 | } 740 | }, 741 | "tags": { 742 | "type": "array", 743 | "xml": { 744 | "name": "tag", 745 | "wrapped": true 746 | }, 747 | "items": { 748 | "$ref": "#/definitions/Tag" 749 | } 750 | }, 751 | "status": { 752 | "type": "string", 753 | "description": "pet status in the store", 754 | "enum": ["available", "pending", "sold"] 755 | } 756 | }, 757 | "xml": { 758 | "name": "Pet" 759 | } 760 | }, 761 | "ApiResponse": { 762 | "type": "object", 763 | "properties": { 764 | "code": { 765 | "type": "integer", 766 | "format": "int32" 767 | }, 768 | "type": { 769 | "type": "string" 770 | }, 771 | "message": { 772 | "type": "string" 773 | } 774 | } 775 | } 776 | }, 777 | "externalDocs": { 778 | "description": "Find out more about Swagger", 779 | "url": "http://swagger.io" 780 | } 781 | } 782 | -------------------------------------------------------------------------------- /test/fixture/uber.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Uber API 4 | description: Move your app forward with the Uber API 5 | version: 1.0.0 6 | host: api.uber.com 7 | schemes: 8 | - https 9 | basePath: /v1 10 | produces: 11 | - application/json 12 | paths: 13 | /products: 14 | get: 15 | summary: Product Types 16 | description: | 17 | The Products endpoint returns information about the *Uber* products 18 | offered at a given location. The response includes the display name 19 | and other details about each product, and lists the products in the 20 | proper display order. 21 | parameters: 22 | - name: latitude 23 | in: query 24 | description: Latitude component of location. 25 | required: true 26 | type: number 27 | format: double 28 | - name: longitude 29 | in: query 30 | description: Longitude component of location. 31 | required: true 32 | type: number 33 | format: double 34 | tags: 35 | - Products 36 | responses: 37 | '200': 38 | description: An array of products 39 | schema: 40 | type: array 41 | items: 42 | $ref: '#/definitions/Product' 43 | default: 44 | description: Unexpected error 45 | schema: 46 | $ref: '#/definitions/Error' 47 | /estimates/price: 48 | get: 49 | summary: Price Estimates 50 | description: > 51 | The Price Estimates endpoint returns an estimated price range 52 | 53 | for each product offered at a given location. The price estimate is 54 | 55 | provided as a formatted string with the full price range and the 56 | localized 57 | 58 | currency symbol.

The response also includes low and high 59 | estimates, 60 | 61 | and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code 62 | for 63 | 64 | situations requiring currency conversion. When surge is active for 65 | a particular 66 | 67 | product, its surge_multiplier will be greater than 1, but the price 68 | estimate 69 | 70 | already factors in this multiplier. 71 | parameters: 72 | - name: start_latitude 73 | in: query 74 | description: Latitude component of start location. 75 | required: true 76 | type: number 77 | format: double 78 | - name: start_longitude 79 | in: query 80 | description: Longitude component of start location. 81 | required: true 82 | type: number 83 | format: double 84 | - name: end_latitude 85 | in: query 86 | description: Latitude component of end location. 87 | required: true 88 | type: number 89 | format: double 90 | - name: end_longitude 91 | in: query 92 | description: Longitude component of end location. 93 | required: true 94 | type: number 95 | format: double 96 | tags: 97 | - Estimates 98 | responses: 99 | '200': 100 | description: An array of price estimates by product 101 | schema: 102 | type: array 103 | items: 104 | $ref: '#/definitions/PriceEstimate' 105 | default: 106 | description: Unexpected error 107 | schema: 108 | $ref: '#/definitions/Error' 109 | /estimates/time: 110 | get: 111 | summary: Time Estimates 112 | 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.' 113 | parameters: 114 | - name: start_latitude 115 | in: query 116 | description: Latitude component of start location. 117 | required: true 118 | type: number 119 | format: double 120 | - name: start_longitude 121 | in: query 122 | description: Longitude component of start location. 123 | required: true 124 | type: number 125 | format: double 126 | - name: customer_uuid 127 | in: query 128 | type: string 129 | format: uuid 130 | description: Unique customer identifier to be used for experience customization. 131 | - name: product_id 132 | in: query 133 | type: string 134 | description: 'Unique identifier representing a specific product for a given latitude & longitude.' 135 | tags: 136 | - Estimates 137 | responses: 138 | '200': 139 | description: An array of products 140 | schema: 141 | type: array 142 | items: 143 | $ref: '#/definitions/Product' 144 | default: 145 | description: Unexpected error 146 | schema: 147 | $ref: '#/definitions/Error' 148 | /me: 149 | get: 150 | summary: User Profile 151 | description: The User Profile endpoint returns information about the Uber user that has authorized with the application. 152 | tags: 153 | - User 154 | responses: 155 | '200': 156 | description: Profile information for a user 157 | schema: 158 | $ref: '#/definitions/Profile' 159 | default: 160 | description: Unexpected error 161 | schema: 162 | $ref: '#/definitions/Error' 163 | /history: 164 | get: 165 | summary: User Activity 166 | 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." 167 | parameters: 168 | - name: offset 169 | in: query 170 | type: integer 171 | format: int32 172 | description: Offset the list of returned results by this amount. Default is zero. 173 | - name: limit 174 | in: query 175 | type: integer 176 | format: int32 177 | description: 'Number of items to retrieve. Default is 5, maximum is 100.' 178 | tags: 179 | - User 180 | responses: 181 | '200': 182 | description: History information for the given user 183 | schema: 184 | $ref: '#/definitions/Activities' 185 | default: 186 | description: Unexpected error 187 | schema: 188 | $ref: '#/definitions/Error' 189 | definitions: 190 | Product: 191 | type: object 192 | properties: 193 | product_id: 194 | type: string 195 | 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.' 196 | description: 197 | type: string 198 | description: Description of product. 199 | display_name: 200 | type: string 201 | description: Display name of product. 202 | capacity: 203 | type: string 204 | description: 'Capacity of product. For example, 4 people.' 205 | image: 206 | type: string 207 | description: Image URL representing the product. 208 | PriceEstimate: 209 | type: object 210 | properties: 211 | product_id: 212 | type: string 213 | 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' 214 | currency_code: 215 | type: string 216 | description: '[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code.' 217 | display_name: 218 | type: string 219 | description: Display name of product. 220 | estimate: 221 | type: string 222 | 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.' 223 | low_estimate: 224 | type: number 225 | description: Lower bound of the estimated price. 226 | high_estimate: 227 | type: number 228 | description: Upper bound of the estimated price. 229 | surge_multiplier: 230 | type: number 231 | description: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. 232 | Profile: 233 | type: object 234 | properties: 235 | first_name: 236 | type: string 237 | description: First name of the Uber user. 238 | last_name: 239 | type: string 240 | description: Last name of the Uber user. 241 | email: 242 | type: string 243 | description: Email address of the Uber user 244 | picture: 245 | type: string 246 | description: Image URL of the Uber user. 247 | promo_code: 248 | type: string 249 | description: Promo code of the Uber user. 250 | Activity: 251 | type: object 252 | properties: 253 | uuid: 254 | type: string 255 | description: Unique identifier for the activity 256 | Activities: 257 | type: object 258 | properties: 259 | offset: 260 | type: integer 261 | format: int32 262 | description: Position in pagination. 263 | limit: 264 | type: integer 265 | format: int32 266 | description: Number of items to retrieve (100 max). 267 | count: 268 | type: integer 269 | format: int32 270 | description: Total number of items available. 271 | history: 272 | type: array 273 | items: 274 | $ref: '#/definitions/Activity' 275 | Error: 276 | type: object 277 | properties: 278 | code: 279 | type: integer 280 | format: int32 281 | message: 282 | type: string 283 | fields: 284 | type: string 285 | -------------------------------------------------------------------------------- /test/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Helpers = require('yeoman-test'); 4 | var Path = require('path'); 5 | var Util = require('./util'); 6 | var TestSuite = require('./util/testsuite'); 7 | 8 | Test('** handler generator **', function (t) { 9 | t.comment('\n'); 10 | t.test('scaffold handler (with no options)', function (t) { 11 | Helpers.run(Path.join( __dirname, '../generators/handler')) 12 | .withPrompts(Util.prompt('handler')) 13 | .on('error', function (error) { 14 | t.error(error); 15 | t.end(); 16 | }) 17 | .on('end', function () { 18 | TestSuite('handler')(t, Util.options()); 19 | t.pass(); 20 | t.end(); 21 | }); 22 | }); 23 | 24 | t.test('scaffold handler with options', function (t) { 25 | var options = { 26 | handlerPath: 'myhandler',//Custom handler path 27 | apiPath: Path.join(__dirname, './fixture/petstore.json') 28 | }; 29 | Helpers.run(Path.join( __dirname, '../generators/handler')) 30 | .withOptions(options) 31 | .withPrompts(Util.prompt('handler')) 32 | .on('error', function (error) { 33 | t.error(error); 34 | t.end(); 35 | }) 36 | .on('end', function () { 37 | TestSuite('handler')(t, Util.options(options)); 38 | t.pass(); 39 | t.end(); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Test = require('tape'); 3 | var Helpers = require('yeoman-test'); 4 | var Path = require('path'); 5 | var Util = require('./util'); 6 | var TestSuite = require('./util/testsuite'); 7 | 8 | Test('** test generator **', function (t) { 9 | t.comment('\n'); 10 | t.test('scaffold test files - (with no options)', function (t) { 11 | Helpers.run(Path.join( __dirname, '../generators/test')) 12 | .withPrompts(Util.prompt('test')) 13 | .on('error', function (error) { 14 | t.error(error); 15 | t.end(); 16 | }) 17 | .on('end', function () { 18 | TestSuite('test')(t, Util.options()); 19 | t.pass(); 20 | t.end(); 21 | }); 22 | }); 23 | 24 | t.test('scaffold test files - with options', function (t) { 25 | var options = { 26 | testPath: 'mytest',//Custom test path 27 | apiPath: Path.join(__dirname, './fixture/uber.yaml'), 28 | framework: 'restify' 29 | }; 30 | Helpers.run(Path.join( __dirname, '../generators/test')) 31 | .withOptions(options) 32 | .withPrompts(Util.prompt('test')) 33 | .on('error', function (error) { 34 | t.error(error); 35 | t.end(); 36 | }) 37 | .on('end', function () { 38 | TestSuite('test')(t, Util.options(options)); 39 | t.pass(); 40 | t.end(); 41 | }); 42 | }); 43 | 44 | t.test('scaffold test files - with security handler', function (t) { 45 | var options = { 46 | testPath: 'mytest',//Custom test path 47 | apiPath: Path.join(__dirname, './fixture/petstore.json'), 48 | framework: 'express' 49 | }; 50 | Helpers.run(Path.join( __dirname, '../generators/test')) 51 | .withOptions(options) 52 | .withPrompts(Util.prompt('test')) 53 | .on('error', function (error) { 54 | t.error(error); 55 | t.end(); 56 | }) 57 | .on('end', function () { 58 | TestSuite('test')(t, Util.options(options), true); 59 | t.pass(); 60 | t.end(); 61 | }); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /test/util/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Path = require('path'); 3 | var Fs = require('fs'); 4 | var JsYaml = require('js-yaml'); 5 | 6 | var dotFiles = [ 7 | '.eslintrc', 8 | '.eslintignore', 9 | '.gitignore', 10 | '.npmignore' 11 | ]; 12 | var projectFiles = [ 13 | 'package.json', 14 | 'README.md' 15 | ]; 16 | 17 | module.exports = { 18 | prompt: mockPrompt, 19 | options: mockOptions, 20 | dotFiles: dotFiles, 21 | projectFiles: projectFiles, 22 | routeFiles: buildRouteFiles, 23 | securityFiles: buildSecurityFiles 24 | }; 25 | 26 | function mockPrompt (name) { 27 | var mockPrompts = { 28 | app : { 29 | appName: 'mockapp', 30 | creatorName: 'lorem ipsum', 31 | githubUser: 'loremipsum', 32 | email: 'loremipsum@awesome.com', 33 | framework: 'express', 34 | apiPath: Path.join(__dirname, '../fixture/petstore_no_security.json'), 35 | 'skip-npm-install': true 36 | }, 37 | data: { 38 | apiPath: Path.join(__dirname, '../fixture/petstore_no_security.json') 39 | }, 40 | handler: { 41 | framework: 'hapi', 42 | apiPath: Path.join(__dirname, '../fixture/petstore_no_security.json') 43 | }, 44 | test: { 45 | framework: 'express', 46 | apiPath: Path.join(__dirname, '../fixture/petstore_no_security.json') 47 | } 48 | }; 49 | return mockPrompts[name]; 50 | } 51 | 52 | function mockOptions(options) { 53 | var apiRelPath = './config/swagger.json'; 54 | options = options || {}; 55 | if (options.apiPath && ('.yml' === Path.extname(options.apiPath) || '.yaml' === Path.extname(options.apiPath))) { 56 | apiRelPath = './config/swagger.yaml'; 57 | } 58 | return { 59 | framework: options.framework || 'express', 60 | apiPath: options.apiPath || Path.join(__dirname, '../fixture/petstore_no_security.json'), 61 | apiRelPath: apiRelPath, 62 | dataPath: options.dataPath || './data', 63 | handlerPath: options.handlerPath || './handlers', 64 | testPath: options.testPath || './tests', 65 | securityPath: options.securityPath || './security' 66 | }; 67 | } 68 | 69 | function buildRouteFiles (prefix, api) { 70 | var apiObj = loadApi(api); 71 | var routes = []; 72 | if (apiObj.paths) { 73 | routes = Object.keys(apiObj.paths).map(function (pathStr) { 74 | return Path.join(prefix, pathStr.replace(/^\/|\/$/g, '') + '.js'); 75 | }); 76 | } 77 | return routes; 78 | } 79 | 80 | function buildSecurityFiles (prefix, api) { 81 | var apiObj = loadApi(api); 82 | var routes = []; 83 | if (apiObj.securityDefinitions) { 84 | routes = Object.keys(apiObj.securityDefinitions).map(function (pathStr) { 85 | return Path.join(prefix, pathStr + '.js'); 86 | }); 87 | } 88 | return routes; 89 | } 90 | 91 | function loadApi (api) { 92 | api = api || Path.join(__dirname, '../fixture/petstore_no_security.json'); 93 | if ('.yml' === Path.extname(api) || '.yaml' === Path.extname(api)) { 94 | return JsYaml.load(Fs.readFileSync(api)); 95 | } else { 96 | return require(api); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/util/testsuite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Assert = require('yeoman-assert'); 3 | var Util = require('./index'); 4 | 5 | module.exports = function (generator) { 6 | var testSuite = { 7 | data : function (t, options) { 8 | /** 9 | * Test the generated `data` files 10 | */ 11 | dataTest(t, options); 12 | }, 13 | handler : function (t, options) { 14 | /** 15 | * Test the generated `handler` and `data` files 16 | */ 17 | dataTest(t, options); 18 | handlerTest(t, options); 19 | }, 20 | test : function (t, options, secuirty) { 21 | /** 22 | * Test the generated `test`, `handler` and `data` files 23 | */ 24 | dataTest(t, options); 25 | handlerTest(t, options); 26 | testTest(t, options, secuirty); 27 | }, 28 | app : function (t, options, security) { 29 | /** 30 | * Test the generated `app`, `test`, `handler` and `data` files 31 | */ 32 | appTest(t, options, security); 33 | dataTest(t, options); 34 | handlerTest(t, options); 35 | testTest(t, options); 36 | } 37 | }; 38 | return testSuite[generator]; 39 | }; 40 | /** 41 | * Test the generated `data` files 42 | */ 43 | function dataTest(tester, options) { 44 | //Data files 45 | var routeFiles = Util.routeFiles(options.dataPath, options.apiPath); 46 | tester.test('scaffold data files', function(t) { 47 | Assert.file(routeFiles); 48 | t.end(); 49 | }); 50 | //Mock gen file 51 | tester.test('scaffold mockgen files', function(t) { 52 | Assert.file([ options.dataPath + '/mockgen.js' ]); 53 | t.end(); 54 | }); 55 | //Config file 56 | tester.test('scaffold api file', function(t) { 57 | Assert.file([ options.apiRelPath ]); 58 | t.end(); 59 | }); 60 | //Secuirty files 61 | tester.test('scaffold data files', function(t) { 62 | Assert.file(Util.securityFiles(options.securityPath, options.apiPath)); 63 | t.end(); 64 | }); 65 | } 66 | /** 67 | * Test the generated `handler` files 68 | */ 69 | function handlerTest(tester, options) { 70 | //Data files 71 | tester.test('scaffold handler files', function(t) { 72 | Assert.file(Util.routeFiles(options.handlerPath, options.apiPath)); 73 | t.end(); 74 | }); 75 | } 76 | /** 77 | * Test the generated `test` files 78 | */ 79 | function testTest(tester, options, security) { 80 | //Data files 81 | var testFiles = Util.routeFiles(options.testPath, options.apiPath); 82 | var testFile = testFiles[0]; 83 | tester.test('scaffold test files', function(t) { 84 | Assert.file(testFiles); 85 | t.end(); 86 | }); 87 | // Test file content 88 | tester.test('test unit test file content', function(t) { 89 | Assert.fileContent([ 90 | [testFile, new RegExp(options.framework, 'i')], 91 | [testFile, new RegExp(options.handlerPath, 'i')] 92 | ]); 93 | t.end(); 94 | }); 95 | //If security is set to true test the securityPath 96 | if (security) { 97 | tester.test('test security path', function(t) { 98 | Assert.fileContent([ 99 | [testFile, new RegExp(options.securityPath, 'i')] 100 | ]); 101 | t.end(); 102 | }); 103 | } 104 | } 105 | /** 106 | * Test the generated `app` files - `package.json`, `README.md` etc 107 | */ 108 | function appTest(tester, options, security) { 109 | //Dot files 110 | tester.test('scaffold dot files', function(t) { 111 | Assert.file(Util.dotFiles); 112 | t.end(); 113 | }); 114 | //Project files 115 | tester.test('scaffold project files', function(t) { 116 | Assert.file(Util.projectFiles); 117 | t.end(); 118 | }); 119 | //Package content 120 | tester.test('test package file content', function(t) { 121 | Assert.fileContent([ 122 | ['package.json', new RegExp(/\"generator-swaggerize\"/)], 123 | ['package.json', new RegExp(/\"name\"\: \"mockapp\"/)], 124 | ['package.json', new RegExp(/\"author\"\: \"lorem ipsum \"/)], 125 | ['package.json', new RegExp(/\"url\"\: \"git\:\/\/github\.com\/loremipsum\/mockapp\.git\"/)], 126 | ['package.json', new RegExp('\"' + options.framework + '\"\:')], 127 | ['package.json', new RegExp('--framework ' + options.framework+ ' --apiPath \'' + options.apiRelPath.replace(/\\/g,'/') + '\'')], 128 | ['README.md', new RegExp(/# mockapp/)] 129 | ]); 130 | t.end(); 131 | }); 132 | // Server file content 133 | tester.test('test server.js file content', function(t) { 134 | Assert.fileContent([ 135 | ['server.js', new RegExp(options.framework, 'i')], 136 | ['server.js', new RegExp(options.handlerPath, 'i')] 137 | ]); 138 | t.end(); 139 | }); 140 | //If security is set to true test the securityPath 141 | if (security && options.framework !== 'hapi') { 142 | tester.test('test security path', function(t) { 143 | Assert.fileContent([ 144 | ['server.js', new RegExp(options.securityPath, 'i')] 145 | ]); 146 | t.end(); 147 | }); 148 | } 149 | } 150 | --------------------------------------------------------------------------------