├── .gitignore ├── lib ├── middleware │ ├── kill.js │ ├── notFound.js │ ├── time.js │ ├── send.js │ ├── sendError.js │ ├── ui.js │ ├── chance.js │ ├── override.js │ ├── status.js │ ├── apiDocs.js │ ├── mock.js │ ├── replay.js │ ├── swagger.js │ ├── classify.js │ └── memory.js ├── softParse.js ├── getId.js ├── setId.js ├── readFile.js ├── swaggerMin.js ├── getIdProperty.js ├── generate2.js ├── dispatch.js ├── generate.js └── swaggerMerge.js ├── bin └── pokemock ├── test ├── request.js ├── swaggerMerge.js ├── dispatch.js ├── merge.yaml ├── recursive.yaml ├── memory.js ├── app.js ├── petstore.json └── petstore2.json ├── CHANGELOG.md ├── index.js ├── package.json ├── LICENSE.md ├── createDefaultApp.js ├── README.md └── public └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | examples/java/target 4 | coverage 5 | reports 6 | -------------------------------------------------------------------------------- /lib/middleware/kill.js: -------------------------------------------------------------------------------- 1 | module.exports = kill; 2 | 3 | // kill endpoint, sometimes handy for stopping a service 4 | function kill( req, res, next ) { 5 | res.json( { 'ciao': true } ); 6 | req.server.destroy(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/middleware/notFound.js: -------------------------------------------------------------------------------- 1 | module.exports = notFound; 2 | 3 | function notFound( req, res, next ) { 4 | 5 | var err = new Error( 'Not found. Pika pika?' ); 6 | err.status = 404; 7 | next( err ); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/middleware/time.js: -------------------------------------------------------------------------------- 1 | module.exports = time; 2 | 3 | // delay request by time specified in header 4 | function time( req, res, next ) { 5 | var time = parseInt( req.headers[ 'x-mock-time' ] || 1 ); 6 | setTimeout( next, time ); 7 | } 8 | -------------------------------------------------------------------------------- /lib/softParse.js: -------------------------------------------------------------------------------- 1 | module.exports = softParse; 2 | 3 | function softParse( x, def ) { 4 | if ( typeof x === 'object' && x !== null ) return x; 5 | 6 | try { 7 | return JSON.parse( x ); 8 | } catch ( ex ) { 9 | return def; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/middleware/send.js: -------------------------------------------------------------------------------- 1 | module.exports = send; 2 | 3 | function send( req, res, next ) { 4 | if ( res.status_ ) res.status( res.status_ ); 5 | if ( res.body_ ) res.json( res.body_ ); 6 | if ( !res.status_ && !res.body_ ) next(); 7 | if ( res.status_ ) res.end(); 8 | } 9 | -------------------------------------------------------------------------------- /bin/pokemock: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var pokemock = require( '../' ); 4 | var createApp = require( '../createDefaultApp' ); 5 | 6 | pokemock.dispatch( process.argv.slice( 2 ), createApp ).catch( function ( err ) { 7 | console.log( err.stack ); 8 | process.exit( 1 ); 9 | } ); 10 | -------------------------------------------------------------------------------- /lib/middleware/sendError.js: -------------------------------------------------------------------------------- 1 | module.exports = sendError; 2 | 3 | function sendError( err, req, res, next ) { 4 | 5 | console.log( err.stack ); 6 | 7 | res.status( err.status || 500 ).json( { 8 | status: err.status || 500, 9 | message: err.message 10 | } ); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/middleware/ui.js: -------------------------------------------------------------------------------- 1 | var express = require( 'express' ); 2 | var path = require( 'path' ); 3 | 4 | // render pokemock specific swagger ui 5 | module.exports = express.Router().use( 6 | express.static( __dirname + '/../../public' ), 7 | express.static( path.dirname( require.resolve( 'swagger-ui' ) ) ) 8 | ); 9 | -------------------------------------------------------------------------------- /lib/getId.js: -------------------------------------------------------------------------------- 1 | var getIdProperty = require( './getIdProperty' ); 2 | 3 | module.exports = getId; 4 | 5 | // try to infer the primary id of an object 6 | // optionally use schema as hint 7 | function getId( object, schema ) { 8 | var prop = getIdProperty( object, schema ); 9 | return prop ? object[ prop ] : null; 10 | } 11 | -------------------------------------------------------------------------------- /lib/setId.js: -------------------------------------------------------------------------------- 1 | var getIdProperty = require( './getIdProperty' ); 2 | 3 | module.exports = setId; 4 | 5 | // try to set the primary id of an object 6 | // optionally use schema as hint 7 | function setId( object, id, schema ) { 8 | var prop = getIdProperty( object, schema ); 9 | if ( prop ) object[ prop ] = id; 10 | } 11 | -------------------------------------------------------------------------------- /lib/readFile.js: -------------------------------------------------------------------------------- 1 | var fs = require( 'fs' ); 2 | 3 | module.exports = readFile; 4 | 5 | function readFile( path ) { 6 | return new Promise( function ( resolve, reject ) { 7 | fs.readFile( path, function ( err, contents ) { 8 | if ( err ) return reject( err ); 9 | resolve( contents ); 10 | } ); 11 | } ); 12 | } 13 | -------------------------------------------------------------------------------- /test/request.js: -------------------------------------------------------------------------------- 1 | var request_ = require( 'request' ); 2 | 3 | module.exports = request; 4 | 5 | function request( url, options ) { 6 | if ( options ) options.url = url; 7 | if ( !options ) options = url; 8 | return new Promise( function ( resolve, reject ) { 9 | request_( options, function ( err, res ) { 10 | if ( err ) return reject( err ); 11 | resolve( res ); 12 | } ); 13 | } ); 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2.0 4 | 5 | - Memory module is now experimental and opt-in 6 | 7 | ## v1.1.3 8 | 9 | - Disable validator in UI 10 | 11 | ## v1.1.2 12 | 13 | - Fix `public/index.html`: Use relative `/api-docs` URL 14 | 15 | ## v1.1.1 16 | 17 | - Fix `ui` middleware: Correctly resolve public directory of `swagger-ui` 18 | 19 | ## v1.1.0 20 | 21 | - Allow multiple Swagger files as input 22 | - Fix #2 23 | - Fix #3 24 | -------------------------------------------------------------------------------- /test/swaggerMerge.js: -------------------------------------------------------------------------------- 1 | var swaggerMerge = require( '../lib/swaggerMerge' ); 2 | var swaggerMin = require( '../lib/swaggerMin' ); 3 | 4 | describe( 'swaggerMerge and swaggerMin', function () { 5 | 6 | it( 'should be able to merge and minify recursive structures', function () { 7 | return swaggerMerge( 8 | [ 'test/recursive.yaml', 'test/merge.yaml' ] 9 | ).then( swaggerMin ).then( function ( api ) { 10 | console.log( JSON.stringify( api, null, ' ' ) ); 11 | } ); 12 | } ); 13 | 14 | } ); 15 | -------------------------------------------------------------------------------- /test/dispatch.js: -------------------------------------------------------------------------------- 1 | var dispatch = require( '../lib/dispatch' ); 2 | var createApp = require( '../createDefaultApp' ); 3 | 4 | describe( 'The dispatcher', function () { 5 | 6 | it( 'should report usage', function () { 7 | return dispatch( [], createApp ); 8 | } ); 9 | 10 | it( 'should have a version option', function () { 11 | return dispatch( [ '-v' ], createApp ); 12 | } ); 13 | 14 | it( 'should have a help option', function () { 15 | return dispatch( [ '-h' ], createApp ); 16 | } ); 17 | 18 | } ); 19 | -------------------------------------------------------------------------------- /lib/middleware/chance.js: -------------------------------------------------------------------------------- 1 | var Chance = require( 'chance' ); 2 | 3 | module.exports = chance; 4 | 5 | // add (seeded) chance object to the current request 6 | function chance( req, res, next ) { 7 | 8 | var seed = req.headers[ 'x-mock-seed' ] || ( new Chance() ).word(); 9 | req.chance = new Chance( [ 10 | seed, 11 | JSON.stringify( req.pathParams ), 12 | JSON.stringify( req.query ), 13 | req.body 14 | ].join( '' ) ); 15 | 16 | // always send current seed back to client 17 | res.set( 'X-Mock-Seed', seed ); 18 | 19 | next(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /test/merge.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: "1.0" 4 | title: Recursive 5 | basePath: /recursive 6 | paths: 7 | /merged: 8 | get: 9 | security: 10 | - test: [ "merged" ] 11 | responses: 12 | 200: 13 | description: OK 14 | schema: 15 | $ref: "#/definitions/Other" 16 | definitions: 17 | Other: 18 | properties: 19 | name: 20 | type: string 21 | securityDefinitions: 22 | test: 23 | type: apiKey 24 | in: header 25 | name: X-Consumer-Key 26 | security: 27 | - test: [ 'merged' ] 28 | - test: [ "merged2" ] 29 | -------------------------------------------------------------------------------- /lib/middleware/override.js: -------------------------------------------------------------------------------- 1 | var jsonpath = require( 'jsonpath' ); 2 | var softParse = require( '../softParse' ); 3 | var _ = require( 'lodash' ); 4 | 5 | module.exports = override; 6 | 7 | function override( req, res, next ) { 8 | 9 | // only override if we have a body 10 | if ( !res.body_ ) return next(); 11 | 12 | var override = softParse( req.headers[ 'x-mock-override' ], {} ); 13 | var data = res.body_; 14 | 15 | // apply jsonpath overrides 16 | _.each( override, function ( value, path ) { 17 | try { 18 | jsonpath.apply( data, path, function () { 19 | return value; 20 | } ); 21 | } catch ( ex ) { 22 | console.log( ex.stack ); 23 | } 24 | } ); 25 | 26 | next(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/middleware/status.js: -------------------------------------------------------------------------------- 1 | module.exports = status; 2 | 3 | function status( req, res, next ) { 4 | 5 | var operation = req.swagger.operation; 6 | if ( !operation ) return next(); 7 | 8 | var statuses = Object.keys( operation.responses ); 9 | var status = req.headers[ 'x-mock-status' ] || statuses.sort( function ( a, b ) { 10 | if ( a === 'default' ) return 1; 11 | if ( b === 'default' ) return -1; 12 | return parseInt( a ) - parseInt( b ); 13 | } )[ 0 ]; 14 | 15 | var response = operation.responses[ status ]; 16 | if ( !response ) { 17 | throw new Error( 'Invalid X-Mock-Status, valid are: ' + statuses ); 18 | } 19 | 20 | res.status_ = status; 21 | res.schema = response.schema; 22 | 23 | next(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /test/recursive.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: "1.0" 4 | title: Recursive 5 | basePath: /recursive 6 | paths: 7 | /: 8 | get: 9 | security: 10 | - test: [] 11 | - test: [ "wow" ] 12 | responses: 13 | 200: 14 | description: OK 15 | schema: 16 | $ref: "#/definitions/Recursive" 17 | definitions: 18 | Recursive: 19 | properties: 20 | id: 21 | type: string 22 | other: 23 | $ref: '#/definitions/Other' 24 | Other: 25 | properties: 26 | name: 27 | type: string 28 | recursive: 29 | $ref: '#/definitions/Recursive' 30 | securityDefinitions: 31 | test: 32 | type: apiKey 33 | in: header 34 | name: X-Api-Key 35 | security: 36 | - test: [] 37 | - test: [ "wow" ] 38 | -------------------------------------------------------------------------------- /lib/middleware/apiDocs.js: -------------------------------------------------------------------------------- 1 | var parser = require( 'json-schema-ref-parser' ); 2 | var _ = require( 'lodash' ); 3 | var swaggerMerge = require( '../swaggerMerge' ); 4 | var swaggerMin = require( '../swaggerMin' ); 5 | 6 | module.exports = apiDocs; 7 | 8 | function apiDocs( apis ) { 9 | 10 | if ( !Array.isArray( apis ) ) apis = [ apis ]; 11 | 12 | var ready = swaggerMerge( apis ).then( swaggerMin ).then( function ( api ) { 13 | // copy api for api-docs 14 | // swaggerMiddleware inserts circular refs 15 | api = _.cloneDeep( api ); 16 | 17 | // cannot mock APIs with a specified host, remove it 18 | delete api.host; 19 | 20 | return api; 21 | 22 | } ); 23 | 24 | return function ( req, res, next ) { 25 | ready.then( function ( api ) { 26 | res.json( api ); 27 | } ).catch( next ); 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dispatch: require( './lib/dispatch' ), 3 | apiDocs: require( './lib/middleware/apiDocs' ), 4 | ui: require( './lib/middleware/ui' ), 5 | kill: require( './lib/middleware/kill' ), 6 | swagger: require( './lib/middleware/swagger' ), 7 | replay: require( './lib/middleware/replay' ), 8 | chance: require( './lib/middleware/chance' ), 9 | classify: require( './lib/middleware/classify' ), 10 | memory: require( './lib/middleware/memory' ), 11 | status: require( './lib/middleware/status' ), 12 | time: require( './lib/middleware/time' ), 13 | mock: require( './lib/middleware/mock' ), 14 | override: require( './lib/middleware/override' ), 15 | notFound: require( './lib/middleware/notFound' ), 16 | sendError: require( './lib/middleware/sendError' ), 17 | send: require( './lib/middleware/send' ), 18 | generate: require( './lib/generate' ), 19 | generate2: require( './lib/generate2' ) 20 | }; 21 | -------------------------------------------------------------------------------- /lib/swaggerMin.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | 3 | // TODO candidate for separate module 4 | 5 | module.exports = swaggerMin; 6 | 7 | function swaggerMin( api ) { 8 | 9 | return _.assign( 10 | toRef( api, api.definitions, 0 ), 11 | { definitions: toRef( api.definitions, api.definitions, 2 ) } 12 | ); 13 | 14 | } 15 | 16 | function toRef( value, definitions, level ) { 17 | 18 | var ref; 19 | 20 | if ( level <= 0 ) { 21 | _.forEach( definitions, function ( definition, name ) { 22 | if ( value === definition ) ref = name; 23 | } ); 24 | } 25 | 26 | if ( ref ) { 27 | return { $ref: '#/definitions/' + ref }; 28 | } else if ( Array.isArray( value ) ) { 29 | return value.map( function ( v ) { 30 | return toRef( v, definitions, level - 1 ); 31 | } ); 32 | } else if ( typeof value === 'object' && value ) { 33 | return _.mapValues( value, function ( v ) { 34 | return toRef( v, definitions, level - 1 ); 35 | } ); 36 | } else { 37 | return value; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /lib/getIdProperty.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var levenshtein = require( 'fast-levenshtein' ); 3 | 4 | module.exports = getIdProperty; 5 | 6 | // try to infer the primary id property of an object 7 | // candidates must have "id" at the end 8 | // shorter props are preferred 9 | // if we know the schema definition name, candidates must be close enough to 10 | // that definition, e.g. don't allow "contractId" to be the primary key of a 11 | // "Customer" - it's more likely a foreign key 12 | function getIdProperty( object, schema ) { 13 | 14 | var definition = schema && schema[ 'x-definition' ]; 15 | var candidates = _.keys( object ).filter( function ( prop ) { 16 | return prop.match( /id$/i ); 17 | } ).sort( function ( a, b ) { 18 | return a.length - b.length; 19 | } ).filter( function ( key ) { 20 | if ( definition && !key.match( /^.?id$/i ) ) { 21 | return levenshtein.get( 22 | definition.toLowerCase(), 23 | key.replace( /_?id$/i, '' ).toLowerCase() 24 | ) <= 2; 25 | } 26 | return true; 27 | } ); 28 | 29 | return candidates[ 0 ]; 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pokemock", 3 | "version": "1.2.0", 4 | "contributors": [ 5 | "Frederik Priede ", 6 | "Morris Brodersen (http://morrisbrodersen.de)" 7 | ], 8 | "repository": "https://github.com/mobilcom-debitel/pokemock", 9 | "license": "MIT", 10 | "bin": { 11 | "pokemock": "./bin/pokemock" 12 | }, 13 | "main": "index.js", 14 | "scripts": { 15 | "test": "node node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha test" 16 | }, 17 | "dependencies": { 18 | "chance": "^1.0.6", 19 | "express": "^4.15.2", 20 | "fast-levenshtein": "^2.0.6", 21 | "json-schema-ref-parser": "^3.1.2", 22 | "jsonpath": "^0.2.11", 23 | "lodash": "^4.17.4", 24 | "minimist": "^1.2.0", 25 | "server-destroy": "^1.0.1", 26 | "swagger-express-middleware": "^1.0.0-alpha.12", 27 | "swagger-ui": "^2.2.10" 28 | }, 29 | "devDependencies": { 30 | "istanbul": "^0.4.5", 31 | "mocha": "^3.2.0", 32 | "request": "^2.81.0" 33 | }, 34 | "publishConfig": { 35 | "registry": "https://registry.npmjs.org" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ``` 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016-2017 mobilcom-debitel GmbH 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | ``` 24 | -------------------------------------------------------------------------------- /lib/middleware/mock.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var softParse = require( '../softParse' ); 3 | 4 | module.exports = mock; 5 | 6 | // generates mock data using the given generator steps 7 | // see generate.js for example steps 8 | function mock( steps ) { 9 | 10 | return function ( req, res, next ) { 11 | 12 | // only generate mock data if we have a schema 13 | if ( !res.schema ) return next(); 14 | 15 | // add x-definition to api definitions 16 | _.each( req.swagger.api.definitions, function ( definition, name ) { 17 | definition[ 'x-definition' ] = name; 18 | } ); 19 | 20 | var limits = softParse( req.headers[ 'x-mock-size' ], {} ); 21 | var maxDepth = parseInt( req.headers[ 'x-mock-depth' ] || 5 ); 22 | 23 | res.body_ = generate( 'root', res.schema, { 24 | chance: req.chance, 25 | limits: limits, 26 | maxDepth: maxDepth 27 | }, generate, 0 ); 28 | 29 | function generate( name, schema, options, gen, depth ) { 30 | for ( var i = 0, l = steps.length; i < l; ++i ) { 31 | var value = steps[ i ]( name, schema, options, gen, depth ); 32 | if ( value !== undefined ) return value; 33 | } 34 | return 'UNSUPPORTED'; 35 | } 36 | 37 | next(); 38 | 39 | }; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lib/middleware/replay.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | 3 | module.exports = replay; 4 | 5 | function replay() { 6 | 7 | var recorded = []; 8 | 9 | return function ( req, res, next ) { 10 | 11 | try { 12 | // add recorded request 13 | var times = parseInt( req.headers[ 'x-mock-replay' ] || 0 ); 14 | var pattern = req.headers[ 'x-mock-replay-pattern' ]; 15 | 16 | for ( var i = 0; i < times; ++i ) { 17 | recorded.push( { 18 | pattern: new RegExp( pattern || _.escapeRegExp( req.originalUrl ) ), 19 | headers: _.pickBy( req.headers, replayable ) 20 | } ); 21 | } 22 | 23 | if ( times === 0 ) { 24 | // get first matching recorded request, if any, and apply 25 | for ( var j = 0; j < recorded.length; ++j ) { 26 | if ( req.originalUrl.match( recorded.pattern ) ) { 27 | _.assign( req.headers, recorded[ j ].headers ); 28 | recorded.splice( j, 1 ); 29 | break; 30 | } 31 | } 32 | } 33 | } catch ( ex ) { 34 | console.log( ex.stack ); 35 | } 36 | 37 | next(); 38 | 39 | }; 40 | 41 | } 42 | 43 | function replayable( value, header ) { 44 | return header.match( /^x-mock/ ) && !header.match( /^x-mock-replay/ ); 45 | } 46 | -------------------------------------------------------------------------------- /lib/middleware/swagger.js: -------------------------------------------------------------------------------- 1 | var express = require( 'express' ); 2 | var swaggerMiddleware = require( 'swagger-express-middleware' ); 3 | var swaggerMerge = require( '../swaggerMerge' ); 4 | var swaggerMin = require( '../swaggerMin' ); 5 | var parser = require( 'json-schema-ref-parser' ); 6 | 7 | module.exports = swagger; 8 | 9 | // set up swagger-express-middleware, pokemock-style 10 | function swagger( apis, app ) { 11 | 12 | if ( !Array.isArray( apis ) ) apis = [ apis ]; 13 | 14 | var ready = swaggerMerge( apis ).then( swaggerMin ).then( function ( api ) { 15 | return new Promise( function ( resolve, reject ) { 16 | 17 | swaggerMiddleware( api, app, function ( err, middleware ) { 18 | 19 | if ( err ) reject( err ); 20 | 21 | resolve( express.Router().use( 22 | middleware.metadata(), 23 | middleware.CORS(), 24 | middleware.files(), 25 | middleware.parseRequest(), 26 | middleware.validateRequest(), 27 | function ( err, req, res, next ) { 28 | next( err.stack.match( /Maximum call stack size exceeded/ ) ? null : err ); 29 | } 30 | ) ); 31 | 32 | } ); 33 | 34 | } ); 35 | 36 | } ); 37 | 38 | return function ( req, res, next ) { 39 | ready.then( function ( router ) { 40 | router.handle( req, res, next ); 41 | } ).catch( next ); 42 | }; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /createDefaultApp.js: -------------------------------------------------------------------------------- 1 | var express = require( 'express' ); 2 | var pokemock = require( './' ); 3 | var generate = pokemock.generate; 4 | var generate2 = pokemock.generate2; 5 | 6 | module.exports = createDefaultApp; 7 | 8 | function createDefaultApp( apis, options ) { 9 | 10 | options = options || {}; 11 | var app = express(); 12 | 13 | app.get( '/api-docs', pokemock.apiDocs( apis ) ); 14 | app.use( '/ui', pokemock.ui ); 15 | 16 | if ( options.killable ) app.use( '/kill', pokemock.kill ); 17 | 18 | app.use( 19 | pokemock.swagger( apis, app ), 20 | pokemock.replay(), 21 | pokemock.chance, 22 | pokemock.time, 23 | pokemock.status, 24 | pokemock.mock( [ 25 | generate.id, 26 | generate2.birthday, 27 | generate2.email, 28 | generate2.url, 29 | generate2.phone, 30 | generate2.city, 31 | generate2.country, 32 | generate2.street, 33 | generate2.zip, 34 | generate2.houseNo, 35 | generate2.prefix, 36 | generate2.first, 37 | generate2.last, 38 | generate2.description, 39 | generate2.summary, 40 | generate2.label, 41 | generate2.price, 42 | generate.string, 43 | generate.number, 44 | generate.integer, 45 | generate.boolean, 46 | generate.array, 47 | generate.object 48 | ] ), 49 | pokemock.override 50 | ); 51 | 52 | if ( options.memory ) { 53 | app.use( 54 | pokemock.classify, 55 | pokemock.memory( options ) 56 | ); 57 | } 58 | 59 | app.use( 60 | pokemock.send, 61 | pokemock.notFound, 62 | pokemock.sendError 63 | ); 64 | 65 | return app; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /lib/generate2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | birthday: match( /dayofbirth|birthday/i, function ( chance, schema ) { 4 | var date = chance.birthday( { type: 'adult' } ); 5 | return schema.format === 'date-time' ? date.toISOString() : date.toISOString().substr( 0, 10 ); 6 | } ), 7 | 8 | email: match( /e?mail/i, function ( chance ) { 9 | return chance.email(); 10 | } ), 11 | 12 | url: match( /homepage|urls?$/i, function ( chance ) { 13 | return chance.url(); 14 | } ), 15 | 16 | first: match( /forename|firstname/i, function ( chance ) { 17 | return chance.first(); 18 | } ), 19 | 20 | last: match( /lastname|surname/i, function ( chance ) { 21 | return chance.last(); 22 | } ), 23 | 24 | country: match( /country$/i, function ( chance ) { 25 | return chance.country(); 26 | } ), 27 | 28 | city: match( /city$/i, function ( chance ) { 29 | return chance.city(); 30 | } ), 31 | 32 | street: match( /street$/i, function ( chance ) { 33 | return chance.street(); 34 | } ), 35 | 36 | zip: match( /zip/i, function ( chance ) { 37 | return chance.zip(); 38 | } ), 39 | 40 | houseNo: match( /houseNo/i, function ( chance ) { 41 | return chance.integer( { min: 1, max: 300 } ) + ''; 42 | } ), 43 | 44 | prefix: match( /prefix|salutation/i, function ( chance ) { 45 | return chance.prefix(); 46 | } ), 47 | 48 | description: match( /description$/i, function ( chance ) { 49 | return chance.paragraph( { sentences: 6 } ); 50 | } ), 51 | 52 | summary: match( /summary$/i, function ( chance ) { 53 | return chance.paragraph( { sentences: 3 } ); 54 | } ), 55 | 56 | label: match( /label$/i, function ( chance ) { 57 | return chance.sentence(); 58 | } ), 59 | 60 | price: match( /(cost|price)$/i, function ( chance ) { 61 | return chance.dollar().substr( 1 ); 62 | } ), 63 | 64 | phone: match( /phone/i, function ( chance ) { 65 | return chance.phone(); 66 | } ) 67 | 68 | }; 69 | 70 | function match( pattern, fn ) { 71 | return function ( name, schema, options ) { 72 | if ( schema.type === 'string' && name.match( pattern ) ) { 73 | return fn( options.chance, schema ); 74 | } 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /test/memory.js: -------------------------------------------------------------------------------- 1 | var createApp = require( '../createDefaultApp' ); 2 | var request = require( './request' ); 3 | var assert = require( 'assert' ); 4 | 5 | describe( 'The Pokemock server (with memory module)', function () { 6 | 7 | var url = 'http://localhost:7374'; 8 | var server; 9 | var options = { 10 | json: true, 11 | headers: { 12 | api_key: 'siegmeyer' 13 | } 14 | }; 15 | 16 | before( function () { 17 | server = createApp( 'test/petstore.json', { memory: true } ).listen( 7374 ); 18 | } ); 19 | 20 | it( 'should remember generated objects by ID', function () { 21 | var customer; 22 | return request( url + '/v2/pet/5', options ).then( function ( res ) { 23 | assert.equal( res.statusCode, 200 ); 24 | customer = res.body; 25 | return request( url + '/v2/pet/5', options ); 26 | } ).then( function ( res ) { 27 | assert.deepEqual( res.body, customer ); 28 | } ); 29 | } ); 30 | 31 | it( 'should delete objects by ID', function () { 32 | return request( url + '/v2/pet/6', options ).then( function ( res ) { 33 | assert.equal( res.statusCode, 200 ); 34 | return request( url + '/v2/pet/6', { 35 | method: 'DELETE', 36 | api_key: 'siegmeyer' 37 | } ); 38 | } ).then( function ( res ) { 39 | return request( url + '/v2/pet/6', options ); 40 | } ).then( function ( res ) { 41 | assert.equal( res.statusCode, 404 ); 42 | } ); 43 | } ); 44 | 45 | it.skip( 'should update objects by ID', function () { 46 | 47 | } ); 48 | 49 | it( 'should create objects', function () { 50 | return request( url + '/v2/pet', { 51 | method: 'POST', 52 | body: { 53 | "id": 4, 54 | "category": { 55 | "id": 78, 56 | "name": "string" 57 | }, 58 | "name": "doggie", 59 | "photoUrls": [ 60 | "string" 61 | ], 62 | "tags": [ 63 | { 64 | "id": 97, 65 | "name": "string" 66 | } 67 | ], 68 | "status": "available" 69 | }, 70 | json: true 71 | } ).then( function ( res ) { 72 | assert.equal( res.statusCode, 200 ); 73 | } ); 74 | } ); 75 | 76 | after( function () { 77 | if ( server ) server.close(); 78 | } ); 79 | 80 | } ); 81 | -------------------------------------------------------------------------------- /lib/dispatch.js: -------------------------------------------------------------------------------- 1 | var readFile = require( './readFile' ); 2 | var cluster = require( 'cluster' ); 3 | var minimist = require( 'minimist' ); 4 | var assert = require( 'assert' ); 5 | var pack = require( '../package.json' ); 6 | var enableDestroy = require( 'server-destroy' ); 7 | var swaggerMerge = require( './swaggerMerge' ); 8 | var swaggerMin = require( './swaggerMin' ); 9 | 10 | module.exports = dispatch; 11 | 12 | function dispatch( argv, createApp ) { 13 | 14 | var args = minimist( argv.slice( 0 ) ); 15 | 16 | if ( args.h || args.help ) return usage(); 17 | if ( args.v || args.version ) return version(); 18 | if ( args._.length === 0 ) return usage(); 19 | if ( ( args.w || args.watch ) && cluster.isMaster ) return watch(); 20 | 21 | return serve( args, createApp ); 22 | 23 | } 24 | 25 | function usage() { 26 | return readFile( __dirname + '/../README.md' ).then( function ( contents ) { 27 | console.log(); 28 | console.log( contents.toString( 'utf-8' ).match( /```([\s\S]*?)```/ )[ 1 ].trim() ); 29 | } ); 30 | } 31 | 32 | function version() { 33 | return new Promise( function ( resolve ) { 34 | console.log( pack.version ); 35 | resolve(); 36 | } ); 37 | } 38 | 39 | function watch() { 40 | return new Promise( function ( resolve ) { 41 | cluster.fork(); 42 | cluster.on( 'exit', function ( worker ) { 43 | console.log( 'Worker ' + worker.process.pid + ' died. Restarting...'); 44 | cluster.fork(); 45 | } ); 46 | resolve(); 47 | } ); 48 | } 49 | 50 | function serve( args, createApp ) { 51 | 52 | return Promise.resolve( 53 | createApp( args._, { 54 | killable: args.k || args.killable, 55 | memory: args.memory 56 | } ) 57 | ).then( function ( app ) { 58 | 59 | // parse port and use 8000 as default if no custom port is set. 60 | var port = parseInt( args.p || args.port || 8000 ); 61 | 62 | app.server = app.listen( port, function () { 63 | console.log( 'Pokemock ready at http://localhost:' + port ); 64 | console.log( 'Swagger UI at http://localhost:' + port + '/ui' ); 65 | if ( args.k || args.killable ) console.log( 'Kill via http://localhost:' + port + '/kill' ); 66 | } ); 67 | 68 | enableDestroy( app.server ); 69 | 70 | if ( args.w || args.watch ) poll( args._ ); 71 | 72 | } ); 73 | 74 | } 75 | 76 | function poll( apis, current ) { 77 | 78 | swaggerMerge( apis ).then( swaggerMin ).then( function ( api ) { 79 | 80 | if ( current ) assert.deepEqual( api, current ); 81 | 82 | current = api; 83 | 84 | setTimeout( function () { 85 | poll( apis, current ); 86 | }, 1000 ); 87 | 88 | } ).catch( function () { 89 | process.exit( 1 ); 90 | } ); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /lib/generate.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | 3 | module.exports = { 4 | 5 | id: function ( name, schema, options ) { 6 | if ( schema.type === 'string' && name.match( /id$/i ) ) { 7 | return options.chance.guid(); 8 | } 9 | }, 10 | 11 | string: function ( name, schema, options ) { 12 | 13 | if ( schema.type !== 'string' ) return; 14 | 15 | // pick one random element from an enum 16 | if ( schema.enum ) { 17 | return options.chance.pickone( schema.enum ); 18 | } 19 | 20 | // when no typehints are found, try to generate meaningful data 21 | // based on 'format' 22 | switch ( schema.format ) { 23 | case 'date': 24 | return options.chance.date().toISOString().substr( 0, 10 ); 25 | case 'date-time': 26 | return options.chance.date().toISOString(); 27 | case 'number': 28 | return options.chance.floating() + ''; 29 | default: 30 | return options.chance.word(); 31 | } 32 | 33 | }, 34 | 35 | number: function ( name, schema, options ) { 36 | 37 | if ( schema.type !== 'number' ) return; 38 | 39 | // supported formats are 'float' and 'double' 40 | // but both are the same in javascript 41 | return options.chance.floating(); 42 | 43 | }, 44 | 45 | integer: function ( name, schema, options ) { 46 | if ( schema.type !== 'integer' ) return; 47 | return options.chance.integer( { min: 0, max: 4096 } ); 48 | }, 49 | 50 | boolean: function ( name, schema, options ) { 51 | if ( schema.type !== 'boolean' ) return; 52 | return options.chance.bool(); 53 | }, 54 | 55 | array: function ( name, schema, options, gen, depth ) { 56 | 57 | if ( schema.type !== 'array' ) return; 58 | 59 | if ( depth > options.maxDepth ) { 60 | return []; 61 | } 62 | 63 | var limit = options.limits[ schema.items[ 'x-definition' ] ] || 64 | options.limits[ name ]; 65 | 66 | var length = limit >= 0 ? limit : options.chance.integer( { min: 1, max: 5 } ); 67 | var array = []; 68 | 69 | for ( var i = 0; i < length; ++i ) { 70 | array.push( gen( name, schema.items, options, gen, depth + 1 ) ); 71 | } 72 | 73 | return array; 74 | 75 | }, 76 | 77 | object: function ( name, schema, options, gen, depth ) { 78 | 79 | if ( schema.type && schema.type !== 'object' ) return; 80 | 81 | if ( depth > options.maxDepth ) { 82 | return null; 83 | } 84 | 85 | var properties = schema.allOf ? _.sample( 86 | _.filter( schema.allOf, function ( data ) { 87 | return Object.keys( data ).length > 0; 88 | } ) 89 | ).properties : schema.properties; 90 | 91 | var self = this; 92 | 93 | return _.mapValues( properties, function ( schema, name ) { 94 | return gen( name, schema, options, gen, depth + 1 ); 95 | } ); 96 | 97 | } 98 | 99 | }; 100 | -------------------------------------------------------------------------------- /lib/middleware/classify.js: -------------------------------------------------------------------------------- 1 | var getId = require( '../getId' ); 2 | var softParse = require( '../softParse' ); 3 | 4 | module.exports = classify; 5 | 6 | // infer semantic action and primary id of a request. returns one of 7 | // - create 8 | // - update 9 | // - updateById 10 | // - delete 11 | // - deleteById 12 | // - get 13 | // - getById 14 | // - unknown 15 | // and an id, if any 16 | function classify( req, res, next ) { 17 | 18 | var swagger = req.swagger; 19 | var operation = swagger && swagger.operation; 20 | var operationId = operation && operation.operationId; 21 | var pathName = swagger && swagger.pathName; 22 | var response = operation && operation.responses && ( operation.responses[ 200 ] || operation.responses[ 201 ] ); 23 | var schema = response && response.schema; 24 | var bodySchema = operation && operation.parameters && operation.parameters.filter( function ( param ) { 25 | return param.in === 'body' && param.schema; 26 | } ).map( function ( param ) { 27 | return param.schema; 28 | } )[ 0 ]; 29 | var body = softParse( req.body, {} ); 30 | var id; 31 | 32 | req.action = { 33 | type: type(), 34 | id: id, 35 | body: body, 36 | bodySchema: bodySchema 37 | }; 38 | 39 | res.set( 'X-Mock-Classification', req.action.type ); 40 | 41 | console.log( 'classification:', req.action ); 42 | 43 | next(); 44 | 45 | function type() { 46 | 47 | if ( req.method === 'DELETE' || 48 | operationId && operationId.match( /^delete|remove|drop/i ) || 49 | pathName && pathName.match( /delete|remove|drop[^/]*$/i ) ) { 50 | id = getId( req.pathParams ) || getId( req.query ); 51 | if ( id ) return 'deleteById'; 52 | return 'delete'; 53 | } 54 | 55 | if ( operationId && operationId.match( /^create|insert/i ) || 56 | pathName && pathName.match( /create|insert[^/]*$/i ) ) { 57 | return 'create'; 58 | } 59 | 60 | if ( req.method === 'POST' ) { 61 | id = getId( body, bodySchema ) || getId( req.query, bodySchema ) || 62 | getId( req.pathParams, bodySchema ); 63 | if ( id ) return 'updateById'; 64 | return 'create'; 65 | } 66 | 67 | if ( req.method === 'PUT' ) { 68 | id = getId( req.pathParams, bodySchema ) || 69 | getId( body, bodySchema ) || getId( req.query, bodySchema ); 70 | if ( id ) return 'updateById'; 71 | return 'create'; 72 | } 73 | 74 | if ( req.method === 'PATCH' ) { 75 | id = getId( req.pathParams, bodySchema ) || 76 | getId( body, bodySchema ) || getId( req.query, bodySchema ); 77 | if ( id ) return 'updateById'; 78 | return 'update'; 79 | } 80 | 81 | if ( req.method === 'GET' ) { 82 | id = getId( req.pathParams, schema ) || 83 | getId( body, schema ) || getId( req.query, schema ); 84 | if ( id ) return 'getById'; 85 | return 'get'; 86 | } 87 | 88 | // TODO HEAD, OPTIONS, ... 89 | 90 | return 'unknown'; 91 | 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pokemock 2 | 3 | A mock server generated from one or more arbitrary Swagger files. 4 | Supports seeding, timeouts, response picking, 5 | entity memory, semantic action inference, etc. 6 | 7 | 8 | ## Usage 9 | 10 | ``` 11 | Syntax: 12 | pokemock ... [-h] [-v] [-w] [-p ] 13 | 14 | Options: 15 | -h, --help Show help 16 | -v, --version Show version 17 | -p, --port Set server port, default is 8000 18 | -w, --watch Watch mode: Restart on Swagger changes 19 | -k, --killable Publish /kill endpoint to stop the service 20 | --memory Enable memory module (experimental) 21 | ``` 22 | 23 | 24 | ## Server 25 | 26 | The mock server listens to the specified port and 27 | mocks endpoints defined in the provided Swagger document. 28 | Additionally, it publishes a Swagger UI under `/ui`, 29 | the Swagger API under `/api-docs` and a `/kill` endpoint for shutdown. 30 | 31 | 32 | ## Request Headers 33 | 34 | Using optional headers, clients can control the server's behavior: 35 | 36 | - __X-Mock-Status__ 37 | - Specifies the response status code 38 | - The correct response is inferred from the API if possible 39 | - Defaults to the first response code specified in the API 40 | - __X-Mock-Seed__ 41 | - Specifies a seed for data generation 42 | - If omitted, a random seed is generated 43 | - The current seed is always returned in a X-Mock-Seed response header 44 | - __X-Mock-Time__ 45 | - Specifies the minimum response time (milliseconds) 46 | - __X-Mock-Size__ 47 | - Specifies array size(s) in the response 48 | - Must be a valid JSON object of 49 | `: ` pairs 50 | - If omitted, array sizes are randomly between 1 and 5 51 | - __X-Mock-Depth__ 52 | - Specifies the maximum JSON data depth 53 | - Defaults to 5 54 | - __X-Mock-Override__ 55 | - Specifies response data via [JSON Path](https://github.com/dchester/jsonpath) 56 | - Must be a valid JSON object of `: ` pairs 57 | - `` is arbitrary JSON 58 | - __X-Mock-Replay__ 59 | - Specifies the number of times the current X-Mock-* headers should be replayed 60 | - The next N requests to the requested URL will replay the current X-Mock-* headers 61 | - __X-Mock-Replay-Pattern__ 62 | - Specifies a regular expression to match for X-Mock-Replay 63 | - If omitted, the exact path is used for replaying 64 | 65 | 66 | ## Memory (experimental) 67 | 68 | Use the `--memory` switch to enable the memory module. 69 | When enabled, entities containing an ID are remembered by the server. 70 | If the entity is requested again, the remembered data is returned. 71 | This also applies to sub-entities across endpoints. 72 | 73 | Additionally, the server tries to infer semantic actions from requests, 74 | such as: 75 | 76 | - Get by id 77 | - Delete by id 78 | - Update by id 79 | - Create new entity 80 | 81 | These actions are applied to known entities in memory. 82 | For example, requesting a deleted entity will result in a 404 response. 83 | 84 | 85 | ## Customization 86 | 87 | Pokemock provides a set of [Express](http://expressjs.com/de/) middlewares 88 | which you can use independently. 89 | The default app defined in `createDefaultApp.js` is an opinionated stack of 90 | middlewares which you're encouraged to hack on. 91 | By re-arranging and adding middlewares (especially generators) 92 | you can tailor Pokemock to fit your APIs. 93 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | var createApp = require( '../createDefaultApp' ); 2 | var request = require( './request' ); 3 | var assert = require( 'assert' ); 4 | 5 | describe( 'The Pokemock server', function () { 6 | 7 | var url = 'http://localhost:7373'; 8 | var server; 9 | var options = { 10 | json: true, 11 | headers: { 12 | api_key: 'siegmeyer' 13 | } 14 | }; 15 | 16 | before( function () { 17 | server = createApp( 'test/petstore.json' ).listen( 7373 ); 18 | } ); 19 | 20 | it( 'should handle unfound paths', function () { 21 | return request( url + '/siegmeyer', options ).then( function ( res ) { 22 | assert.ok( res.body.message.match( /not found/i ) ); 23 | assert.equal( res.statusCode, 404 ); 24 | } ); 25 | } ); 26 | 27 | it( 'should publish a Swagger UI', function () { 28 | return request( url + '/ui' ).then( function ( res ) { 29 | assert.equal( res.statusCode, 200 ); 30 | } ); 31 | } ); 32 | 33 | it( 'should publish Swagger UI assets', function () { 34 | return request( url + '/ui/swagger-ui.js' ).then( function ( res ) { 35 | assert.equal( res.statusCode, 200 ); 36 | } ); 37 | } ); 38 | 39 | it( 'should generate mock data', function () { 40 | return request( url + '/v2/pet/findByStatus?status=available' ).then( function ( res ) { 41 | assert.equal( res.statusCode, 200 ); 42 | } ); 43 | } ); 44 | 45 | it( 'should accept objects', function () { 46 | return request( url + '/v2/pet', { 47 | method: 'POST', 48 | body: { 49 | "id": 4, 50 | "category": { 51 | "id": 78, 52 | "name": "string" 53 | }, 54 | "name": "doggie", 55 | "photoUrls": [ 56 | "string" 57 | ], 58 | "tags": [ 59 | { 60 | "id": 97, 61 | "name": "string" 62 | } 63 | ], 64 | "status": "available" 65 | }, 66 | json: true 67 | } ).then( function ( res ) { 68 | assert.equal( res.statusCode, 200 ); 69 | } ); 70 | } ); 71 | 72 | it( 'should respond after a specified time', function () { 73 | 74 | this.timeout( 10000 ); 75 | 76 | var t0 = Date.now(); 77 | 78 | return request( url + '/v2/pet/4', { 79 | headers: { 80 | 'X-Mock-Time': 1000, 81 | api_key: 'siegmeyer' 82 | }, 83 | json: true 84 | } ).then( function ( res ) { 85 | var t1 = Date.now(); 86 | assert.ok( t1 - t0 > 1000 ); 87 | } ); 88 | } ); 89 | 90 | it( 'should replay X-Mock-* headers', function () { 91 | return request( url + '/v2/pet/4', { 92 | headers: { 93 | 'X-Mock-Replay': 3, 94 | 'X-Mock-Status': 404, 95 | api_key: 'siegmeyer' 96 | }, 97 | json: true 98 | } ).then( function ( res ) { 99 | assert.equal( res.statusCode, 404 ); 100 | } ).then( function () { 101 | return request( url + '/v2/pet/4', options ); 102 | } ).then( function ( res ) { 103 | assert.equal( res.statusCode, 404 ); 104 | } ).then( function () { 105 | return request( url + '/v2/pet/4', options ); 106 | } ).then( function ( res ) { 107 | assert.equal( res.statusCode, 404 ); 108 | } ).then( function () { 109 | return request( url + '/v2/pet/4', options ); 110 | } ).then( function ( res ) { 111 | assert.equal( res.statusCode, 404 ); 112 | } ).then( function () { 113 | return request( url + '/v2/pet/4', options ); 114 | } ).then( function ( res ) { 115 | assert.ok( res.body.id ); 116 | assert.equal( res.statusCode, 200 ); 117 | } ); 118 | } ); 119 | 120 | after( function () { 121 | if ( server ) server.close(); 122 | } ); 123 | 124 | } ); 125 | -------------------------------------------------------------------------------- /lib/swaggerMerge.js: -------------------------------------------------------------------------------- 1 | var parser = require( 'json-schema-ref-parser' ); 2 | var _ = require( 'lodash' ); 3 | 4 | // TODO candidate for separate module 5 | 6 | module.exports = swaggerMerge; 7 | 8 | function swaggerMerge( apis, options ) { 9 | 10 | options = options || {}; 11 | 12 | if ( apis.length === 0 ) return Promise.reject( new Error( 'swaggerMerge requires at least one API' ) ); 13 | 14 | return parser.dereference( apis[ 0 ] ).then( function ( api ) { 15 | 16 | api.tags = api.tags || []; 17 | api.paths = api.paths || {}; 18 | api.definitions = api.definitions || {}; 19 | api.securityDefinitions = api.securityDefinitions || {}; 20 | api.info = api.info || {}; 21 | api.info.title = api.info.title || ''; 22 | api.info.description = api.info.description || ''; 23 | normalizeSecurity( api ); 24 | 25 | // resolve base path 26 | if ( api.basePath ) { 27 | 28 | api.paths = _.mapKeys( api.paths, function ( data, path ) { 29 | // the final replace() removes double and trailing slashes 30 | return ( api.basePath + '/' + path ).replace( /\/+(\/|$)/g, '$1' ); 31 | } ); 32 | 33 | delete api.basePath; 34 | 35 | } 36 | 37 | if ( apis.length === 1 ) return api; 38 | 39 | // merge the rest 40 | return swaggerMerge( apis.slice( 1 ), options ).then( function ( api2 ) { 41 | 42 | if ( options.mergeDescriptions ) { 43 | if ( api2.info.title ) api.info.description += '\n\n## ' + api2.info.title; 44 | if ( api2.info.description ) api.info.description += '\n\n' + api2.info.description; 45 | } 46 | 47 | // add tags only once 48 | api2.tags.forEach( function ( tag2 ) { 49 | if ( api.tags.filter( function ( tag ) { 50 | return tag.name === tag2.name; 51 | } ).length === 0 ) { 52 | api.tags.push( tag2 ); 53 | } 54 | } ); 55 | 56 | // add definitions 57 | // collisions are resolved by appending underscores 58 | _.assignIn( 59 | api.definitions, 60 | _.mapKeys( api2.definitions, function ( definition, name ) { 61 | while ( api.definitions[ name ] ) name += '_'; 62 | return name; 63 | } ) 64 | ); 65 | 66 | // add security definitions 67 | // collisions are resolved by appending underscores 68 | _.assignIn( 69 | api.securityDefinitions, 70 | _.mapKeys( api2.securityDefinitions, function ( definition, name ) { 71 | var newName = name; 72 | while ( api.securityDefinitions[ newName ] ) newName += '_'; 73 | replaceSecurityName( api2, name, newName ); 74 | return newName; 75 | } ) 76 | ); 77 | 78 | // add paths 79 | _.assignIn( api.paths, api2.paths ); 80 | 81 | return api; 82 | 83 | } ); 84 | 85 | } ); 86 | 87 | } 88 | 89 | function normalizeSecurity( api ) { 90 | _.forEach( api.paths, function ( operations ) { 91 | _.forEach( operations, function ( operation ) { 92 | if ( api.security || operation.security ) { 93 | operation.security = _.uniqBy( 94 | ( api.security || [] ).concat( operation.security || [] ), 95 | JSON.stringify 96 | ); 97 | } 98 | } ); 99 | } ); 100 | 101 | delete api.security; 102 | } 103 | 104 | function replaceSecurityName( api, name, newName ) { 105 | _.forEach( api.paths, function ( operations ) { 106 | _.forEach( operations, function ( operation ) { 107 | if ( !operation.security ) return; 108 | operation.security = operation.security.map( function ( object ) { 109 | return _.mapKeys( object, function ( scopes, n ) { 110 | return n === name ? newName : name; 111 | } ); 112 | } ); 113 | } ); 114 | } ); 115 | } 116 | -------------------------------------------------------------------------------- /lib/middleware/memory.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var getId = require( '../getId' ); 3 | var setId = require( '../setId' ); 4 | var mock = require( './mock' ); 5 | 6 | module.exports = memory; 7 | 8 | // remembers generated responses by ID 9 | function memory() { 10 | 11 | var objects = {}; 12 | 13 | return handle; 14 | 15 | function handle( req, res, next ) { 16 | 17 | if ( res.status_ >= 300 || req.headers[ 'x-mock-memory' ] + '' === '0' ) { 18 | return next(); 19 | } 20 | 21 | try { 22 | // get data from status, mock, and classify middlewares 23 | var data = res.body_; 24 | var random = res.body_; 25 | var schema = res.schema; 26 | var action = req.action; 27 | 28 | // use memory 29 | switch ( action.type ) { 30 | case 'get': 31 | data = load( schema, data ); 32 | store( schema, data ); 33 | break; 34 | case 'getById': 35 | data = load( schema, { id: action.id, __memory: true } ); 36 | if ( data ) { 37 | if ( data.__memory ) { 38 | setId( random, data.id, schema ); 39 | data = random; 40 | } 41 | store( schema, data ); 42 | } else { 43 | data = 'deleted'; 44 | } 45 | break; 46 | case 'delete': 47 | case 'deleteById': 48 | remove( action.id ); 49 | break; 50 | case 'updateById': 51 | data = update( action.id, action.body ); 52 | store( action.bodySchema, data ); 53 | break; 54 | case 'create': 55 | store( action.bodySchema, data ); 56 | data = update( getId( data ), action.body ); 57 | store( action.bodySchema, data ); 58 | break; 59 | default: 60 | // ignore 61 | } 62 | 63 | if ( data === 'deleted' ) { 64 | return res.status( 404 ).json( { 65 | pokemockError: 'Unknown entity' 66 | } ); 67 | } 68 | 69 | res.body_ = data; 70 | 71 | } catch ( ex ) { 72 | // don't die on memory issues 73 | console.log( ex.stack ); 74 | } 75 | 76 | next(); 77 | 78 | } 79 | 80 | // remember given data 81 | function store( schema, data ) { 82 | 83 | var type = data && ( schema.type || 'object' ); 84 | 85 | switch ( type ) { 86 | 87 | case 'object': 88 | var id = getId( data ); 89 | if ( id ) objects[ id ] = data; 90 | _.forEach( schema.properties, function ( schema, name ) { 91 | store( schema, data[ name ] ); 92 | } ); 93 | return this; 94 | 95 | case 'array': 96 | data.forEach( function ( item ) { 97 | store( schema.items, item ); 98 | } ); 99 | return this; 100 | 101 | default: 102 | return this; 103 | 104 | } 105 | 106 | } 107 | 108 | // load known data into given data 109 | function load( schema, data ) { 110 | 111 | var type = data && ( schema.type || 'object' ); 112 | 113 | switch ( type ) { 114 | 115 | case 'object': 116 | // load known object or null 117 | var id = getId( data ); 118 | if ( id && objects[ id ] === 'deleted' ) return null; 119 | if ( id && objects[ id ] ) data = objects[ id ]; 120 | 121 | if ( !data.__memory ) _.forEach( schema.properties, function ( schema, name ) { 122 | data[ name ] = load( schema, data[ name ] ); 123 | } ); 124 | 125 | return data; 126 | 127 | case 'array': 128 | // filter deleted objects 129 | // otherwise load known data 130 | return data.filter( function ( item ) { 131 | var id = getId( item ); 132 | return !id || objects[ id ] !== 'deleted'; 133 | } ).map( function ( item ) { 134 | return load( schema.items, item ); 135 | } ); 136 | 137 | default: 138 | return data; 139 | 140 | } 141 | } 142 | 143 | function update( id, data ) { 144 | if ( objects[ id ] === 'deleted' ) return 'deleted'; 145 | return _.assign( objects[ id ] || {}, data ); 146 | } 147 | 148 | // remember that an object was deleted 149 | function remove( id ) { 150 | objects[ id ] = 'deleted'; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 92 | 93 | 94 | 95 | 105 | 106 |
 
107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /test/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 | { 20 | "name":"pet", 21 | "description":"Everything about your Pets", 22 | "externalDocs":{ 23 | "description":"Find out more", 24 | "url":"http://swagger.io" 25 | } 26 | }, 27 | { 28 | "name":"store", 29 | "description":"Access to Petstore orders" 30 | }, 31 | { 32 | "name":"user", 33 | "description":"Operations about user", 34 | "externalDocs":{ 35 | "description":"Find out more about our store", 36 | "url":"http://swagger.io" 37 | } 38 | } 39 | ], 40 | "schemes":[ 41 | "http" 42 | ], 43 | "paths":{ 44 | "/pet":{ 45 | "post":{ 46 | "tags":[ 47 | "pet" 48 | ], 49 | "summary":"Add a new pet to the store", 50 | "description":"", 51 | "operationId":"addPet", 52 | "consumes":[ 53 | "application/json", 54 | "application/xml" 55 | ], 56 | "produces":[ 57 | "application/xml", 58 | "application/json" 59 | ], 60 | "parameters":[ 61 | { 62 | "in":"body", 63 | "name":"body", 64 | "description":"Pet object that needs to be added to the store", 65 | "required":true, 66 | "schema":{ 67 | "$ref":"#/definitions/Pet" 68 | } 69 | } 70 | ], 71 | "responses":{ 72 | "200":{ 73 | "description":"Pet created" 74 | }, 75 | "405":{ 76 | "description":"Invalid input" 77 | } 78 | }, 79 | "security":[ 80 | { 81 | "petstore_auth":[ 82 | "write:pets", 83 | "read:pets" 84 | ] 85 | } 86 | ] 87 | }, 88 | "put":{ 89 | "tags":[ 90 | "pet" 91 | ], 92 | "summary":"Update an existing pet", 93 | "description":"", 94 | "operationId":"updatePet", 95 | "consumes":[ 96 | "application/json", 97 | "application/xml" 98 | ], 99 | "produces":[ 100 | "application/xml", 101 | "application/json" 102 | ], 103 | "parameters":[ 104 | { 105 | "in":"body", 106 | "name":"body", 107 | "description":"Pet object that needs to be added to the store", 108 | "required":true, 109 | "schema":{ 110 | "$ref":"#/definitions/Pet" 111 | } 112 | } 113 | ], 114 | "responses":{ 115 | "400":{ 116 | "description":"Invalid ID supplied" 117 | }, 118 | "404":{ 119 | "description":"Pet not found" 120 | }, 121 | "405":{ 122 | "description":"Validation exception" 123 | } 124 | }, 125 | "security":[ 126 | { 127 | "petstore_auth":[ 128 | "write:pets", 129 | "read:pets" 130 | ] 131 | } 132 | ] 133 | } 134 | }, 135 | "/pet/findByStatus":{ 136 | "get":{ 137 | "tags":[ 138 | "pet" 139 | ], 140 | "summary":"Finds Pets by status", 141 | "description":"Multiple status values can be provided with comma separated strings", 142 | "operationId":"findPetsByStatus", 143 | "produces":[ 144 | "application/xml", 145 | "application/json" 146 | ], 147 | "parameters":[ 148 | { 149 | "name":"status", 150 | "in":"query", 151 | "description":"Status values that need to be considered for filter", 152 | "required":true, 153 | "type":"array", 154 | "items":{ 155 | "type":"string", 156 | "enum":[ 157 | "available", 158 | "pending", 159 | "sold" 160 | ], 161 | "default":"available" 162 | }, 163 | "collectionFormat":"multi" 164 | } 165 | ], 166 | "responses":{ 167 | "200":{ 168 | "description":"successful operation", 169 | "schema":{ 170 | "type":"array", 171 | "items":{ 172 | "$ref":"#/definitions/Pet" 173 | } 174 | } 175 | }, 176 | "400":{ 177 | "description":"Invalid status value" 178 | } 179 | }, 180 | "security":[ 181 | { 182 | "petstore_auth":[ 183 | "write:pets", 184 | "read:pets" 185 | ] 186 | } 187 | ] 188 | } 189 | }, 190 | "/pet/findByTags":{ 191 | "get":{ 192 | "tags":[ 193 | "pet" 194 | ], 195 | "summary":"Finds Pets by tags", 196 | "description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 197 | "operationId":"findPetsByTags", 198 | "produces":[ 199 | "application/xml", 200 | "application/json" 201 | ], 202 | "parameters":[ 203 | { 204 | "name":"tags", 205 | "in":"query", 206 | "description":"Tags to filter by", 207 | "required":true, 208 | "type":"array", 209 | "items":{ 210 | "type":"string" 211 | }, 212 | "collectionFormat":"multi" 213 | } 214 | ], 215 | "responses":{ 216 | "200":{ 217 | "description":"successful operation", 218 | "schema":{ 219 | "type":"array", 220 | "items":{ 221 | "$ref":"#/definitions/Pet" 222 | } 223 | } 224 | }, 225 | "400":{ 226 | "description":"Invalid tag value" 227 | } 228 | }, 229 | "security":[ 230 | { 231 | "petstore_auth":[ 232 | "write:pets", 233 | "read:pets" 234 | ] 235 | } 236 | ], 237 | "deprecated":true 238 | } 239 | }, 240 | "/pet/{petId}":{ 241 | "get":{ 242 | "tags":[ 243 | "pet" 244 | ], 245 | "summary":"Find pet by ID", 246 | "description":"Returns a single pet", 247 | "operationId":"getPetById", 248 | "produces":[ 249 | "application/xml", 250 | "application/json" 251 | ], 252 | "parameters":[ 253 | { 254 | "name":"petId", 255 | "in":"path", 256 | "description":"ID of pet to return", 257 | "required":true, 258 | "type":"integer", 259 | "format":"int64" 260 | } 261 | ], 262 | "responses":{ 263 | "200":{ 264 | "description":"successful operation", 265 | "schema":{ 266 | "$ref":"#/definitions/Pet" 267 | } 268 | }, 269 | "400":{ 270 | "description":"Invalid ID supplied" 271 | }, 272 | "404":{ 273 | "description":"Pet not found" 274 | } 275 | }, 276 | "security":[ 277 | { 278 | "api_key":[ 279 | 280 | ] 281 | } 282 | ] 283 | }, 284 | "post":{ 285 | "tags":[ 286 | "pet" 287 | ], 288 | "summary":"Updates a pet in the store with form data", 289 | "description":"", 290 | "operationId":"updatePetWithForm", 291 | "consumes":[ 292 | "application/x-www-form-urlencoded" 293 | ], 294 | "produces":[ 295 | "application/xml", 296 | "application/json" 297 | ], 298 | "parameters":[ 299 | { 300 | "name":"petId", 301 | "in":"path", 302 | "description":"ID of pet that needs to be updated", 303 | "required":true, 304 | "type":"integer", 305 | "format":"int64" 306 | }, 307 | { 308 | "name":"name", 309 | "in":"formData", 310 | "description":"Updated name of the pet", 311 | "required":false, 312 | "type":"string" 313 | }, 314 | { 315 | "name":"status", 316 | "in":"formData", 317 | "description":"Updated status of the pet", 318 | "required":false, 319 | "type":"string" 320 | } 321 | ], 322 | "responses":{ 323 | "405":{ 324 | "description":"Invalid input" 325 | } 326 | }, 327 | "security":[ 328 | { 329 | "petstore_auth":[ 330 | "write:pets", 331 | "read:pets" 332 | ] 333 | } 334 | ] 335 | }, 336 | "delete":{ 337 | "tags":[ 338 | "pet" 339 | ], 340 | "summary":"Deletes a pet", 341 | "description":"", 342 | "operationId":"deletePet", 343 | "produces":[ 344 | "application/xml", 345 | "application/json" 346 | ], 347 | "parameters":[ 348 | { 349 | "name":"api_key", 350 | "in":"header", 351 | "required":false, 352 | "type":"string" 353 | }, 354 | { 355 | "name":"petId", 356 | "in":"path", 357 | "description":"Pet id to delete", 358 | "required":true, 359 | "type":"integer", 360 | "format":"int64" 361 | } 362 | ], 363 | "responses":{ 364 | "200":{ 365 | "description": "Pet deleted" 366 | }, 367 | "400":{ 368 | "description":"Invalid ID supplied" 369 | }, 370 | "404":{ 371 | "description":"Pet not found" 372 | } 373 | }, 374 | "security":[ 375 | { 376 | "petstore_auth":[ 377 | "write:pets", 378 | "read:pets" 379 | ] 380 | } 381 | ] 382 | } 383 | }, 384 | "/pet/{petId}/uploadImage":{ 385 | "post":{ 386 | "tags":[ 387 | "pet" 388 | ], 389 | "summary":"uploads an image", 390 | "description":"", 391 | "operationId":"uploadFile", 392 | "consumes":[ 393 | "multipart/form-data" 394 | ], 395 | "produces":[ 396 | "application/json" 397 | ], 398 | "parameters":[ 399 | { 400 | "name":"petId", 401 | "in":"path", 402 | "description":"ID of pet to update", 403 | "required":true, 404 | "type":"integer", 405 | "format":"int64" 406 | }, 407 | { 408 | "name":"additionalMetadata", 409 | "in":"formData", 410 | "description":"Additional data to pass to server", 411 | "required":false, 412 | "type":"string" 413 | }, 414 | { 415 | "name":"file", 416 | "in":"formData", 417 | "description":"file to upload", 418 | "required":false, 419 | "type":"file" 420 | } 421 | ], 422 | "responses":{ 423 | "200":{ 424 | "description":"successful operation", 425 | "schema":{ 426 | "$ref":"#/definitions/ApiResponse" 427 | } 428 | } 429 | }, 430 | "security":[ 431 | { 432 | "petstore_auth":[ 433 | "write:pets", 434 | "read:pets" 435 | ] 436 | } 437 | ] 438 | } 439 | }, 440 | "/store/inventory":{ 441 | "get":{ 442 | "tags":[ 443 | "store" 444 | ], 445 | "summary":"Returns pet inventories by status", 446 | "description":"Returns a map of status codes to quantities", 447 | "operationId":"getInventory", 448 | "produces":[ 449 | "application/json" 450 | ], 451 | "parameters":[ 452 | 453 | ], 454 | "responses":{ 455 | "200":{ 456 | "description":"successful operation", 457 | "schema":{ 458 | "type":"object", 459 | "additionalProperties":{ 460 | "type":"integer", 461 | "format":"int32" 462 | } 463 | } 464 | } 465 | }, 466 | "security":[ 467 | { 468 | "api_key":[ 469 | 470 | ] 471 | } 472 | ] 473 | } 474 | }, 475 | "/store/order":{ 476 | "post":{ 477 | "tags":[ 478 | "store" 479 | ], 480 | "summary":"Place an order for a pet", 481 | "description":"", 482 | "operationId":"placeOrder", 483 | "produces":[ 484 | "application/xml", 485 | "application/json" 486 | ], 487 | "parameters":[ 488 | { 489 | "in":"body", 490 | "name":"body", 491 | "description":"order placed for purchasing the pet", 492 | "required":true, 493 | "schema":{ 494 | "$ref":"#/definitions/Order" 495 | } 496 | } 497 | ], 498 | "responses":{ 499 | "200":{ 500 | "description":"successful operation", 501 | "schema":{ 502 | "$ref":"#/definitions/Order" 503 | } 504 | }, 505 | "400":{ 506 | "description":"Invalid Order" 507 | } 508 | } 509 | } 510 | }, 511 | "/store/order/{orderId}":{ 512 | "get":{ 513 | "tags":[ 514 | "store" 515 | ], 516 | "summary":"Find purchase order by ID", 517 | "description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 518 | "operationId":"getOrderById", 519 | "produces":[ 520 | "application/xml", 521 | "application/json" 522 | ], 523 | "parameters":[ 524 | { 525 | "name":"orderId", 526 | "in":"path", 527 | "description":"ID of pet that needs to be fetched", 528 | "required":true, 529 | "type":"integer", 530 | "maximum":10.0, 531 | "minimum":1.0, 532 | "format":"int64" 533 | } 534 | ], 535 | "responses":{ 536 | "200":{ 537 | "description":"successful operation", 538 | "schema":{ 539 | "$ref":"#/definitions/Order" 540 | } 541 | }, 542 | "400":{ 543 | "description":"Invalid ID supplied" 544 | }, 545 | "404":{ 546 | "description":"Order not found" 547 | } 548 | } 549 | }, 550 | "delete":{ 551 | "tags":[ 552 | "store" 553 | ], 554 | "summary":"Delete purchase order by ID", 555 | "description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 556 | "operationId":"deleteOrder", 557 | "produces":[ 558 | "application/xml", 559 | "application/json" 560 | ], 561 | "parameters":[ 562 | { 563 | "name":"orderId", 564 | "in":"path", 565 | "description":"ID of the order that needs to be deleted", 566 | "required":true, 567 | "type":"integer", 568 | "minimum":1.0, 569 | "format":"int64" 570 | } 571 | ], 572 | "responses":{ 573 | "400":{ 574 | "description":"Invalid ID supplied" 575 | }, 576 | "404":{ 577 | "description":"Order not found" 578 | } 579 | } 580 | } 581 | }, 582 | "/user":{ 583 | "post":{ 584 | "tags":[ 585 | "user" 586 | ], 587 | "summary":"Create user", 588 | "description":"This can only be done by the logged in user.", 589 | "operationId":"createUser", 590 | "produces":[ 591 | "application/xml", 592 | "application/json" 593 | ], 594 | "parameters":[ 595 | { 596 | "in":"body", 597 | "name":"body", 598 | "description":"Created user object", 599 | "required":true, 600 | "schema":{ 601 | "$ref":"#/definitions/User" 602 | } 603 | } 604 | ], 605 | "responses":{ 606 | "default":{ 607 | "description":"successful operation" 608 | } 609 | } 610 | } 611 | }, 612 | "/user/createWithArray":{ 613 | "post":{ 614 | "tags":[ 615 | "user" 616 | ], 617 | "summary":"Creates list of users with given input array", 618 | "description":"", 619 | "operationId":"createUsersWithArrayInput", 620 | "produces":[ 621 | "application/xml", 622 | "application/json" 623 | ], 624 | "parameters":[ 625 | { 626 | "in":"body", 627 | "name":"body", 628 | "description":"List of user object", 629 | "required":true, 630 | "schema":{ 631 | "type":"array", 632 | "items":{ 633 | "$ref":"#/definitions/User" 634 | } 635 | } 636 | } 637 | ], 638 | "responses":{ 639 | "default":{ 640 | "description":"successful operation" 641 | } 642 | } 643 | } 644 | }, 645 | "/user/createWithList":{ 646 | "post":{ 647 | "tags":[ 648 | "user" 649 | ], 650 | "summary":"Creates list of users with given input array", 651 | "description":"", 652 | "operationId":"createUsersWithListInput", 653 | "produces":[ 654 | "application/xml", 655 | "application/json" 656 | ], 657 | "parameters":[ 658 | { 659 | "in":"body", 660 | "name":"body", 661 | "description":"List of user object", 662 | "required":true, 663 | "schema":{ 664 | "type":"array", 665 | "items":{ 666 | "$ref":"#/definitions/User" 667 | } 668 | } 669 | } 670 | ], 671 | "responses":{ 672 | "default":{ 673 | "description":"successful operation" 674 | } 675 | } 676 | } 677 | }, 678 | "/user/login":{ 679 | "get":{ 680 | "tags":[ 681 | "user" 682 | ], 683 | "summary":"Logs user into the system", 684 | "description":"", 685 | "operationId":"loginUser", 686 | "produces":[ 687 | "application/xml", 688 | "application/json" 689 | ], 690 | "parameters":[ 691 | { 692 | "name":"username", 693 | "in":"query", 694 | "description":"The user name for login", 695 | "required":true, 696 | "type":"string" 697 | }, 698 | { 699 | "name":"password", 700 | "in":"query", 701 | "description":"The password for login in clear text", 702 | "required":true, 703 | "type":"string" 704 | } 705 | ], 706 | "responses":{ 707 | "200":{ 708 | "description":"successful operation", 709 | "schema":{ 710 | "type":"string" 711 | }, 712 | "headers":{ 713 | "X-Rate-Limit":{ 714 | "type":"integer", 715 | "format":"int32", 716 | "description":"calls per hour allowed by the user" 717 | }, 718 | "X-Expires-After":{ 719 | "type":"string", 720 | "format":"date-time", 721 | "description":"date in UTC when token expires" 722 | } 723 | } 724 | }, 725 | "400":{ 726 | "description":"Invalid username/password supplied" 727 | } 728 | } 729 | } 730 | }, 731 | "/user/logout":{ 732 | "get":{ 733 | "tags":[ 734 | "user" 735 | ], 736 | "summary":"Logs out current logged in user session", 737 | "description":"", 738 | "operationId":"logoutUser", 739 | "produces":[ 740 | "application/xml", 741 | "application/json" 742 | ], 743 | "parameters":[ 744 | 745 | ], 746 | "responses":{ 747 | "default":{ 748 | "description":"successful operation" 749 | } 750 | } 751 | } 752 | }, 753 | "/user/{username}":{ 754 | "get":{ 755 | "tags":[ 756 | "user" 757 | ], 758 | "summary":"Get user by user name", 759 | "description":"", 760 | "operationId":"getUserByName", 761 | "produces":[ 762 | "application/xml", 763 | "application/json" 764 | ], 765 | "parameters":[ 766 | { 767 | "name":"username", 768 | "in":"path", 769 | "description":"The name that needs to be fetched. Use user1 for testing. ", 770 | "required":true, 771 | "type":"string" 772 | } 773 | ], 774 | "responses":{ 775 | "200":{ 776 | "description":"successful operation", 777 | "schema":{ 778 | "$ref":"#/definitions/User" 779 | } 780 | }, 781 | "400":{ 782 | "description":"Invalid username supplied" 783 | }, 784 | "404":{ 785 | "description":"User not found" 786 | } 787 | } 788 | }, 789 | "put":{ 790 | "tags":[ 791 | "user" 792 | ], 793 | "summary":"Updated user", 794 | "description":"This can only be done by the logged in user.", 795 | "operationId":"updateUser", 796 | "produces":[ 797 | "application/xml", 798 | "application/json" 799 | ], 800 | "parameters":[ 801 | { 802 | "name":"username", 803 | "in":"path", 804 | "description":"name that need to be updated", 805 | "required":true, 806 | "type":"string" 807 | }, 808 | { 809 | "in":"body", 810 | "name":"body", 811 | "description":"Updated user object", 812 | "required":true, 813 | "schema":{ 814 | "$ref":"#/definitions/User" 815 | } 816 | } 817 | ], 818 | "responses":{ 819 | "400":{ 820 | "description":"Invalid user supplied" 821 | }, 822 | "404":{ 823 | "description":"User not found" 824 | } 825 | } 826 | }, 827 | "delete":{ 828 | "tags":[ 829 | "user" 830 | ], 831 | "summary":"Delete user", 832 | "description":"This can only be done by the logged in user.", 833 | "operationId":"deleteUser", 834 | "produces":[ 835 | "application/xml", 836 | "application/json" 837 | ], 838 | "parameters":[ 839 | { 840 | "name":"username", 841 | "in":"path", 842 | "description":"The name that needs to be deleted", 843 | "required":true, 844 | "type":"string" 845 | } 846 | ], 847 | "responses":{ 848 | "400":{ 849 | "description":"Invalid username supplied" 850 | }, 851 | "404":{ 852 | "description":"User not found" 853 | } 854 | } 855 | } 856 | } 857 | }, 858 | "securityDefinitions":{ 859 | "petstore_auth":{ 860 | "type":"oauth2", 861 | "authorizationUrl":"http://petstore.swagger.io/oauth/dialog", 862 | "flow":"implicit", 863 | "scopes":{ 864 | "write:pets":"modify pets in your account", 865 | "read:pets":"read your pets" 866 | } 867 | }, 868 | "api_key":{ 869 | "type":"apiKey", 870 | "name":"api_key", 871 | "in":"header" 872 | } 873 | }, 874 | "definitions":{ 875 | "Order":{ 876 | "type":"object", 877 | "properties":{ 878 | "id":{ 879 | "type":"integer", 880 | "format":"int64" 881 | }, 882 | "petId":{ 883 | "type":"integer", 884 | "format":"int64" 885 | }, 886 | "quantity":{ 887 | "type":"integer", 888 | "format":"int32" 889 | }, 890 | "shipDate":{ 891 | "type":"string", 892 | "format":"date-time" 893 | }, 894 | "status":{ 895 | "type":"string", 896 | "description":"Order Status", 897 | "enum":[ 898 | "placed", 899 | "approved", 900 | "delivered" 901 | ] 902 | }, 903 | "complete":{ 904 | "type":"boolean", 905 | "default":false 906 | } 907 | }, 908 | "xml":{ 909 | "name":"Order" 910 | } 911 | }, 912 | "Category":{ 913 | "type":"object", 914 | "properties":{ 915 | "id":{ 916 | "type":"integer", 917 | "format":"int64" 918 | }, 919 | "name":{ 920 | "type":"string" 921 | } 922 | }, 923 | "xml":{ 924 | "name":"Category" 925 | } 926 | }, 927 | "User":{ 928 | "type":"object", 929 | "properties":{ 930 | "id":{ 931 | "type":"integer", 932 | "format":"int64" 933 | }, 934 | "username":{ 935 | "type":"string" 936 | }, 937 | "firstName":{ 938 | "type":"string" 939 | }, 940 | "lastName":{ 941 | "type":"string" 942 | }, 943 | "email":{ 944 | "type":"string" 945 | }, 946 | "password":{ 947 | "type":"string" 948 | }, 949 | "phone":{ 950 | "type":"string" 951 | }, 952 | "userStatus":{ 953 | "type":"integer", 954 | "format":"int32", 955 | "description":"User Status" 956 | } 957 | }, 958 | "xml":{ 959 | "name":"User" 960 | } 961 | }, 962 | "Tag":{ 963 | "type":"object", 964 | "properties":{ 965 | "id":{ 966 | "type":"integer", 967 | "format":"int64" 968 | }, 969 | "name":{ 970 | "type":"string" 971 | } 972 | }, 973 | "xml":{ 974 | "name":"Tag" 975 | } 976 | }, 977 | "ApiResponse":{ 978 | "type":"object", 979 | "properties":{ 980 | "code":{ 981 | "type":"integer", 982 | "format":"int32" 983 | }, 984 | "type":{ 985 | "type":"string" 986 | }, 987 | "message":{ 988 | "type":"string" 989 | } 990 | } 991 | }, 992 | "Pet":{ 993 | "type":"object", 994 | "required":[ 995 | "name", 996 | "photoUrls" 997 | ], 998 | "properties":{ 999 | "id":{ 1000 | "type":"integer", 1001 | "format":"int64" 1002 | }, 1003 | "category":{ 1004 | "$ref":"#/definitions/Category" 1005 | }, 1006 | "name":{ 1007 | "type":"string", 1008 | "example":"doggie" 1009 | }, 1010 | "photoUrls":{ 1011 | "type":"array", 1012 | "xml":{ 1013 | "name":"photoUrl", 1014 | "wrapped":true 1015 | }, 1016 | "items":{ 1017 | "type":"string" 1018 | } 1019 | }, 1020 | "tags":{ 1021 | "type":"array", 1022 | "xml":{ 1023 | "name":"tag", 1024 | "wrapped":true 1025 | }, 1026 | "items":{ 1027 | "$ref":"#/definitions/Tag" 1028 | } 1029 | }, 1030 | "status":{ 1031 | "type":"string", 1032 | "description":"pet status in the store", 1033 | "enum":[ 1034 | "available", 1035 | "pending", 1036 | "sold" 1037 | ] 1038 | } 1039 | }, 1040 | "xml":{ 1041 | "name":"Pet" 1042 | } 1043 | } 1044 | }, 1045 | "externalDocs":{ 1046 | "description":"Find out more about Swagger", 1047 | "url":"http://swagger.io" 1048 | } 1049 | } 1050 | -------------------------------------------------------------------------------- /test/petstore2.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 | { 20 | "name":"pet", 21 | "description":"Everything about your Pets", 22 | "externalDocs":{ 23 | "description":"Find out more", 24 | "url":"http://swagger.io" 25 | } 26 | }, 27 | { 28 | "name":"store", 29 | "description":"Access to Petstore orders" 30 | }, 31 | { 32 | "name":"user", 33 | "description":"Operations about user", 34 | "externalDocs":{ 35 | "description":"Find out more about our store", 36 | "url":"http://swagger.io" 37 | } 38 | } 39 | ], 40 | "schemes":[ 41 | "http" 42 | ], 43 | "paths":{ 44 | "/pet2":{ 45 | "post":{ 46 | "tags":[ 47 | "pet" 48 | ], 49 | "summary":"Add a new pet to the store", 50 | "description":"", 51 | "operationId":"addPet", 52 | "consumes":[ 53 | "application/json", 54 | "application/xml" 55 | ], 56 | "produces":[ 57 | "application/xml", 58 | "application/json" 59 | ], 60 | "parameters":[ 61 | { 62 | "in":"body", 63 | "name":"body", 64 | "description":"Pet object that needs to be added to the store", 65 | "required":true, 66 | "schema":{ 67 | "$ref":"#/definitions/Pet" 68 | } 69 | } 70 | ], 71 | "responses":{ 72 | "200":{ 73 | "description":"Pet created" 74 | }, 75 | "405":{ 76 | "description":"Invalid input" 77 | } 78 | }, 79 | "security":[ 80 | { 81 | "petstore_auth":[ 82 | "write:pets", 83 | "read:pets" 84 | ] 85 | } 86 | ] 87 | }, 88 | "put":{ 89 | "tags":[ 90 | "pet" 91 | ], 92 | "summary":"Update an existing pet", 93 | "description":"", 94 | "operationId":"updatePet", 95 | "consumes":[ 96 | "application/json", 97 | "application/xml" 98 | ], 99 | "produces":[ 100 | "application/xml", 101 | "application/json" 102 | ], 103 | "parameters":[ 104 | { 105 | "in":"body", 106 | "name":"body", 107 | "description":"Pet object that needs to be added to the store", 108 | "required":true, 109 | "schema":{ 110 | "$ref":"#/definitions/Pet" 111 | } 112 | } 113 | ], 114 | "responses":{ 115 | "400":{ 116 | "description":"Invalid ID supplied" 117 | }, 118 | "404":{ 119 | "description":"Pet not found" 120 | }, 121 | "405":{ 122 | "description":"Validation exception" 123 | } 124 | }, 125 | "security":[ 126 | { 127 | "petstore_auth":[ 128 | "write:pets", 129 | "read:pets" 130 | ] 131 | } 132 | ] 133 | } 134 | }, 135 | "/pet2/findByStatus":{ 136 | "get":{ 137 | "tags":[ 138 | "pet" 139 | ], 140 | "summary":"Finds Pets by status", 141 | "description":"Multiple status values can be provided with comma separated strings", 142 | "operationId":"findPetsByStatus", 143 | "produces":[ 144 | "application/xml", 145 | "application/json" 146 | ], 147 | "parameters":[ 148 | { 149 | "name":"status", 150 | "in":"query", 151 | "description":"Status values that need to be considered for filter", 152 | "required":true, 153 | "type":"array", 154 | "items":{ 155 | "type":"string", 156 | "enum":[ 157 | "available", 158 | "pending", 159 | "sold" 160 | ], 161 | "default":"available" 162 | }, 163 | "collectionFormat":"multi" 164 | } 165 | ], 166 | "responses":{ 167 | "200":{ 168 | "description":"successful operation", 169 | "schema":{ 170 | "type":"array", 171 | "items":{ 172 | "$ref":"#/definitions/Pet" 173 | } 174 | } 175 | }, 176 | "400":{ 177 | "description":"Invalid status value" 178 | } 179 | }, 180 | "security":[ 181 | { 182 | "petstore_auth":[ 183 | "write:pets", 184 | "read:pets" 185 | ] 186 | } 187 | ] 188 | } 189 | }, 190 | "/pet/findByTags2":{ 191 | "get":{ 192 | "tags":[ 193 | "pet" 194 | ], 195 | "summary":"Finds Pets by tags", 196 | "description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 197 | "operationId":"findPetsByTags", 198 | "produces":[ 199 | "application/xml", 200 | "application/json" 201 | ], 202 | "parameters":[ 203 | { 204 | "name":"tags", 205 | "in":"query", 206 | "description":"Tags to filter by", 207 | "required":true, 208 | "type":"array", 209 | "items":{ 210 | "type":"string" 211 | }, 212 | "collectionFormat":"multi" 213 | } 214 | ], 215 | "responses":{ 216 | "200":{ 217 | "description":"successful operation", 218 | "schema":{ 219 | "type":"array", 220 | "items":{ 221 | "$ref":"#/definitions/Pet" 222 | } 223 | } 224 | }, 225 | "400":{ 226 | "description":"Invalid tag value" 227 | } 228 | }, 229 | "security":[ 230 | { 231 | "petstore_auth":[ 232 | "write:pets", 233 | "read:pets" 234 | ] 235 | } 236 | ], 237 | "deprecated":true 238 | } 239 | }, 240 | "/pet2/{petId}":{ 241 | "get":{ 242 | "tags":[ 243 | "pet" 244 | ], 245 | "summary":"Find pet by ID", 246 | "description":"Returns a single pet", 247 | "operationId":"getPetById", 248 | "produces":[ 249 | "application/xml", 250 | "application/json" 251 | ], 252 | "parameters":[ 253 | { 254 | "name":"petId", 255 | "in":"path", 256 | "description":"ID of pet to return", 257 | "required":true, 258 | "type":"integer", 259 | "format":"int64" 260 | } 261 | ], 262 | "responses":{ 263 | "200":{ 264 | "description":"successful operation", 265 | "schema":{ 266 | "$ref":"#/definitions/Pet" 267 | } 268 | }, 269 | "400":{ 270 | "description":"Invalid ID supplied" 271 | }, 272 | "404":{ 273 | "description":"Pet not found" 274 | } 275 | }, 276 | "security":[ 277 | { 278 | "api_key":[ 279 | 280 | ] 281 | } 282 | ] 283 | }, 284 | "post":{ 285 | "tags":[ 286 | "pet" 287 | ], 288 | "summary":"Updates a pet in the store with form data", 289 | "description":"", 290 | "operationId":"updatePetWithForm", 291 | "consumes":[ 292 | "application/x-www-form-urlencoded" 293 | ], 294 | "produces":[ 295 | "application/xml", 296 | "application/json" 297 | ], 298 | "parameters":[ 299 | { 300 | "name":"petId", 301 | "in":"path", 302 | "description":"ID of pet that needs to be updated", 303 | "required":true, 304 | "type":"integer", 305 | "format":"int64" 306 | }, 307 | { 308 | "name":"name", 309 | "in":"formData", 310 | "description":"Updated name of the pet", 311 | "required":false, 312 | "type":"string" 313 | }, 314 | { 315 | "name":"status", 316 | "in":"formData", 317 | "description":"Updated status of the pet", 318 | "required":false, 319 | "type":"string" 320 | } 321 | ], 322 | "responses":{ 323 | "405":{ 324 | "description":"Invalid input" 325 | } 326 | }, 327 | "security":[ 328 | { 329 | "petstore_auth":[ 330 | "write:pets", 331 | "read:pets" 332 | ] 333 | } 334 | ] 335 | }, 336 | "delete":{ 337 | "tags":[ 338 | "pet" 339 | ], 340 | "summary":"Deletes a pet", 341 | "description":"", 342 | "operationId":"deletePet", 343 | "produces":[ 344 | "application/xml", 345 | "application/json" 346 | ], 347 | "parameters":[ 348 | { 349 | "name":"api_key", 350 | "in":"header", 351 | "required":false, 352 | "type":"string" 353 | }, 354 | { 355 | "name":"petId", 356 | "in":"path", 357 | "description":"Pet id to delete", 358 | "required":true, 359 | "type":"integer", 360 | "format":"int64" 361 | } 362 | ], 363 | "responses":{ 364 | "200":{ 365 | "description": "Pet deleted" 366 | }, 367 | "400":{ 368 | "description":"Invalid ID supplied" 369 | }, 370 | "404":{ 371 | "description":"Pet not found" 372 | } 373 | }, 374 | "security":[ 375 | { 376 | "petstore_auth":[ 377 | "write:pets", 378 | "read:pets" 379 | ] 380 | } 381 | ] 382 | } 383 | }, 384 | "/pet/{petId}/uploadImage":{ 385 | "post":{ 386 | "tags":[ 387 | "pet" 388 | ], 389 | "summary":"uploads an image", 390 | "description":"", 391 | "operationId":"uploadFile", 392 | "consumes":[ 393 | "multipart/form-data" 394 | ], 395 | "produces":[ 396 | "application/json" 397 | ], 398 | "parameters":[ 399 | { 400 | "name":"petId", 401 | "in":"path", 402 | "description":"ID of pet to update", 403 | "required":true, 404 | "type":"integer", 405 | "format":"int64" 406 | }, 407 | { 408 | "name":"additionalMetadata", 409 | "in":"formData", 410 | "description":"Additional data to pass to server", 411 | "required":false, 412 | "type":"string" 413 | }, 414 | { 415 | "name":"file", 416 | "in":"formData", 417 | "description":"file to upload", 418 | "required":false, 419 | "type":"file" 420 | } 421 | ], 422 | "responses":{ 423 | "200":{ 424 | "description":"successful operation", 425 | "schema":{ 426 | "$ref":"#/definitions/ApiResponse" 427 | } 428 | } 429 | }, 430 | "security":[ 431 | { 432 | "petstore_auth":[ 433 | "write:pets", 434 | "read:pets" 435 | ] 436 | } 437 | ] 438 | } 439 | }, 440 | "/store/inventory":{ 441 | "get":{ 442 | "tags":[ 443 | "store" 444 | ], 445 | "summary":"Returns pet inventories by status", 446 | "description":"Returns a map of status codes to quantities", 447 | "operationId":"getInventory", 448 | "produces":[ 449 | "application/json" 450 | ], 451 | "parameters":[ 452 | 453 | ], 454 | "responses":{ 455 | "200":{ 456 | "description":"successful operation", 457 | "schema":{ 458 | "type":"object", 459 | "additionalProperties":{ 460 | "type":"integer", 461 | "format":"int32" 462 | } 463 | } 464 | } 465 | }, 466 | "security":[ 467 | { 468 | "api_key":[ 469 | 470 | ] 471 | } 472 | ] 473 | } 474 | }, 475 | "/store/order":{ 476 | "post":{ 477 | "tags":[ 478 | "store" 479 | ], 480 | "summary":"Place an order for a pet", 481 | "description":"", 482 | "operationId":"placeOrder", 483 | "produces":[ 484 | "application/xml", 485 | "application/json" 486 | ], 487 | "parameters":[ 488 | { 489 | "in":"body", 490 | "name":"body", 491 | "description":"order placed for purchasing the pet", 492 | "required":true, 493 | "schema":{ 494 | "$ref":"#/definitions/Order" 495 | } 496 | } 497 | ], 498 | "responses":{ 499 | "200":{ 500 | "description":"successful operation", 501 | "schema":{ 502 | "$ref":"#/definitions/Order" 503 | } 504 | }, 505 | "400":{ 506 | "description":"Invalid Order" 507 | } 508 | } 509 | } 510 | }, 511 | "/store/order/{orderId}":{ 512 | "get":{ 513 | "tags":[ 514 | "store" 515 | ], 516 | "summary":"Find purchase order by ID", 517 | "description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 518 | "operationId":"getOrderById", 519 | "produces":[ 520 | "application/xml", 521 | "application/json" 522 | ], 523 | "parameters":[ 524 | { 525 | "name":"orderId", 526 | "in":"path", 527 | "description":"ID of pet that needs to be fetched", 528 | "required":true, 529 | "type":"integer", 530 | "maximum":10.0, 531 | "minimum":1.0, 532 | "format":"int64" 533 | } 534 | ], 535 | "responses":{ 536 | "200":{ 537 | "description":"successful operation", 538 | "schema":{ 539 | "$ref":"#/definitions/Order" 540 | } 541 | }, 542 | "400":{ 543 | "description":"Invalid ID supplied" 544 | }, 545 | "404":{ 546 | "description":"Order not found" 547 | } 548 | } 549 | }, 550 | "delete":{ 551 | "tags":[ 552 | "store" 553 | ], 554 | "summary":"Delete purchase order by ID", 555 | "description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 556 | "operationId":"deleteOrder", 557 | "produces":[ 558 | "application/xml", 559 | "application/json" 560 | ], 561 | "parameters":[ 562 | { 563 | "name":"orderId", 564 | "in":"path", 565 | "description":"ID of the order that needs to be deleted", 566 | "required":true, 567 | "type":"integer", 568 | "minimum":1.0, 569 | "format":"int64" 570 | } 571 | ], 572 | "responses":{ 573 | "400":{ 574 | "description":"Invalid ID supplied" 575 | }, 576 | "404":{ 577 | "description":"Order not found" 578 | } 579 | } 580 | } 581 | }, 582 | "/user":{ 583 | "post":{ 584 | "tags":[ 585 | "user" 586 | ], 587 | "summary":"Create user", 588 | "description":"This can only be done by the logged in user.", 589 | "operationId":"createUser", 590 | "produces":[ 591 | "application/xml", 592 | "application/json" 593 | ], 594 | "parameters":[ 595 | { 596 | "in":"body", 597 | "name":"body", 598 | "description":"Created user object", 599 | "required":true, 600 | "schema":{ 601 | "$ref":"#/definitions/User" 602 | } 603 | } 604 | ], 605 | "responses":{ 606 | "default":{ 607 | "description":"successful operation" 608 | } 609 | } 610 | } 611 | }, 612 | "/user/createWithArray":{ 613 | "post":{ 614 | "tags":[ 615 | "user" 616 | ], 617 | "summary":"Creates list of users with given input array", 618 | "description":"", 619 | "operationId":"createUsersWithArrayInput", 620 | "produces":[ 621 | "application/xml", 622 | "application/json" 623 | ], 624 | "parameters":[ 625 | { 626 | "in":"body", 627 | "name":"body", 628 | "description":"List of user object", 629 | "required":true, 630 | "schema":{ 631 | "type":"array", 632 | "items":{ 633 | "$ref":"#/definitions/User" 634 | } 635 | } 636 | } 637 | ], 638 | "responses":{ 639 | "default":{ 640 | "description":"successful operation" 641 | } 642 | } 643 | } 644 | }, 645 | "/user/createWithList":{ 646 | "post":{ 647 | "tags":[ 648 | "user" 649 | ], 650 | "summary":"Creates list of users with given input array", 651 | "description":"", 652 | "operationId":"createUsersWithListInput", 653 | "produces":[ 654 | "application/xml", 655 | "application/json" 656 | ], 657 | "parameters":[ 658 | { 659 | "in":"body", 660 | "name":"body", 661 | "description":"List of user object", 662 | "required":true, 663 | "schema":{ 664 | "type":"array", 665 | "items":{ 666 | "$ref":"#/definitions/User" 667 | } 668 | } 669 | } 670 | ], 671 | "responses":{ 672 | "default":{ 673 | "description":"successful operation" 674 | } 675 | } 676 | } 677 | }, 678 | "/user/login":{ 679 | "get":{ 680 | "tags":[ 681 | "user" 682 | ], 683 | "summary":"Logs user into the system", 684 | "description":"", 685 | "operationId":"loginUser", 686 | "produces":[ 687 | "application/xml", 688 | "application/json" 689 | ], 690 | "parameters":[ 691 | { 692 | "name":"username", 693 | "in":"query", 694 | "description":"The user name for login", 695 | "required":true, 696 | "type":"string" 697 | }, 698 | { 699 | "name":"password", 700 | "in":"query", 701 | "description":"The password for login in clear text", 702 | "required":true, 703 | "type":"string" 704 | } 705 | ], 706 | "responses":{ 707 | "200":{ 708 | "description":"successful operation", 709 | "schema":{ 710 | "type":"string" 711 | }, 712 | "headers":{ 713 | "X-Rate-Limit":{ 714 | "type":"integer", 715 | "format":"int32", 716 | "description":"calls per hour allowed by the user" 717 | }, 718 | "X-Expires-After":{ 719 | "type":"string", 720 | "format":"date-time", 721 | "description":"date in UTC when token expires" 722 | } 723 | } 724 | }, 725 | "400":{ 726 | "description":"Invalid username/password supplied" 727 | } 728 | } 729 | } 730 | }, 731 | "/user/logout":{ 732 | "get":{ 733 | "tags":[ 734 | "user" 735 | ], 736 | "summary":"Logs out current logged in user session", 737 | "description":"", 738 | "operationId":"logoutUser", 739 | "produces":[ 740 | "application/xml", 741 | "application/json" 742 | ], 743 | "parameters":[ 744 | 745 | ], 746 | "responses":{ 747 | "default":{ 748 | "description":"successful operation" 749 | } 750 | } 751 | } 752 | }, 753 | "/user/{username}":{ 754 | "get":{ 755 | "tags":[ 756 | "user" 757 | ], 758 | "summary":"Get user by user name", 759 | "description":"", 760 | "operationId":"getUserByName", 761 | "produces":[ 762 | "application/xml", 763 | "application/json" 764 | ], 765 | "parameters":[ 766 | { 767 | "name":"username", 768 | "in":"path", 769 | "description":"The name that needs to be fetched. Use user1 for testing. ", 770 | "required":true, 771 | "type":"string" 772 | } 773 | ], 774 | "responses":{ 775 | "200":{ 776 | "description":"successful operation", 777 | "schema":{ 778 | "$ref":"#/definitions/User" 779 | } 780 | }, 781 | "400":{ 782 | "description":"Invalid username supplied" 783 | }, 784 | "404":{ 785 | "description":"User not found" 786 | } 787 | } 788 | }, 789 | "put":{ 790 | "tags":[ 791 | "user" 792 | ], 793 | "summary":"Updated user", 794 | "description":"This can only be done by the logged in user.", 795 | "operationId":"updateUser", 796 | "produces":[ 797 | "application/xml", 798 | "application/json" 799 | ], 800 | "parameters":[ 801 | { 802 | "name":"username", 803 | "in":"path", 804 | "description":"name that need to be updated", 805 | "required":true, 806 | "type":"string" 807 | }, 808 | { 809 | "in":"body", 810 | "name":"body", 811 | "description":"Updated user object", 812 | "required":true, 813 | "schema":{ 814 | "$ref":"#/definitions/User" 815 | } 816 | } 817 | ], 818 | "responses":{ 819 | "400":{ 820 | "description":"Invalid user supplied" 821 | }, 822 | "404":{ 823 | "description":"User not found" 824 | } 825 | } 826 | }, 827 | "delete":{ 828 | "tags":[ 829 | "user" 830 | ], 831 | "summary":"Delete user", 832 | "description":"This can only be done by the logged in user.", 833 | "operationId":"deleteUser", 834 | "produces":[ 835 | "application/xml", 836 | "application/json" 837 | ], 838 | "parameters":[ 839 | { 840 | "name":"username", 841 | "in":"path", 842 | "description":"The name that needs to be deleted", 843 | "required":true, 844 | "type":"string" 845 | } 846 | ], 847 | "responses":{ 848 | "400":{ 849 | "description":"Invalid username supplied" 850 | }, 851 | "404":{ 852 | "description":"User not found" 853 | } 854 | } 855 | } 856 | } 857 | }, 858 | "securityDefinitions":{ 859 | "petstore_auth":{ 860 | "type":"oauth2", 861 | "authorizationUrl":"http://petstore.swagger.io/oauth/dialog", 862 | "flow":"implicit", 863 | "scopes":{ 864 | "write:pets":"modify pets in your account", 865 | "read:pets":"read your pets" 866 | } 867 | }, 868 | "api_key":{ 869 | "type":"apiKey", 870 | "name":"api_key", 871 | "in":"header" 872 | } 873 | }, 874 | "definitions":{ 875 | "Order":{ 876 | "type":"object", 877 | "properties":{ 878 | "id":{ 879 | "type":"integer", 880 | "format":"int64" 881 | }, 882 | "petId":{ 883 | "type":"integer", 884 | "format":"int64" 885 | }, 886 | "quantity":{ 887 | "type":"integer", 888 | "format":"int32" 889 | }, 890 | "shipDate":{ 891 | "type":"string", 892 | "format":"date-time" 893 | }, 894 | "status":{ 895 | "type":"string", 896 | "description":"Order Status", 897 | "enum":[ 898 | "placed", 899 | "approved", 900 | "delivered" 901 | ] 902 | }, 903 | "complete":{ 904 | "type":"boolean", 905 | "default":false 906 | } 907 | }, 908 | "xml":{ 909 | "name":"Order" 910 | } 911 | }, 912 | "Category":{ 913 | "type":"object", 914 | "properties":{ 915 | "id":{ 916 | "type":"integer", 917 | "format":"int64" 918 | }, 919 | "name":{ 920 | "type":"string" 921 | } 922 | }, 923 | "xml":{ 924 | "name":"Category" 925 | } 926 | }, 927 | "User":{ 928 | "type":"object", 929 | "properties":{ 930 | "id":{ 931 | "type":"integer", 932 | "format":"int64" 933 | }, 934 | "username":{ 935 | "type":"string" 936 | }, 937 | "firstName":{ 938 | "type":"string" 939 | }, 940 | "lastName":{ 941 | "type":"string" 942 | }, 943 | "email":{ 944 | "type":"string" 945 | }, 946 | "password":{ 947 | "type":"string" 948 | }, 949 | "phone":{ 950 | "type":"string" 951 | }, 952 | "userStatus":{ 953 | "type":"integer", 954 | "format":"int32", 955 | "description":"User Status" 956 | } 957 | }, 958 | "xml":{ 959 | "name":"User" 960 | } 961 | }, 962 | "Tag":{ 963 | "type":"object", 964 | "properties":{ 965 | "id":{ 966 | "type":"integer", 967 | "format":"int64" 968 | }, 969 | "name":{ 970 | "type":"string" 971 | } 972 | }, 973 | "xml":{ 974 | "name":"Tag" 975 | } 976 | }, 977 | "ApiResponse":{ 978 | "type":"object", 979 | "properties":{ 980 | "code":{ 981 | "type":"integer", 982 | "format":"int32" 983 | }, 984 | "type":{ 985 | "type":"string" 986 | }, 987 | "message":{ 988 | "type":"string" 989 | } 990 | } 991 | }, 992 | "Pet":{ 993 | "type":"object", 994 | "required":[ 995 | "name", 996 | "photoUrls" 997 | ], 998 | "properties":{ 999 | "id":{ 1000 | "type":"integer", 1001 | "format":"int64" 1002 | }, 1003 | "category":{ 1004 | "$ref":"#/definitions/Category" 1005 | }, 1006 | "name":{ 1007 | "type":"string", 1008 | "example":"doggie" 1009 | }, 1010 | "photoUrls":{ 1011 | "type":"array", 1012 | "xml":{ 1013 | "name":"photoUrl", 1014 | "wrapped":true 1015 | }, 1016 | "items":{ 1017 | "type":"string" 1018 | } 1019 | }, 1020 | "tags":{ 1021 | "type":"array", 1022 | "xml":{ 1023 | "name":"tag", 1024 | "wrapped":true 1025 | }, 1026 | "items":{ 1027 | "$ref":"#/definitions/Tag" 1028 | } 1029 | }, 1030 | "status":{ 1031 | "type":"string", 1032 | "description":"pet status in the store", 1033 | "enum":[ 1034 | "available", 1035 | "pending", 1036 | "sold" 1037 | ] 1038 | } 1039 | }, 1040 | "xml":{ 1041 | "name":"Pet" 1042 | } 1043 | } 1044 | }, 1045 | "externalDocs":{ 1046 | "description":"Find out more about Swagger", 1047 | "url":"http://swagger.io" 1048 | } 1049 | } 1050 | --------------------------------------------------------------------------------