[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"env\"\n  ],\n  \"plugins\": [\n    \"transform-runtime\",\n    \"transform-object-rest-spread\"\n  ]\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\nnpm-debug.log\n.idea\nsrc/fileSources/remoteFiles/*.yml\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\nbin/\nnode_modules/\n\nnpm-debug.log\n\ncli/morphling-config.json\n\nsrc/data/*.yaml\nsrc/data/*.yml\nsrc/data/*.json\n\n*.tgz\npackage\n"
  },
  {
    "path": ".npmignore",
    "content": "src\ncli\n.babelrc\n.idea\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:8.2.1\nRUN mkdir -p /usr/app\nWORKDIR /usr/app\n\nCOPY ./package.json ./\nRUN npm install\nRUN npm install pm2 -g --silent\nCOPY bin/src ./src\nRUN mkdir -p /usr/app/src/data\n\nARG NODE_PORT=8883\nENV NODE_PORT=$NODE_PORT\n\nEXPOSE ${NODE_PORT}\nCMD [\"pm2-docker\", \"src/server.js\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2017 Leboncoin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Morphling\n\n*Cause nobody aint got time to wait for back-ends to be developped.*\n\n\n## Features\n\n- A sweet all-in-CLI with **no knowledge of Javascript necessary** _(if you can write JSON you good 👍 )_\n- **Mocks any object, array or field** declared in a swagger with meaningful **autogenerated data**\n- **Persists route mocks** to allow you to develop your front-end as fast as a lightning\n- Uses **Faker generators** behind the curtains\n- **Empty responses, error codes, any http method, any body** _not sure about that last one but I'm trying ok_\n- Two lines install and **minimal knowledge of backend necessary**\n- Supports **JSON and YAML** swaggers\n- Supports (a big part of) **OpenAPI 3.0**\n\n## Installation\n\n- Ensure that you have both `docker` and `docker-compose` installed and available to your terminal.\n\n- Also, make sure that the docker daemon is running.\n\n- Now just go for it: `npm i -g morphlingjs` it takes a while so grab a cub of coffee on the way.\n\n- Run the configuration utility to select the port morphling will run on: `morphling config`\n\n- Add a swagger.yml file to Morphling by doing a `morphling apply your/file`\n\nNow morphling knows your swagger and can mock it properly. \nJust try to hit a route that is described in your swagger !\n\nTo see what swaggers Morphling knows do a `morphling list`.\n\n*Note: If having any issues during the install with npm such as 'Cannot read property 0 of undefined', \ntry to downgrade your current npm install to 5.2.0* \n\n## Overriding a route\n\nMorphling allows you to create a mock for a specific route ; because sometimes you need a specific body from your server.\n\nFor instance, you want your local server to return a specific response on a given route. It can be a route that is described\nin your swagger where you don't want autogenerated data or just a route you don't want to bother putting in the swagger.\n\nYou will want to use **an override file**, which you can find two examples of in the [examples](https://github.com/leboncoin/morphlingjs/tree/master/swagger-override-examples).\n\nDoing a \n\n- `morphling apply swagger-override-examples/demo.json -o` \n\nand then \n\n- `morphling toggle demo.json`\n\nwill create a route on `localhost:8883/v2/store/order/1` and then enable it (if morphling runs on 8883). This route will return, as seen in the example file,\na 200 (OK) HTTP code, with an empty body.\n\n## CLI Commands\n\nAny command here can be run with the `--help` flag to give you a tad more info 👍\n\n### help\n   - `help [cmd]             display help for [cmd]`\n\nDisplays the help\n\n### apply\n`apply|a <file>         Apply a Swagger to morphling`\n\nSend a Swagger file to the Morphling instance for it to be mocked. \n\nIf used with `-o` flag, save file as an **Override file** to Morphling.\nOverrides are **disabled by default**. To enable/disable them, check the `toggle` command.\n\nThere is no validation working on the server at the time of writing this which means that it will break silently.\nIf a route is not mocked, I suggest that you `morphling bash` into the instance and run `pm2 logs` while trying to `apply` the swagger.\nMight give you some info. If not, please send an issue! I'll do my best to fix it ASAP.\n\n### bash\n   - `bash|b                 Bash into the morphling container`\n\nOpen a bash straight into the container. Useful for checking logs with `pm2 logs` for instance.\n\n### config\n   - `config|c               Configure the morphling server`\n   \nPersist CLI options, for instance the port that the CLI will hit on localhost\n\n### describe\n   - `describe|d <filename>  Describe a morphling override`\n   \nDescribe an override file's content.\n\n### list\n   - `list|ls                List files saved in morphling`\n\nList all swaggers saved to Morphling. Use with `-o` to list override files.\n   \n### remove - NOT IMPLEMENTED YET\n\n   - `remove|rm              Remove a Swagger file saved in Morphling`\n### remove-all - NOT IMPLEMENTED YET\n\n   - `remove-all|rma         Clear Swagger files saved in Morphling`\n   \n### restart\n   - `restart|rr             Restart the morphling server`\n   \nRestart the Morphling instance. _who would have thought_\n\n### start\n   - `start|s                Start the morphling server on 8883 if no port is provided`\n   \nStart the morphling instance.\n\n### stop\n   - `stop|k                 Stop the morphling server`\\\n  \nStop the morphling instance.\n\n### toggle\n   - `toggle|t <filename>    Toggle an override`\n   \nEnable/disable an override. Use with `-d` to disable all, or `-e` to enable all.\n\n\n_Note: if you think about other useful commands, feel free to submit an issue! 😉_\n\n## Installation from source \n\n_Here be dragons, you probably don't need to do this, except if want to contribute, which would be lovely._\n\nEnsure that you have both `docker` and `docker-compose` installed and available to your terminal.\n\n- Clone the package\n- Inside the package: `make start` and wait for the build to be over\n- If the message `Morphling started on 8883` pops, it means that everything went well!\n- `npm link` to make `morphling` available globally to your terminal\n- `morphling config` and go through the short process\n- `morphling --help` for other informations should help you out.\n\n*Note: If having any issues during the install with npm such as 'Cannot read property 0 of undefined', \ntry to downgrade your current npm install to 5.2.0* \n\n## Useful development commands\n\nI've made available a `morphling bash` command which immediately bashes you inside the currently-running Docker \ninstance of Morphling\n\n`make `\n\n- `start` Run the build proces and start the Morphling docker instance immediatly then pipe the logs\n- `dev` Start the server without docker on a bare Nodejs process\n- `build` Build all javascript files with *Babel*.\n- `docker-build` Run the docker build process and run the server.\n- `clear-docker` Hard-delete the Morphling instance from your local Docker\n- `devstart-hard` Clear all dependencies and docker Morphling instances and start\n\n## Upcoming features _feel free to submit PRs_\n\n- SwaggerUI!\n- Print proper Morphlingjs version with `--version / -v`\n- On the go switching of running ports\n- Add ID to overrides and Swagger files so you dont have to type them down / or maybe just autocomplete them\n- `morphling validate` A proper validator for the swagger files and an associated CLI command\n- `morphling remove` Remove a file from Morphling\n- `morphling remove-all` Remove all files from Morphling (also remove local saves of fixtures) always with --force\n- A better autocompletion using the npm package `commander-completion`\n- Overriding a field key in an object with a specific faker generator (ie: zipcode, username...)\n- Override checker (ensure that a specific override does not already exist when adding another one)\n"
  },
  {
    "path": "cli/index.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\n\nprogram\n    .version('0.0.1')\n    .description('CLI interface for Morphling')\n    .command('apply <file>', 'Apply a Swagger to morphling').alias('a')\n    .command('bash', 'Bash into the morphling container').alias('b')\n    .command('config', 'Configure the morphling server').alias('c')\n    .command('describe <filename>', 'Describe a morphling override').alias('d')\n    .command('list', 'List files saved in morphling').alias('ls')\n    .command('remove', 'Remove a Swagger file saved in Morphling').alias('rm')\n    .command('remove-all', 'Clear Swagger files saved in Morphling').alias('rma')\n    .command('restart', 'Restart the morphling server').alias('rr')\n    .command('start', 'Start the morphling server on 8883 if no port is provided').alias('s')\n    .command('stop', 'Stop the morphling server').alias('k')\n    .command('toggle <filename>', 'Toggle an override').alias('t')\n\nprogram.parse(process.argv);\n"
  },
  {
    "path": "cli/morphling-apply.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport FormData from 'form-data';\nimport chalk from 'chalk';\nimport fetch from 'node-fetch';\nimport fs from 'fs';\nimport * as utilFunctions from './utils/util'\nimport { restartAction } from './morphling-restart';\n\nconst applyAction = async (dir) => {\n    try {\n        const config = await utilFunctions.returnConfigIfExists();\n        const port = config.port || program.port || 8883;\n        const isOverrideFile = program.override || false;\n        const url = `http://localhost:${port}`;\n        const splitDir = dir.split('/');\n        const filename = splitDir[splitDir.length - 1];\n        const filenameForOverride = isOverrideFile ? `morphling-override-${filename}` : filename;\n\n        const listUrl = `${url}/morphling/list?filename=${filenameForOverride}`;\n\n        const request = await fetch(listUrl);\n        const { fileExists } = await request.json();\n\n        if (!fileExists || program.force) {\n            console.log(chalk.green(`Applying '${chalk.bold(filename)}' to Morphling ...`));\n\n            const form = new FormData();\n\n            form.append('file', fs.createReadStream(`${process.cwd()}/${dir}`));\n            form.append('fileName', filenameForOverride);\n\n            await fetch(`${url}/morphling/apply${isOverrideFile ? '?override=true' : ''}`, {\n                method: 'POST',\n                body: form,\n            });\n            console.log(chalk.green(`${isOverrideFile ? 'Override file' : 'File'} '${chalk.bold(filename)}' successfully uploaded! Restarting...`));\n            await restartAction();\n        } else {\n            console.log(chalk.red(`${isOverrideFile ? 'Override file' : 'File'} '${chalk.bold(filename)}' already exists on Morphling. Type morphling apply with -f to force.`));\n        }\n    } catch (e) {\n        if (e.code === 'ECONNREFUSED') {\n            console.log(chalk.red(`Connection to Morphling was refused. Is Morphling currently running?`));\n        }\n    }\n}\n\n\nprogram\n    .action(applyAction)\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .option('-f, --force', 'Erase file that might already exist')\n    .option('-o --override', 'Apply a custom override to morphling')\n    .parse(process.argv);\n"
  },
  {
    "path": "cli/morphling-bash.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\nimport _ from 'lodash';\nimport childProcess from 'child_process';\n\nprogram\n    .parse(process.argv);\n\nconsole.log(chalk.white('Container ID is:'));\n\nshell.exec('docker ps --filter \"name=morphlingjs_web\" -q', (code, stdout, stderr) => {\n    if (stderr) {\n        console.log(stderr);\n    } else {\n        console.log(chalk.white(`Bashing into Morphling.. Type ${chalk.bold('exit')} to exit.`));\n        childProcess.spawnSync('docker', ['exec', '-it', _.trim(stdout), 'bash'], {\n            stdio: 'inherit'\n        })\n    }\n})\n"
  },
  {
    "path": "cli/morphling-config.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport prompt from 'prompt';\nimport chalk from 'chalk';\nimport promptProps from './utils/prompt-props';\nimport * as utilFunctions from './utils/util';\nimport { deleteConfigFile } from './utils/util';\nimport prettyjson from 'prettyjson';\nimport { returnConfigIfExists } from './utils/util'\n\nprogram\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .option('-f, --force', 'Override existing config')\n    .option('-d --delete', 'Delete existing configuration')\n    .option('-s --show', 'Show existing configuration')\n    .parse(process.argv);\n\n(async () => {\n    const configFileAlreadyExists = await utilFunctions.checkConfigFileExistence();\n\n    if (program.delete) {\n        try {\n            await deleteConfigFile();\n            console.log(`${chalk.green('Config deleted')}`)\n        } catch (e) {\n            if (e.code === 'ENOENT') {\n                console.log(`${chalk.red(' 😱 Config file does not exist !')}`);\n                console.log(`${chalk.blue(`${chalk.bold('morphling config')}`)} will walk you through the config`);\n            } else {\n                console.error(e);\n            }\n        }\n\n        return;\n    } else if (program.show) {\n        try {\n            const config = await returnConfigIfExists();\n            console.log(' 👇 Current configuration');\n            console.log('');\n            console.log(prettyjson.render(config));\n            console.log('');\n        } catch(e) {\n            if (e.code === 'ENOENT') {\n                console.log(`${chalk.red(' 😱 Config file does not exist !')}`);\n                console.log(`${chalk.blue(`${chalk.bold('morphling config')}`)} will walk you through the config`);\n            } else {\n                console.error(e);\n            }\n        }\n\n        return;\n    }\n\n    prompt.start();\n    prompt.message = promptProps.message;\n\n\n    if (configFileAlreadyExists && !program.force) {\n        console.log(chalk.red(` 😱 Config file already exists! Use ${chalk.bold('morphling config')} with -f to override the existing config.`));\n        return;\n    }\n\n    prompt.get(promptProps.propertiesList.config, async (err, res) => {\n        if (err) {\n            console.log(err);\n            return;\n        }\n\n        if (res.makeDefault) {\n\n            const objectTosave = {\n                port: res.port,\n                default: res.makeDefault\n\n            }\n            try {\n                await utilFunctions.writeConfigFile(objectTosave);\n\n            } catch (e) {\n                console.error('ERROR WHILE SAVING CONFIG', e);\n                return;\n            }\n            console.log(chalk.green(` 🖖 Alright, Morphling will always run on ${res.port}!`));\n            console.log(chalk.white(` 😉 You can change that by doing ${chalk.bold('morphling config')} again`));\n            console.log(chalk.white(` 😉 Now start morphling by using ${chalk.bold('morphling start')}`));\n        } else {\n            console.log(chalk.green(` 🖖 Alright, Morphling will run on ${res.port}, but config will not be saved!`));\n        }\n    })\n})();\n"
  },
  {
    "path": "cli/morphling-describe.js",
    "content": "#! /usr/bin/env node\n\nimport _ from 'lodash';\nimport program from 'commander';\nimport chalk from 'chalk';\nimport fetch from 'node-fetch'\nimport Table from 'cli-table3';\n\nimport * as utilFunctions from './utils/util'\nimport * as util from 'util'\nimport prettyjson from 'prettyjson'\n\nconst describeAction = async (filename) => {\n    try {\n        let port;\n        const config = await utilFunctions.returnConfigIfExists();\n\n        if (config) {\n            port = config.port;\n        } else {\n            port = program.port || 8883;\n        }\n\n        const url = `http://localhost:${port}/morphling/describe?filename=${filename}`;\n\n        const request = await fetch(url)\n        if (request.status === 404) {\n            console.log(chalk.red(` 😱 File was not found. Use ${chalk.bold('morphling list -o')} to list all overrides`));\n            return;\n        }\n\n        const res = await request.json();\n\n        let table = new Table({\n            head: [\n                chalk.blue('Method'),\n                chalk.blue('Route'),\n                chalk.blue('Code'),\n                chalk.blue('Empty Response?'),\n                chalk.blue('Enabled'),\n            ]\n        });\n        const bodyIsEmpty = _.keys(res.body).length === 0;\n        console.log('');\n\n        console.log(`Description for override ${chalk.bold(filename)}`);\n\n        table.push([\n            res.method,\n            res.route,\n            res.httpCode,\n            bodyIsEmpty ? 'Yes' : 'No',\n            res.overrideStatus ? chalk.green('ON') : chalk.red('OFF'),\n        ]);\n\n        console.log(table.toString());\n\n        if (!bodyIsEmpty && !program.withBody && !program.withBodyRaw) {\n            console.log(chalk.blue(`Use ${chalk.bold('morphling describe -b <filename>')} to display the body`))\n        }\n\n        if (program.withBody || program.withBodyRaw) {\n            if (bodyIsEmpty) {\n                console.log(chalk.red(` 😱 Body is empty!`));\n            } else {\n                console.log('');\n                if (!program.withBodyRaw) {\n                    console.log(' 👇 Prettified body:');\n                    console.log('');\n                    console.log(prettyjson.render(res.body));\n                    console.log('');\n\n                } else {\n                    console.log(' 👇 Raw body:');\n                    console.log('');\n                    console.log(util.inspect(res.body, { depth: null, colors: true }))\n                    console.log('');\n                }\n            }\n        }\n    } catch (e) {\n        if (e.code === 'ECONNREFUSED') {\n            console.log(chalk.red(` 😱 Connection to Morphling was refused. Is Morphling currently running?`));\n        }\n        console.error(e);\n    }\n}\n\nprogram\n    .action(describeAction)\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .option('-b, --with-body', 'Pretty-print the body')\n    .option('-r, --with-body-raw', 'Print the body as-is')\n    .parse(process.argv);\n\nif (program.args.length === 0) {\n    console.log(chalk.red(` 😱 No filename provided. Use ${chalk.bold('morphling list -o')} to list all overrides`));\n}\n"
  },
  {
    "path": "cli/morphling-list.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport chalk from 'chalk';\nimport fetch from 'node-fetch';\nimport _ from 'lodash';\nimport Table from 'cli-table3';\n\nimport * as utilFunctions from './utils/util';\n\nprogram\n    .option('-v, --verbose', 'Print all the things')\n    .option('-o --overrides', 'List overrides')\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .parse(process.argv);\n\n(async () => {\n    try {\n        let port;\n        const config = await utilFunctions.returnConfigIfExists();\n\n        if (config) {\n            port = config.port;\n        } else {\n            port = program.port || 8883;\n        }\n\n        const listOverrides = !!program.overrides;\n        const url = `http://localhost:${port}/morphling/list?list-overrides=${listOverrides}`;\n\n        const request = await fetch(url)\n        const { files } = await request.json();\n\n        if (files.length === 0) {\n            console.log(chalk.yellow(` 🤔 No ${!listOverrides ? 'swaggers' : 'overrides'} saved on morphling!`));\n            return;\n        }\n\n        console.log(chalk.green(` 👇 ${!listOverrides ? 'Swaggers' : 'Overrides'} currently saved on Morphling:`));\n        let table = new Table({ head: [chalk.blue('File name')] });\n\n        _.each(files, (file) => {\n            table.push([file]);\n        })\n        console.log(table.toString());\n\n    } catch (e) {\n        if (e.code === 'ECONNREFUSED') {\n            console.log(chalk.red(` 😱 Connection to Morphling was refused. Is Morphling currently running?`));\n        }\n        console.log('Error in response', e);\n    }\n})()\n"
  },
  {
    "path": "cli/morphling-remove-all.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\n\nprogram\n    .option('-v, --verbose', 'Print all the things')\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .parse(process.argv);\n"
  },
  {
    "path": "cli/morphling-remove.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\n\nprogram\n    .option('-v, --verbose', 'Print all the things')\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .parse(process.argv);\n"
  },
  {
    "path": "cli/morphling-restart.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\nimport * as utilFunctions from './utils/util'\nimport { getInstalledPath } from 'get-installed-path'\n\nexport const restartAction = async () => {\n    const config = await utilFunctions.returnConfigIfExists();\n    const port = config.port || program.port || 8883;\n    const verbose = program.verbose || false;\n\n    if (!shell.which('docker')) {\n        console.log(chalk.red(` 🖕 Morphling requires docker to be able to run.`))\n        shell.exit(1);\n    }\n\n    if (!shell.which('docker-compose')) {\n        console.log(chalk.red(` 🖕 Morphling requires docker-compose to be able to run.`))\n        shell.exit(1);\n    }\n\n    const installedPath = await getInstalledPath('morphlingjs');\n\n    if (!verbose) {\n        if(!process.env.DEV){\n            shell.exec(`export NODE_PORT=${port} && cd ${installedPath} && docker-compose restart > /dev/null 2>&1`);\n        } else {\n            shell.exec(`export NODE_PORT=${port} && docker-compose restart > /dev/null 2>&1`);\n        }\n    } else {\n        if(!process.env.DEV){\n            shell.exec(`export NODE_PORT=${port} && cd ${installedPath} && docker-compose restart`);\n        } else {\n            shell.exec(`export NODE_PORT=${port} && docker-compose up restart`);\n        }\n    }\n\n    console.log(chalk.green(` 🖖 Morphling successfully restarted on ${port}`));\n};\n\nprogram\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .parse(process.argv);\n"
  },
  {
    "path": "cli/morphling-start.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\n\nimport * as utilFunctions from './utils/util';\nimport { getInstalledPath } from 'get-installed-path'\n\nprogram\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .parse(process.argv);\n\n(async () => {\n    let port;\n    const verbose = program.verbose || false;\n\n    const config = await utilFunctions.returnConfigIfExists();\n\n    if (config) {\n        console.log(chalk.white('Starting morphling using config file...'))\n        port = config.port;\n    } else {\n        port = program.port || 8883;\n    }\n\n    if (!shell.which('docker')) {\n        console.log(chalk.red(` 😱 Morphling requires docker to be able to run. 😱 `))\n        shell.exit(1);\n    }\n\n    if (!shell.which('docker-compose')) {\n        console.log(chalk.red(` 😱 Morphling requires docker-compose to be able to run. 😱 `))\n        shell.exit(1);\n    }\n\n    const installedPath = await getInstalledPath('morphlingjs');\n\n    console.log(`export NODE_PORT=${port} && cd ${installedPath} && docker-compose up -d > /dev/null 2>&1`);\n\n    if (!verbose) {\n        if(!process.env.DEV){\n            shell.exec(`export NODE_PORT=${port} && cd ${installedPath} && docker-compose up -d > /dev/null 2>&1`);\n        } else {\n            shell.exec(`export NODE_PORT=${port} && docker-compose up -d > /dev/null 2>&1`);\n        }\n    } else {\n        if(!process.env.DEV){\n            shell.exec(`export NODE_PORT=${port} && cd ${installedPath} && docker-compose up -d`);\n        } else {\n            shell.exec(`export NODE_PORT=${port} && docker-compose up -d`);\n        }\n    }\n\n    console.log(chalk.green(` 🖖 Morphling started on ${port}`));\n})()\n\n"
  },
  {
    "path": "cli/morphling-stop.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport shell from 'shelljs';\nimport chalk from 'chalk';\nimport * as utilFunctions from './utils/util'\nimport { getInstalledPath } from 'get-installed-path'\n\nprogram\n    .option('-v, --verbose', 'Print all the things')\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .parse(process.argv);\n\n(async () => {\n    let port;\n    const config = await utilFunctions.returnConfigIfExists();\n\n    if (config) {\n        console.log(chalk.white('Starting morphling using config file...'))\n        port = config.port;\n    } else {\n        port = program.port || 8883;\n    }\n\n    const verbose = program.verbose || false;\n\n    if (!shell.which('docker')) {\n        console.log(chalk.red(` 😱 Morphling requires docker to be able to run. 😱 `))\n        shell.exit(1);\n    }\n\n    if (!shell.which('docker-compose')) {\n        console.log(chalk.red(` 😱 Morphling requires docker-compose to be able to run. 😱 `))\n        shell.exit(1);\n    }\n    const installedPath = await getInstalledPath('morphlingjs');\n\n    if (!verbose) {\n        shell.exec(`cd ${installedPath} && NODE_PORT=${port} docker-compose stop > /dev/null 2>&1`);\n    } else {\n        shell.exec(`cd ${installedPath} && NODE_PORT=${port} docker-compose stop`)\n    }\n\n    console.log(chalk.green(`Morphling instance was killed 🖖`));\n})()\n"
  },
  {
    "path": "cli/morphling-toggle.js",
    "content": "#! /usr/bin/env node\n\nimport program from 'commander';\nimport chalk from 'chalk';\nimport fetch from 'node-fetch';\n\nimport * as utilFunctions from './utils/util'\n\nconst toggleAction = async (filename) => {\n    try {\n        let port;\n\n        const config = await utilFunctions.returnConfigIfExists();\n\n        if (config) {\n            port = config.port;\n        } else {\n            port = program.port || 8883;\n        }\n\n        let url = `http://localhost:${port}/morphling/toggle`;\n\n        if (program.enableAll) {\n            url += '?enable=all';\n        } else if (program.disableAll) {\n            url += '?enable=none';\n        } else {\n            url += `?filename=${filename}`;\n        }\n\n        const request = await fetch(url);\n        const res = await request.json();\n\n        const status = res.overrideStatus ? chalk.green('ON') : chalk.red('OFF');\n        if (program.enableAll || program.disableAll) {\n            console.log(` 👍 All overrides are now ${status}`);\n        } else {\n            console.log(` 👍 Override ${filename} is now ${status}`);\n        }\n\n    } catch (e) {\n        if (e.code === 'ECONNREFUSED') {\n            console.log(chalk.red(` 😱 Connection to Morphling was refused. Is Morphling currently running?`));\n            return;\n        }\n    }\n}\n\nprogram\n    .action(toggleAction)\n    .option('-p, --port <port>', 'A port number', parseInt)\n    .option('-v, --verbose', 'Print all the things')\n    .option('-e, --enable-all', 'Enable all overrides')\n    .option('-d, --disable-all', 'Disable all overrides')\n    .parse(process.argv);\n\nif (program.args.length === 0 && !program.enableAll && !program.disableAll) {\n    console.log(chalk.red(` 😱 No filename provided. Use ${chalk.bold('morphling list -o')} to list all overrides`));\n}\n\nif (program.enableAll || program.disableAll) {\n    (async () => {\n        try {\n            await toggleAction()\n        } catch (e) {\n        }\n    })()\n}\n"
  },
  {
    "path": "cli/utils/prompt-props.js",
    "content": "import chalk from 'chalk';\n\nexport default {\n    message: chalk.green('Morphling'),\n    propertiesList: {\n        config: [\n            {\n                name: 'port',\n                type: 'integer',\n                description: chalk.white('On which port do you want to run Morphling?'),\n                default: 8883,\n            },\n            {\n                name: 'makeDefault',\n                type: 'string',\n                required: true,\n                description: chalk.white('Do you want to save that as the default morphling port (y/n)?'),\n                pattern: /[yn]/,\n                default: 'y',\n                before:(value)=> value === 'y'\n            }\n        ],\n        start: [\n            {\n                name: 'run',\n                type: 'boolean',\n                required: true,\n                description: chalk.white(`You're done! Wanna run Morphling now?`)\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "cli/utils/util.js",
    "content": "import fs from 'fs';\nimport { getInstalledPath } from 'get-installed-path';\n\nconst env = process.env.DEV ? '' : '/bin';\n\nexport const writeConfigFile = async (objectToWrite) => {\n    const installedPath = await getInstalledPath('morphlingjs');\n    const path = `${installedPath}${env}/cli/morphling-config.json`;\n\n    return new Promise(function (resolve, reject) {\n        fs.writeFile(path, JSON.stringify(objectToWrite), function (err) {\n            if (err) {\n                console.log(err);\n                return reject(err);\n            }\n            return resolve();\n        });\n    });\n}\n\nexport const deleteConfigFile = async () => {\n    const installedPath = await getInstalledPath('morphlingjs');\n    const path = `${installedPath}${env}/cli/morphling-config.json`;\n\n    return new Promise(function (resolve, reject) {\n        fs.unlink(path, (err) => {\n            if (err) {\n                return reject(err);\n            }\n\n            return resolve();\n        });\n    });\n}\n\nexport const checkConfigFileExistence = async () => {\n    const installedPath = await getInstalledPath('morphlingjs');\n    const path = `${installedPath}${env}/cli/morphling-config.json`;\n\n    return new Promise((resolve) => {\n        fs.stat(path, (e) => {\n            if (e) return resolve(false)\n\n            return resolve(true)\n        })\n    });\n}\n\nexport const readFile = async (path) => {\n    return new Promise((resolve, reject) => {\n        fs.readFile(path, 'utf8', (err, data) => {\n            if (err) {\n                return reject(err);\n            }\n            return resolve(data);\n        })\n    });\n}\n\nexport const returnConfigIfExists = async () => {\n    const installedPath = await getInstalledPath('morphlingjs');\n    const path = `${installedPath}${env}/cli/morphling-config.json`;\n\n    return (await checkConfigFileExistence()) ? JSON.parse(await readFile(path)) : false;\n}\n\nexport const parseFile = async () => {\n    try {\n        const fileContent = await readFile(path);\n        const parsedFile = overrideFileParser(fileContent);\n    } catch (e) {\n        console.log(e);\n    }\n}\n\nexport const overrideFileParser = async (file) => {\n    let fileContent;\n\n    try {\n        fileContent = JSON.parse(file);\n    } catch (e) {\n        return Promise.reject('UNPARSED_JSON')\n    }\n\n    if (!fileContent.route) {\n        return Promise.reject(new Error('NO_ROUTE_DEFINED'));\n    }\n    if (!fileContent.method) {\n        return Promise.reject(new Error('NO_METHOD_DEFINED'));\n    }\n    if (!fileContent.exitHttpCode) {\n        return Promise.reject(new Error('NO_HTTP_CODE_DEFINED'));\n    }\n\n    return fileContent;\n}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"2\"\nservices:\n  web:\n    build: .\n    environment:\n      - NODE_ENV=production\n      - NODE_PORT=${NODE_PORT}\n    ports:\n      - \"${NODE_PORT}:${NODE_PORT}\"\n"
  },
  {
    "path": "makefile",
    "content": "start:\n\tnpm install || true\n\tmake build\n\tNODE_PORT=8883 make docker-build\n\tNODE_PORT=8883 make docker-up\n\ndev:\n\tDEV=true ./node_modules/.bin/babel-watch src/server.js\n\ndocker-start:\n\tmake docker-build\n\tmake docker-up\n\ndevstart-hard:\n\tmake clear-build || true\n\tmake clear-deps || true\n\tmake clear-docker || true\n\tmake start\n\nbuild:\n\tmake clear-build || true\n\tmkdir bin || true\n\t./node_modules/.bin/babel src -d bin/src\n\t./node_modules/.bin/babel cli -d bin/cli\n\tmkdir bin/src/data\n\nclear-build:\n\trm -rf bin || true\n\nclear-deps:\n\trm -rf ./node_modules || true\n\nclear-docker:\n\tdocker-compose down || true\n\ndocker-up:\n\tdocker-compose up\n\ndocker-build:\n\tNODE_PORT=${NODE_PORT} docker-compose build\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"morphlingjs\",\n  \"version\": \"0.1.99\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"postinstall\": \"NODE_PORT=1 docker-compose build\",\n    \"prepublish\": \"make build\"\n  },\n  \"bin\": {\n    \"morphling\": \"bin/cli/index.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/leboncoin/morphlingjs\"\n  },\n  \"files\": [\n    \"bin/\",\n    \"Dockerfile\",\n    \"docker-compose.yml\",\n    \"README.md\"\n  ],\n  \"keywords\": [\n    \"mock\",\n    \"swagger\",\n    \"docker\",\n    \"CLI\"\n  ],\n  \"author\": \"Valentin Roudge <v.roudge@gmail.com>\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"babel-cli\": \"~6.24.1\",\n    \"babel-core\": \"~6.25.0\",\n    \"babel-plugin-transform-object-rest-spread\": \"~6.23.0\",\n    \"babel-plugin-transform-runtime\": \"~6.23.0\",\n    \"babel-preset-env\": \"~1.6.0\",\n    \"babel-watch\": \"~2.0.7\"\n  },\n  \"dependencies\": {\n    \"babel-runtime\": \"^6.26.0\",\n    \"chalk\": \"^2.1.0\",\n    \"cli-table3\": \"^0.5.0\",\n    \"commander\": \"^2.11.0\",\n    \"faker\": \"^4.1.0\",\n    \"form-data\": \"^2.3.1\",\n    \"get-installed-path\": \"^4.0.8\",\n    \"koa\": \"~2.3.0\",\n    \"koa-body\": \"^2.3.0\",\n    \"koa-router\": \"~7.2.1\",\n    \"lodash\": \"~4.17.5\",\n    \"lokijs\": \"1.5.0\",\n    \"node-fetch\": \"^1.7.3\",\n    \"prettyjson\": \"^1.2.1\",\n    \"prompt\": \"^1.0.0\",\n    \"shelljs\": \"^0.7.8\",\n    \"swagger-parser\": \"3.4.2\",\n    \"yamljs\": \"~0.3.0\"\n  }\n}\n"
  },
  {
    "path": "src/api/apply.js",
    "content": "import koaRouter from 'koa-router'\nimport koaBody from 'koa-body'\nimport { writeFileStream, writeFile } from '../utils/utils'\n\nconst router = koaRouter();\n\nrouter.post('/apply', koaBody({\n    multipart: true, formidable: { uploadDir: `${process.cwd()}/src/data` }\n}), async (ctx) => {\n    try {\n        const { fields, files } = ctx.request.body;\n        const { fileName } = fields;\n        const { file } = files;\n\n        const writtenFile = await writeFileStream(fileName, file);\n\n        if (ctx.query.override && ctx.query.override === 'true') {\n            const overridenFile = JSON.parse(writtenFile);\n            //TODO give an option in the cli to immediately enable\n            overridenFile.overrideStatus = false;\n            await writeFile(`${fileName}`, JSON.stringify(overridenFile));\n        }\n\n        ctx.statusCode = 200;\n    } catch (e) {\n        console.error(e);\n        ctx.throw(500);\n    }\n});\n\nexport default router;\n\n\n\n"
  },
  {
    "path": "src/api/describe.js",
    "content": "import koaRouter from 'koa-router'\nimport { readFile } from '../utils/utils'\n\nconst router = koaRouter();\n\nrouter.get('/describe', async (ctx) => {\n    try {\n        if (!ctx.query['filename']) {\n            return ctx.throw(new Error('NOT_FOUND'))\n        }\n        const { filename } = ctx.query;\n\n        let fileDir = `${process.cwd()}/src/data/morphling-override-`\n\n        ctx.body = await readFile(`${fileDir}${filename}`);\n    } catch (e) {\n    }\n});\n\nexport default router;\n\n\n"
  },
  {
    "path": "src/api/index.js",
    "content": "import koaRouter from 'koa-router'\n\nimport morphlingApply from './apply'\nimport morphlingList from './list'\nimport morphlingRemove from './remove'\nimport morphlingRemoveAll from './remove-all'\nimport morphlingDescribe from './describe'\nimport morphlingToggle from './toggle'\n\nconst router = koaRouter({ prefix: '/morphling' })\n\n\n\nrouter\n    .use(morphlingApply.routes(), morphlingApply.allowedMethods())\n    .use(morphlingList.routes(), morphlingList.allowedMethods())\n    .use(morphlingRemove.routes(), morphlingRemove.allowedMethods())\n    .use(morphlingRemoveAll.routes(), morphlingRemoveAll.allowedMethods())\n    .use(morphlingDescribe.routes(), morphlingDescribe.allowedMethods())\n    .use(morphlingToggle.routes(), morphlingToggle.allowedMethods());\n\nexport default router;\n"
  },
  {
    "path": "src/api/list.js",
    "content": "import _ from 'lodash';\n\nimport koaRouter from 'koa-router'\nimport { checkFileExistence, readDir } from '../utils/utils'\n\nconst router = koaRouter();\n\nrouter.get('/list', async (ctx) => {\n    try {\n        if (ctx.query.filename) {\n            const fileExists = await checkFileExistence(`${process.cwd()}/src/data/${ctx.query.filename}`);\n            ctx.body = fileExists ? { fileExists: true } : { fileExists: false };\n        } else if (ctx.query['list-overrides'] === 'true') {\n            const files = await readDir(`${process.cwd()}/src/data/`, (files) => {\n                    return _.filter(files, file => file.includes('morphling-override'))\n                        .map(file => file.replace('morphling-override-', ''))\n                }\n            );\n            ctx.body = { files };\n        } else {\n            const files = await readDir(`${process.cwd()}/src/data/`, (files) => {\n                return _.filter(files,\n                    file => !file.includes('morphling-override') && !['db.json', '.gitkeep'].includes(file)\n                );\n            });\n            ctx.body = { files };\n        }\n    } catch (e) {\n        console.log('Error in get list', e);\n    }\n});\n\nexport default router;\n"
  },
  {
    "path": "src/api/middleware/error.js",
    "content": "export default async (ctx, next) => {\n    try {\n        await next();\n    } catch (err) {\n        // will only respond with JSON\n        ctx.status = err.code || 500;\n\n        ctx.body = {\n            message: err.message\n        };\n    }\n}\n"
  },
  {
    "path": "src/api/middleware/loki.js",
    "content": "import { getDB } from '../../db/index'\n\nexport default async(ctx, next)=> {\n    try {\n        ctx.db = getDB()\n        await next();\n    } catch(e) {\n        console.log('Error in getDB', e);\n\n    }\n\n}\n"
  },
  {
    "path": "src/api/remove-all.js",
    "content": "import koaRouter from 'koa-router'\n\nconst router = koaRouter();\n\nrouter.delete('/remove-all', async (ctx) => {\n    ctx.body = 'OK'\n});\n\nexport default router;\n\n\n\n"
  },
  {
    "path": "src/api/remove.js",
    "content": "import koaRouter from 'koa-router'\n\nconst router = koaRouter();\n\nrouter.delete('/remove', async (ctx) => {\n    ctx.body = 'OK'\n});\n\nexport default router;\n\n\n\n"
  },
  {
    "path": "src/api/toggle.js",
    "content": "import _ from 'lodash';\nimport koaRouter from 'koa-router'\nimport { readDir, readFile, writeFile } from '../utils/utils'\n\nconst router = koaRouter();\n\nrouter.get('/toggle', async (ctx) => {\n    try {\n        if (ctx.query['filename']) {\n            const { filename } = ctx.query;\n            let file = `${process.cwd()}/src/data/morphling-override-${filename}`;\n\n            const fileToToggle = JSON.parse(await readFile(file));\n            fileToToggle.overrideStatus = !fileToToggle.overrideStatus;\n\n            await writeFile(`morphling-override-${filename}`, fileToToggle);\n\n            ctx.body = { overrideStatus: fileToToggle.overrideStatus };\n        } else if (ctx.query['enable']) {\n            const trueIfEnableAll = ctx.query['enable'] === 'all';\n\n            const files = await readDir(`${process.cwd()}/src/data/`, (files) => {\n                return _.filter(files,\n                    file => file.includes('morphling-override')\n                );\n            });\n\n            await Promise.all(\n                _.map(files, async (file) => {\n                    const fileToToggle = JSON.parse(await readFile(`${process.cwd()}/src/data/${file}`));\n                    fileToToggle.overrideStatus = trueIfEnableAll;\n                    return await writeFile(file, fileToToggle);\n                })\n            );\n\n            ctx.body = { overrideStatus: trueIfEnableAll };\n        } else {\n            ctx.throw(404);\n        }\n    } catch (e) {\n        ctx.throw(e);\n    }\n});\n\nexport default router;\n"
  },
  {
    "path": "src/data/.gitkeep",
    "content": ""
  },
  {
    "path": "src/db/collection.js",
    "content": "export const findOne = (collection, value, eq) => {\n    const query = {};\n    query[value] = { '$eq': eq }\n    return collection.find(query)[0]\n}\n\n/**\n * Creates a collection in database\n * @param db database instance\n * @param modelName name of model\n * @param opts other options (See lokiJS docs)\n * @returns {Collection|*}\n */\nexport const createCollection = (db, modelName, opts) => {\n    const entries = db.getCollection(modelName);\n    if (entries) {\n        throw new Error('Collection already exists');\n    }\n\n    return db.addCollection(modelName, opts)\n}\n\n/**\n * Get collection from database\n * @param db database instance\n * @param modelName\n * @returns {Collection}\n */\nexport const findCollection = (db, modelName) => {\n    const entries = db.getCollection(modelName);\n\n    if (!entries) {\n        throw new Error('MODEL_DOES_NOT_EXIST')\n    }\n\n    return entries;\n}\n\n"
  },
  {
    "path": "src/db/fixtures.js",
    "content": "import _ from 'lodash'\nimport faker from 'faker'\n\nimport { getOneModel, createModel } from './index'\nimport { createCollection, findCollection, insertOne } from './collection'\n\n/**\n * Create all fixtures from a given list of parsed swagger definitions\n * @param db A LokiJS DB instance\n * @param definitions A list of parsed swagger definitions\n */\nexport const createFixturesFromDefinitions = (db, definitions) => {\n    try {\n        const createdModels = _.map(definitions, definition => {\n            // store all models in datase\n            return createModel(db, definition)\n        })\n\n        // after all models are stored in database, we can start to generate fixtures for all\n        // since we have all references stored\n\n        return _.map(createdModels, model => {\n            const fixtures = _.map(_.range(5), iter => generateOneFixture(db, model));\n\n            const { modelName } = model;\n            let currentModelCollection;\n\n            try {\n                currentModelCollection = findCollection(db, modelName)\n            } catch (e) {\n                if (e.message === 'MODEL_DOES_NOT_EXIST') {\n                    currentModelCollection = createCollection(db, modelName)\n                }\n            }\n\n            return currentModelCollection.insert(fixtures)\n        })\n    } catch (e) {\n        console.error(e);\n    }\n}\n\n/**\n * Create one fixture from a given model\n * @param db Loki DB instance\n * @param model model content\n * @returns {{}}\n */\nconst generateOneFixture = (db, model) => {\n    const { type, modelName } = model;\n\n    switch (type) {\n        case 'object':\n            const { properties } = model;\n            //handle dictionnaries (additonalProperties instead of properties)\n            if (!properties) {\n                const fixture = {}\n\n                //TODO handle maps or object with additionalProperties here\n\n                return fixture;\n            } else {\n                const fixture = {};\n                fixture[modelName] = _.reduce(properties, (acc, prop, propName) => generate(db, acc, prop, propName, modelName), {})\n                return fixture;\n            }\n            break;\n        case 'array':\n            const fixture = {}\n            fixture[modelName] = _.map(_.range(5), (elem) => generate(db, {}, model.items, modelName)[modelName]);\n            return fixture\n            break;\n        default:\n            throw new Error('Non handled generable type', { model, type })\n            break;\n    }\n}\n\n/**\n * Get reference model name from reference string\n * @param {string} item\n */\nconst cleanPropertyReference = (item) => {\n    return item.replace(`#/definitions/`, '')\n};\n\n/**\n * Generate one fixture\n * @param db\n * @param accumulator\n * @param property\n * @param propertyName\n * @param parentModelName\n * @returns {*}\n */\nconst generate = (db, accumulator, property, propertyName, parentModelName) => {\n    accumulator[propertyName] = accumulator[propertyName] ? accumulator[propertyName] : {}\n    const mapping = {\n        'integer': faker.random.number,\n        'number': faker.random.number,\n        'string': faker.random.word,\n        'boolean': faker.random.boolean,\n    }\n\n    //TODO make sure that we handle enum fields with non-random values\n\n    const customMapping = {\n        'array': (property, propertyName) => {\n            const { items } = property;\n\n            //handle case where array is an array of references to another object\n            //we get the model from database and then generate from it;\n            if (items['$ref']) {\n                //TODO chain 5 times\n                const modelName = cleanPropertyReference(items['$ref']);\n                const model = getOneModel(db, modelName);\n\n                // if the current parsed model is the same than its container it is a circular reference\n                if (modelName === parentModelName) {\n                    return 'MORPHLING_CIRCULAR_REFERENCE'\n                }\n\n                return _.map(_.range(5), elem => generateOneFixture(db, model));\n            }\n            //else we just generate an array of fixtures\n            return _.map(_.range(5), elem => generate(db, {}, items, propertyName)[propertyName]);\n        },\n        'lat': faker.address.latitude,\n        'lng': faker.address.longitude,\n        'zipcode': faker.address.zipCode,\n        'address': faker.address.streetAddress,\n    }\n\n    const fakerGenerators = {\n        ...faker.random,\n        ...faker.address,\n        ...faker.company,\n        ...faker.finance,\n        ...faker.internet,\n        ...faker.name,\n        ...faker.random,\n    };\n\n    //it is a reference to another object, keep reference for further generation down the line\n    if (property['$ref']) {\n        const modelName = cleanPropertyReference(property['$ref']);\n        const model = getOneModel(db, modelName);\n\n        //console.log(generateOneFixture(db, model));\n        accumulator[propertyName] = generateOneFixture(db, model);\n    }\n    // else a manual override exists for a given property\n    else if (customMapping[propertyName]) {\n        accumulator[propertyName] = customMapping[propertyName]();\n    }\n    //else a faker generator with the same name exists (using it will give more realistic data)\n    else if (fakerGenerators[propertyName]) {\n        accumulator[propertyName] = fakerGenerators[propertyName]();\n    }\n    //else use property type to generate the data\n    else if (mapping[property.type]) {\n        accumulator[propertyName] = mapping[property.type]();\n\n    }\n    //else object is an array, we need to recure to generate\n    else if (property.type === 'array') {\n        accumulator[propertyName] = customMapping['array'](property, propertyName);\n    }\n    //keep property as is if no field where generation could be done was found\n    else {\n        //TODO maybe example ?\n        accumulator[propertyName] = property;\n    }\n\n    return accumulator;\n}\n"
  },
  {
    "path": "src/db/index.js",
    "content": "import Loki from 'lokijs'\nimport _ from 'lodash'\n\nimport * as collectionUtils from './collection'\n\nexport const MODEL_TABLE_NAME = 'models'\nexport const MODEL_NAME_FIELD = 'modelName'\nexport const FIXTURE_FILE_NAME = 'db.json'\n\nlet db;\n\n/**\n * Initalizes the LokiJS database instance\n * @returns {Promise.<*>}\n */\nexport const getDB = () => {\n    if (!db) {\n        db = new Loki(`${process.cwd()}/src/data/${FIXTURE_FILE_NAME}`, {\n            autosave: true,\n            autosaveInterval: 100\n        });\n        db.save();\n    }\n    return db;\n}\n\n/**\n * Creates a model in database model collection if it does not exist\n * Also creates the model table if that table does not exist\n * @param db database instance\n * @param definition parsed swagger model definition\n * @returns {object} inserted model instance\n */\nexport const createModel = (db, definition) => {\n    let modelCollection\n\n    try {\n        modelCollection = collectionUtils.findCollection(db, MODEL_TABLE_NAME)\n    } catch (e) {\n        if (e.message === 'MODEL_DOES_NOT_EXIST') {\n            modelCollection = collectionUtils.createCollection(db, MODEL_TABLE_NAME, { unique: [MODEL_NAME_FIELD] })\n        } else {\n            console.error('Something went wrong while creating the models table.')\n        }\n    }\n\n    let foundModel = collectionUtils.findOne(modelCollection, MODEL_NAME_FIELD, definition.modelName)\n\n    if (!foundModel) {\n        modelCollection.insert(definition);\n        foundModel = collectionUtils.findOne(modelCollection, MODEL_NAME_FIELD, definition.modelName)\n    }\n    return foundModel;\n}\n\n/**\n * Ensure there are no duplicates in model names\n * @param models array of model names\n * @returns {Array}\n */\nexport const validateModelNames = (models) => {\n    if (_.uniq(models).length !== _.keys(models).length) {\n        console.warn('BEWARE, MODELS ARE DUPLICATED BETWEEN SWAGGERS. REMOVING DUPLICATES.');\n    }\n    return _.uniq(models);\n}\n\n/**\n * Get one model from DB\n * @param db\n * @param modelName\n */\nexport const getOneModel = (db, modelName) => {\n    const modelTable = collectionUtils.findCollection(db, MODEL_TABLE_NAME)\n    return collectionUtils.findOne(modelTable, MODEL_NAME_FIELD, modelName)\n}\n"
  },
  {
    "path": "src/server.js",
    "content": "import Koa from 'koa'\nimport _ from 'lodash';\n\nimport apiRouter from './api'\nimport errorMiddleware from './api/middleware/error'\nimport lokiMiddleware from './api/middleware/loki'\nimport Morphling from './utils/morphling';\nimport overrideRouter from './utils/override-router';\n\nconst serverPort = process.env.NODE_PORT || 8883;\n\n(async () => {\n    const app = new Koa()\n\n    app.use(errorMiddleware)\n    app.use(lokiMiddleware)\n\n    app.use(apiRouter.routes(), apiRouter.allowedMethods())\n\n    try {\n        const overrideRouters = await overrideRouter();\n        _.map(overrideRouters, (router)=>{\n            app.use(router.routes(), router.allowedMethods());\n        });\n\n        const morphlingInstance = await Morphling();\n        _.map(morphlingInstance, (router)=>{\n            app.use(router.routes(), router.allowedMethods());\n        });\n    } catch(e) {\n        console.error(e);\n    }\n\n    app.on('error', (err, ctx) => {\n        console.error('server error', err, ctx)\n    })\n\n    app.listen(serverPort)\n    console.log(`Morphling ready on port ${serverPort}`)\n})()\n\n"
  },
  {
    "path": "src/utils/createRouterFromSwagger.js",
    "content": "import koaRouter from 'koa-router'\nimport _ from 'lodash'\nimport { findCollection } from '../db/collection'\n\n/**\n * Put all the other stuff down there together\n *\n * @param db\n * @param basePath\n * @param paths\n * @returns {*}\n */\nexport default (db, { basePath, paths }) => {\n    //remove last slash of route if there is one\n    const cleanedBasePath = basePath[basePath.length - 1] !== '/' ? basePath : basePath.slice(0, -1);\n\n    //create router container\n    let router = koaRouter({\n        prefix: cleanedBasePath || ''\n    })\n\n    _.each(paths, (pathOptions, route) => {\n        //get methods without parameters\n        const routerMethods = _.compact(_.map(_.keys(pathOptions), key => key !== 'parameters' ? key : null))\n\n        // mount methods to router if it has any\n        if (routerMethods.length) {\n            router = addMethodsToRouter(db, router, { route, routerMethods, pathOptions })\n        }\n    })\n\n    return router\n}\n\n/**\n * Add methods + bind callbacks to the router + bind parameters\n * @param db\n * @param router\n * @param route\n * @param routerMethods\n * @param pathOptions\n * @returns {*}\n */\nconst addMethodsToRouter = (db, router, { route, routerMethods, pathOptions }) => {\n    const formattedRouteParameters = createAliasesForParameters(route)\n    const formattedRoute = _.reduce(formattedRouteParameters, (acc, elem) => {\n        return acc.replace(elem.name, `:${elem.alias}`).replace(/[{}]/g, '')\n    }, route)\n\n    let routerRef = router\n\n    _.each(routerMethods, (method) => {\n        //create route\n        const { responses, parameters } = pathOptions[method]\n        const getter = responseGetter(db, responses);\n\n        router[method](formattedRoute, getter);\n\n        //add parameters link to route if there are any to add\n        if (formattedRouteParameters) {\n            const swaggerOptions = _.filter(pathOptions[method].parameters, { in: 'path' })\n            router = addUrlParamToRouter(router, formattedRouteParameters, swaggerOptions)\n        }\n\n        //add handlers to route\n\n    })\n\n    return routerRef\n}\n\n/**\n * Create route callback function, defining randomly how many fixtures we will return\n * and which one exactly from the local db\n *\n * @param db\n * @param responses\n * @returns {function(*)}\n */\nconst responseGetter = (db, responses) => {\n    let body;\n    const defaultSuccess = responses[200] || responses[201] || responses[204];\n\n    return ((ctx) => {\n        if (defaultSuccess && defaultSuccess.schema) {\n            const objectRef = getReferencesAndCount(defaultSuccess.schema);\n            const collection = findCollection(db, objectRef.object);\n\n            body = _.take(\n                _.shuffle(\n                    _.map(\n                        collection.find(),\n                        `${objectRef.object}`\n                    )\n                )\n                , objectRef.fixtureCount);\n            if (defaultSuccess.schema.type === 'array') {\n                ctx.body = body;\n            }\n            else if (defaultSuccess.schema['$ref']) {\n                ctx.body = body[0];\n            }\n            return;\n        } else if (defaultSuccess) {\n            const possibleCodes = [200, 201, 204];\n            for (const code of possibleCodes){\n                if(responses[code]){\n                    ctx.status = code;\n                    break;\n                }\n            }\n            return;\n        }\n        ctx.throw('Unsupported format! Can you create an issue in Github about that?')\n\n    });\n\n    //2 handle parameters in body (in: body)\n    //  -> is body required ?\n    //  -> validate against schema\n    //3 define if should give success or error through override\n    //  -> if so, do return error from schema\n    //4 get smallest response code and accept it as default one\n}\n\n/**\n * Get the fixture that should be return + a random number\n *\n * @param reference\n * @returns {{fixtureCount: number, object}}\n */\nconst getReferencesAndCount = (reference) => {\n    if (reference['$ref']) {\n        const object = reference['$ref'].replace('#/definitions/', '');\n        return { fixtureCount: 1, object }\n    } else if (reference.items) {\n        const object = reference.items['$ref'].replace('#/definitions/', '');\n        return { fixtureCount: (Math.round(Math.random() * 5)) || 1, object }\n    } else {\n        return { fixtureCount: 0, object: reference.description }\n    }\n}\n\n/**\n * Format parameters\n *\n * @param route\n * @returns {*}\n */\nconst createAliasesForParameters = (route) => {\n    const urlParams = route.match(/{(.*)}/g)\n\n    return _.reduce(urlParams, (acc, param) => {\n        const rawParameter = param.replace(/[{}]/g, '')\n\n        if (!acc[rawParameter]) {\n            acc[rawParameter] = {\n                name: rawParameter,\n                alias: param.replace(/\\-/g, '')\n            }\n        }\n        return acc\n    }, {})\n}\n\n/**\n * Binds parameters to the router so they can be correctly recognized on query\n * @param router\n * @param formattedRouteParameters\n * @param swaggerOptions\n * @returns {*}\n */\nconst addUrlParamToRouter = (router, formattedRouteParameters, swaggerOptions) => {\n    const routerRef = router\n\n    _.each(formattedRouteParameters, ({ name }) => {\n        //get details of parameter (required, type...)\n        const swaggerOptionsForParameter = _.find(swaggerOptions, { name });\n\n        routerRef.param(name, async (value, ctx, next) => {\n            //check that parameter exists\n            if (swaggerOptionsForParameter.required) {\n                if (!value) {\n                    return ctx.throw(`Parameter ${name} is missing`)\n                }\n            }\n            return next()\n        })\n    })\n\n    return routerRef\n}\n"
  },
  {
    "path": "src/utils/morphling.js",
    "content": "import _ from 'lodash'\nimport swaggerParser from 'swagger-parser'\nimport YAML from 'yamljs'\n\nimport { getDB, validateModelNames } from '../db'\nimport { createFixturesFromDefinitions } from '../db/fixtures'\nimport createRouterFromSwagger from './createRouterFromSwagger'\nimport { readDir, readFile } from './utils'\n\nconst SOURCE_FILES_LOCATION = `${process.cwd()}/src/data`;\n\n/**\n * Read swagger files in data directory and pass them to the fixture + routes creator\n * @returns {Promise.<void>}\n */\nexport default async () => {\n    try {\n        const localSources = await readDir(SOURCE_FILES_LOCATION, (files) => {\n            return _.filter(files,\n                file => !file.includes('morphling-override') && !['db.json', '.gitkeep'].includes(file)\n            );\n        });\n\n        const swaggers = await Promise.all(\n            _.map(localSources, async (fileName) => {\n                const fileContent = await readFile(`${SOURCE_FILES_LOCATION}/${fileName}`);\n                if (fileName.includes('.json')) {\n                    return JSON.parse(fileContent);\n                } else if (fileName.includes('.yml') || fileName.includes('.yaml')) {\n                    return YAML.parse(fileContent)\n                }\n            })\n        )\n        return await Morphling(swaggers);\n    } catch (e) {\n        console.log('Error while fetching local swaggers', e);\n\n    }\n}\n\n/**\n * Takes an array of swaggers in, returns a router with bound fixtures out\n * @param swaggers\n * @returns {Promise.<void>}\n * @constructor\n */\nconst Morphling = async (swaggers) => {\n    const db = getDB()\n\n    try {\n        const parsedSwaggers = await Promise.all(\n            _.map(swaggers, (elem) => {\n                return parseSwagger(elem)\n            }));\n\n\n        const allModelDefinitions = _.merge(..._.map(parsedSwaggers, (swagger) => {\n            return _.map(swagger.definitions, (def, defName) => ({ modelName: defName, ...def }))\n        }));\n\n\n        const cleanedModelDefinitions = validateModelNames(allModelDefinitions);\n\n        const fixtures = createFixturesFromDefinitions(db, cleanedModelDefinitions);\n\n        return _.map(swaggers, (source) => {\n            const { basePath, paths, } = source;\n            return createRouterFromSwagger(db, { basePath, paths })\n        })\n    } catch (e) {\n        console.error(e)\n    }\n}\n\n/**\n * Swagger parser, dont not parse circular references because JSON cannot handle that\n * @param source\n * @returns {Promise.<*>}\n */\nconst parseSwagger = async (source) => {\n    return await swaggerParser.bundle(source, { $refs: { circular: 'ignore' } });\n}\n\n"
  },
  {
    "path": "src/utils/override-router.js",
    "content": "import koaRouter from 'koa-router'\nimport _ from 'lodash'\nimport { readDir, readFile } from './utils'\n\nconst SOURCE_FILES_LOCATION = `${process.cwd()}/src/data`;\n\n/**\n * Create a router for a given override file\n *\n * The route is created during the boot, but we check again\n * the override status during the request, to have working toggle\n *\n * @param name\n * @param overrideContent\n * @returns {*}\n */\nconst createRouter = (name, overrideContent) => {\n    const router = koaRouter();\n\n    const { httpCode, body, route, method } = JSON.parse(overrideContent);\n    const lowerCasedMethod = method.toLowerCase();\n\n    router[lowerCasedMethod](route, async (ctx) => {\n        console.log('pass');\n\n        const { overrideStatus } = JSON.parse(await readFile(`${SOURCE_FILES_LOCATION}/${name}`));\n\n        if(overrideStatus){\n            ctx.body = _.keys(body).length > 0 ? body : null;\n            ctx.status = httpCode;\n        }\n    });\n\n    return router;\n}\n\n/**\n * Read override files in data directory and return an array of koa routers ready for use\n * @returns {Promise.<*[]>}\n */\nexport default async () => {\n    try {\n        const overrideFiles = await readDir(SOURCE_FILES_LOCATION, (files) => {\n            return _.filter(files, file => file.includes('morphling-override'));\n        });\n\n        return await Promise.all(\n            _.map(overrideFiles, async (fileName) => {\n                const fileContent = await readFile(`${SOURCE_FILES_LOCATION}/${fileName}`);\n                return createRouter(fileName, fileContent)\n            })\n        )\n    } catch (e) {\n        console.error(e);\n    }\n}\n"
  },
  {
    "path": "src/utils/utils.js",
    "content": "import fs from 'fs'\nimport _ from 'lodash';\n\n/**\n * Read file content in path\n * @param path\n * @returns {Promise}\n */\nexport const readFile = async (path) => {\n    return new Promise((resolve, reject) => {\n        fs.readFile(path, 'utf8', (err, data) => {\n            if (err) {\n                return reject(err);\n            }\n            return resolve(data);\n        })\n    });\n}\n/**\n * Write file in path from other file\n * @returns {Promise}\n * @param fileName\n * @param file\n */\nexport const writeFileStream = async (fileName, file) => {\n    try {\n        const readStream = fs.createReadStream(file.path);\n        const writeStream = fs.createWriteStream(`${process.cwd()}/src/data/${fileName}`);\n        readStream.pipe(writeStream);\n        fs.unlink(file.path);\n\n        return await readFile(`${process.cwd()}/src/data/${fileName}`);\n    } catch (e) {\n        console.log('Error while writing file!', e);\n\n    }\n}\n\n/**\n * Write file in path\n * @param filename file name\n * @param content JSON object\n * @returns {Promise}\n */\nexport const writeFile = async (filename, content) => {\n    const contentToWrite = _.isObject(content) ? JSON.stringify(content) : content;\n    return new Promise(function (resolve, reject) {\n        fs.writeFile(`${process.cwd()}/src/data/${filename}`, contentToWrite, 'utf8', (err, res) => {\n            if (err) {\n                return reject(err);\n            }\n            return resolve(res);\n        });\n    });\n}\n\n/**\n * check if file exists\n * @returns {Promise}\n * @param path\n */\nexport const checkFileExistence = async (path) => {\n    return new Promise((resolve) => {\n        fs.stat(path, (e) => {\n            if (e) return resolve(false)\n            return resolve(true)\n        })\n    });\n}\n\n/**\n * Read directory file names\n * @param path path to directory\n * @param exclude files to filter out of exit\n * @returns {Promise} resolved filenames\n */\nexport const readDir = async (path, exclude) => {\n    return new Promise((resolve, reject) => {\n        fs.readdir(path, (err, files) => {\n            if (err) {\n                return reject(err);\n            }\n\n            if (exclude) {\n                if (typeof exclude === 'function') {\n                    return resolve(exclude(files))\n                }\n\n                return resolve(\n                    _.filter(files, file => !exclude.includes(file))\n                );\n\n            }\n\n            return resolve(files);\n        })\n    });\n}\n"
  },
  {
    "path": "swagger-examples/example.yaml",
    "content": "---\nswagger: \"2.0\"\ninfo:\n  version: \"1.0.0\"\n  title: \"Swagger Petstore\"\n  termsOfService: \"http://swagger.io/terms/\"\n  contact:\n    email: \"apiteam@swagger.io\"\n  license:\n    name: \"Apache 2.0\"\n    url: \"http://www.apache.org/licenses/LICENSE-2.0.html\"\nhost: \"petstore.swagger.io\"\nbasePath: \"/v2/\"\ntags:\n- name: \"pet\"\n  description: \"Everything about your Pets\"\n  externalDocs:\n    description: \"Find out more\"\n    url: \"http://swagger.io\"\n- name: \"store\"\n  description: \"Access to Petstore orders\"\n- name: \"user\"\n  description: \"Operations about user\"\n  externalDocs:\n    description: \"Find out more about our store\"\n    url: \"http://swagger.io\"\nschemes:\n- \"http\"\npaths:\n  /pet:\n    post:\n      tags:\n      - \"pet\"\n      summary: \"Add a new pet to the store\"\n      description: \"\"\n      operationId: \"addPet\"\n      consumes:\n      - \"application/json\"\n      - \"application/xml\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"Pet object that needs to be added to the store\"\n        required: true\n        schema:\n          $ref: \"#/definitions/Pet\"\n      responses:\n        405:\n          description: \"Invalid input\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n    put:\n      tags:\n      - \"pet\"\n      summary: \"Update an existing pet\"\n      description: \"\"\n      operationId: \"updatePet\"\n      consumes:\n      - \"application/json\"\n      - \"application/xml\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"Pet object that needs to be added to the store\"\n        required: true\n        schema:\n          $ref: \"#/definitions/Pet\"\n      responses:\n        400:\n          description: \"Invalid ID supplied\"\n        404:\n          description: \"Pet not found\"\n        405:\n          description: \"Validation exception\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n  /pet/findByStatus:\n    get:\n      tags:\n      - \"pet\"\n      summary: \"Finds Pets by status\"\n      description: \"Multiple status values can be provided with comma separated strings\"\n      operationId: \"findPetsByStatus\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"status\"\n        in: \"query\"\n        description: \"Status values that need to be considered for filter\"\n        required: true\n        type: \"array\"\n        items:\n          type: \"string\"\n          enum:\n          - \"available\"\n          - \"pending\"\n          - \"sold\"\n          default: \"available\"\n        collectionFormat: \"multi\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            type: \"array\"\n            items:\n              $ref: \"#/definitions/Pet\"\n        400:\n          description: \"Invalid status value\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n  /pet/findByTags:\n    get:\n      tags:\n      - \"pet\"\n      summary: \"Finds Pets by tags\"\n      operationId: \"findPetsByTags\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"tags\"\n        in: \"query\"\n        description: \"Tags to filter by\"\n        required: true\n        type: \"array\"\n        items:\n          type: \"string\"\n        collectionFormat: \"multi\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            type: \"array\"\n            items:\n              $ref: \"#/definitions/Pet\"\n        400:\n          description: \"Invalid tag value\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n      deprecated: true\n  /pet/{petId}:\n    get:\n      tags:\n      - \"pet\"\n      summary: \"Find pet by ID\"\n      description: \"Returns a single pet\"\n      operationId: \"getPetById\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"petId\"\n        in: \"path\"\n        description: \"ID of pet to return\"\n        required: true\n        type: \"integer\"\n        format: \"int64\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            $ref: \"#/definitions/Pet\"\n        400:\n          description: \"Invalid ID supplied\"\n        404:\n          description: \"Pet not found\"\n      security:\n      - api_key: []\n    post:\n      tags:\n      - \"pet\"\n      summary: \"Updates a pet in the store with form data\"\n      description: \"\"\n      operationId: \"updatePetWithForm\"\n      consumes:\n      - \"application/x-www-form-urlencoded\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"petId\"\n        in: \"path\"\n        description: \"ID of pet that needs to be updated\"\n        required: true\n        type: \"integer\"\n        format: \"int64\"\n      - name: \"name\"\n        in: \"formData\"\n        description: \"Updated name of the pet\"\n        required: false\n        type: \"string\"\n      - name: \"status\"\n        in: \"formData\"\n        description: \"Updated status of the pet\"\n        required: false\n        type: \"string\"\n      responses:\n        405:\n          description: \"Invalid input\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n    delete:\n      tags:\n      - \"pet\"\n      summary: \"Deletes a pet\"\n      description: \"\"\n      operationId: \"deletePet\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"api_key\"\n        in: \"header\"\n        required: false\n        type: \"string\"\n      - name: \"petId\"\n        in: \"path\"\n        description: \"Pet id to delete\"\n        required: true\n        type: \"integer\"\n        format: \"int64\"\n      responses:\n        400:\n          description: \"Invalid ID supplied\"\n        404:\n          description: \"Pet not found\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n  /pet/{petId}/uploadImage:\n    post:\n      tags:\n      - \"pet\"\n      summary: \"uploads an image\"\n      description: \"\"\n      operationId: \"uploadFile\"\n      consumes:\n      - \"multipart/form-data\"\n      produces:\n      - \"application/json\"\n      parameters:\n      - name: \"petId\"\n        in: \"path\"\n        description: \"ID of pet to update\"\n        required: true\n        type: \"integer\"\n        format: \"int64\"\n      - name: \"additionalMetadata\"\n        in: \"formData\"\n        description: \"Additional data to pass to server\"\n        required: false\n        type: \"string\"\n      - name: \"file\"\n        in: \"formData\"\n        description: \"file to upload\"\n        required: false\n        type: \"file\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            $ref: \"#/definitions/ApiResponse\"\n      security:\n      - petstore_auth:\n        - \"write:pets\"\n        - \"read:pets\"\n  /store/inventory:\n    get:\n      tags:\n      - \"store\"\n      summary: \"Returns pet inventories by status\"\n      description: \"Returns a map of status codes to quantities\"\n      operationId: \"getInventory\"\n      produces:\n      - \"application/json\"\n      parameters: []\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            type: \"object\"\n            additionalProperties:\n              type: \"integer\"\n              format: \"int32\"\n      security:\n      - api_key: []\n  /store/order:\n    post:\n      tags:\n      - \"store\"\n      summary: \"Place an order for a pet\"\n      description: \"\"\n      operationId: \"placeOrder\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"order placed for purchasing the pet\"\n        required: true\n        schema:\n          $ref: \"#/definitions/Order\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            $ref: \"#/definitions/Order\"\n        400:\n          description: \"Invalid Order\"\n  /store/order/{orderId}:\n    get:\n      tags:\n      - \"store\"\n      summary: \"Find purchase order by ID\"\n      operationId: \"getOrderById\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"orderId\"\n        in: \"path\"\n        description: \"ID of pet that needs to be fetched\"\n        required: true\n        type: \"integer\"\n        maximum: 10.0\n        minimum: 1.0\n        format: \"int64\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            $ref: \"#/definitions/Order\"\n        400:\n          description: \"Invalid ID supplied\"\n        404:\n          description: \"Order not found\"\n    delete:\n      tags:\n      - \"store\"\n      summary: \"Delete purchase order by ID\"\n      operationId: \"deleteOrder\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"orderId\"\n        in: \"path\"\n        description: \"ID of the order that needs to be deleted\"\n        required: true\n        type: \"integer\"\n        minimum: 1.0\n        format: \"int64\"\n      responses:\n        400:\n          description: \"Invalid ID supplied\"\n        404:\n          description: \"Order not found\"\n  /user:\n    post:\n      tags:\n      - \"user\"\n      summary: \"Create user\"\n      description: \"This can only be done by the logged in user.\"\n      operationId: \"createUser\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"Created user object\"\n        required: true\n        schema:\n          $ref: \"#/definitions/User\"\n      responses:\n        default:\n          description: \"successful operation\"\n  /user/createWithArray:\n    post:\n      tags:\n      - \"user\"\n      summary: \"Creates list of users with given input array\"\n      description: \"\"\n      operationId: \"createUsersWithArrayInput\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"List of user object\"\n        required: true\n        schema:\n          type: \"array\"\n          items:\n            $ref: \"#/definitions/User\"\n      responses:\n        default:\n          description: \"successful operation\"\n  /user/createWithList:\n    post:\n      tags:\n      - \"user\"\n      summary: \"Creates list of users with given input array\"\n      description: \"\"\n      operationId: \"createUsersWithListInput\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - in: \"body\"\n        name: \"body\"\n        description: \"List of user object\"\n        required: true\n        schema:\n          type: \"array\"\n          items:\n            $ref: \"#/definitions/User\"\n      responses:\n        default:\n          description: \"successful operation\"\n  /user/login:\n    get:\n      tags:\n      - \"user\"\n      summary: \"Logs user into the system\"\n      description: \"\"\n      operationId: \"loginUser\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"username\"\n        in: \"query\"\n        description: \"The user name for login\"\n        required: true\n        type: \"string\"\n      - name: \"password\"\n        in: \"query\"\n        description: \"The password for login in clear text\"\n        required: true\n        type: \"string\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            type: \"string\"\n          headers:\n            X-Rate-Limit:\n              type: \"integer\"\n              format: \"int32\"\n              description: \"calls per hour allowed by the user\"\n            X-Expires-After:\n              type: \"string\"\n              format: \"date-time\"\n              description: \"date in UTC when token expires\"\n        400:\n          description: \"Invalid username/password supplied\"\n  /user/logout:\n    get:\n      tags:\n      - \"user\"\n      summary: \"Logs out current logged in user session\"\n      description: \"\"\n      operationId: \"logoutUser\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters: []\n      responses:\n        default:\n          description: \"successful operation\"\n  /user/{username}:\n    get:\n      tags:\n      - \"user\"\n      summary: \"Get user by user name\"\n      description: \"\"\n      operationId: \"getUserByName\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"username\"\n        in: \"path\"\n        description: \"The name that needs to be fetched. Use user1 for testing. \"\n        required: true\n        type: \"string\"\n      responses:\n        200:\n          description: \"successful operation\"\n          schema:\n            $ref: \"#/definitions/User\"\n        400:\n          description: \"Invalid username supplied\"\n        404:\n          description: \"User not found\"\n    put:\n      tags:\n      - \"user\"\n      summary: \"Updated user\"\n      description: \"This can only be done by the logged in user.\"\n      operationId: \"updateUser\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"username\"\n        in: \"path\"\n        description: \"name that need to be updated\"\n        required: true\n        type: \"string\"\n      - in: \"body\"\n        name: \"body\"\n        description: \"Updated user object\"\n        required: true\n        schema:\n          $ref: \"#/definitions/User\"\n      responses:\n        400:\n          description: \"Invalid user supplied\"\n        404:\n          description: \"User not found\"\n    delete:\n      tags:\n      - \"user\"\n      summary: \"Delete user\"\n      description: \"This can only be done by the logged in user.\"\n      operationId: \"deleteUser\"\n      produces:\n      - \"application/xml\"\n      - \"application/json\"\n      parameters:\n      - name: \"username\"\n        in: \"path\"\n        description: \"The name that needs to be deleted\"\n        required: true\n        type: \"string\"\n      responses:\n        400:\n          description: \"Invalid username supplied\"\n        404:\n          description: \"User not found\"\nsecurityDefinitions:\n  petstore_auth:\n    type: \"oauth2\"\n    authorizationUrl: \"http://petstore.swagger.io/oauth/dialog\"\n    flow: \"implicit\"\n    scopes:\n      write:pets: \"modify pets in your account\"\n      read:pets: \"read your pets\"\n  api_key:\n    type: \"apiKey\"\n    name: \"api_key\"\n    in: \"header\"\ndefinitions:\n  Order:\n    type: \"object\"\n    properties:\n      id:\n        type: \"integer\"\n        format: \"int64\"\n      petId:\n        type: \"integer\"\n        format: \"int64\"\n      quantity:\n        type: \"integer\"\n        format: \"int32\"\n      shipDate:\n        type: \"string\"\n        format: \"date-time\"\n      status:\n        type: \"string\"\n        description: \"Order Status\"\n        enum:\n        - \"placed\"\n        - \"approved\"\n        - \"delivered\"\n      complete:\n        type: \"boolean\"\n        default: false\n    xml:\n      name: \"Order\"\n  Category:\n    type: \"object\"\n    properties:\n      id:\n        type: \"integer\"\n        format: \"int64\"\n      name:\n        type: \"string\"\n    xml:\n      name: \"Category\"\n  User:\n    type: \"object\"\n    properties:\n      id:\n        type: \"integer\"\n        format: \"int64\"\n      username:\n        type: \"string\"\n      firstName:\n        type: \"string\"\n      lastName:\n        type: \"string\"\n      email:\n        type: \"string\"\n      password:\n        type: \"string\"\n      phone:\n        type: \"string\"\n      userStatus:\n        type: \"integer\"\n        format: \"int32\"\n        description: \"User Status\"\n    xml:\n      name: \"User\"\n  Tag:\n    type: \"object\"\n    properties:\n      id:\n        type: \"integer\"\n        format: \"int64\"\n      name:\n        type: \"string\"\n    xml:\n      name: \"Tag\"\n  Pet:\n    type: \"object\"\n    required:\n    - \"name\"\n    - \"photoUrls\"\n    properties:\n      id:\n        type: \"integer\"\n        format: \"int64\"\n      category:\n        $ref: \"#/definitions/Category\"\n      name:\n        type: \"string\"\n        example: \"doggie\"\n      photoUrls:\n        type: \"array\"\n        xml:\n          name: \"photoUrl\"\n          wrapped: true\n        items:\n          type: \"string\"\n      tags:\n        type: \"array\"\n        xml:\n          name: \"tag\"\n          wrapped: true\n        items:\n          $ref: \"#/definitions/Tag\"\n      status:\n        type: \"string\"\n        description: \"pet status in the store\"\n        enum:\n        - \"available\"\n        - \"pending\"\n        - \"sold\"\n    xml:\n      name: \"Pet\"\n  ApiResponse:\n    type: \"object\"\n    properties:\n      code:\n        type: \"integer\"\n        format: \"int32\"\n      type:\n        type: \"string\"\n      message:\n        type: \"string\"\nexternalDocs:\n  description: \"Find out more about Swagger\"\n  url: \"http://swagger.io\"\n"
  },
  {
    "path": "swagger-override-examples/demo-with-body.json",
    "content": "{\n  \"route\": \"/v2/user/lololo\",\n  \"body\": {\n    \"hello\": \"world\",\n    \"foo\": [12,123,1234],\n    \"top\":{\n      \"kek\":\"lololo\"\n    }\n  },\n  \"empty\": false,\n  \"httpCode\": 200,\n  \"method\": \"GET\"\n}\n"
  },
  {
    "path": "swagger-override-examples/demo.json",
    "content": "{\n  \"route\": \"/v2/store/order/1\",\n  \"body\": {\n  },\n  \"empty\": true,\n  \"httpCode\": 200,\n  \"method\": \"GET\"\n}\n"
  }
]