[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    parser: '@typescript-eslint/parser',\n    parserOptions: {\n        project: ['./tsconfig.json', './test/tsconfig.json'],\n    },\n    extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],\n    rules: {\n        '@typescript-eslint/explicit-module-boundary-types': 'off',\n        '@typescript-eslint/no-explicit-any': 'off',\n        '@typescript-eslint/no-inferrable-types': 'off',\n    },\n    overrides: [\n        {\n            files: ['test/**/*.ts', 'test/**/*.tsx'],\n            rules: {\n                '@typescript-eslint/no-non-null-assertion': 'off',\n            },\n        },\n    ],\n};\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n    - package-ecosystem: npm\n      directory: '/'\n      schedule:\n          interval: daily\n          time: '10:00'\n      open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/github-ci.yaml",
    "content": "name: GitHub CI\non: [push]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.x, 20.x]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Node ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm install\n      - name: test\n        run: npm test\n  release:\n    runs-on: ubuntu-latest\n    needs: test\n    if: github.ref == 'refs/heads/master'\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version: lts/*\n      - run: npm install\n      - name: semantic-release\n        run: npm run semantic-release\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n/notes.md\n/lib\n/types\n/.nyc_output\n/coverage\n/npm-debug.log\n/yarn-error.log\n/yarn.lock\n/package.lock\n.DS_Store\n.idea\n\n# Artifacts from IDEs, etc.:\n/.vscode/snipsnap.code-snippets\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx --no-install pretty-quick --staged\nnpx --no-install lint-staged"
  },
  {
    "path": ".markdownlint.json",
    "content": "{\n    \"line-length\": false,\n    \"no-inline-html\": {\n      \"allowed_elements\": [\n        \"a\"\n      ]\n    },\n    \"no-trailing-punctuation\": {\n        \"punctuation\": \".,;:!\"\n    },\n    \"single-trailing-newline\": false\n}"
  },
  {
    "path": ".mocharc.js",
    "content": "module.exports = {\n    extension: ['js', 'jsx', 'ts', 'tsx'],\n    require: ['ts-node/register'],\n    recursive: true,\n    fullTrace: true,\n    checkLeaks: true,\n    reporter: 'spec',\n};\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": ".nycrc",
    "content": "{\n    \"extension\": [\".ts\"],\n    \"exclude\": [\"**/*.d.ts\"],\n    \"include\": [\"src/**/*.ts\"],\n    \"reporter\": [\"html\", \"text-summary\", \"lcov\"],\n    \"all\": true\n}"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n    trailingComma: 'es5',\n    printWidth: 100,\n    tabWidth: 4,\n    semi: true,\n    singleQuote: true,\n    overrides: [\n        {\n            files: '*.md',\n            options: {\n                tabWidth: 2,\n            },\n        },\n        {\n            files: '*.yaml',\n            options: {\n                tabWidth: 2,\n            },\n        },\n    ],\n};\n"
  },
  {
    "path": ".releaserc.yml",
    "content": "extends: '@jwalton/semantic-release-config'"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"type\": \"node\",\n            \"request\": \"launch\",\n            \"name\": \"Run Current Mocha Test\",\n            \"program\": \"${workspaceFolder}/node_modules/mocha/bin/_mocha\",\n            \"protocol\": \"inspector\",\n            \"smartStep\": true,\n            \"sourceMaps\": true,\n            \"outputCapture\": \"std\",\n            \"args\": [\"--colors\", \"${relativeFile}\"],\n            \"env\": {\n                \"RUN_FROM_SRC\": \"true\",\n                \"NODE_ENV\": \"test\",\n                \"SETUP_DOM\": \"true\"\n            },\n            \"internalConsoleOptions\": \"openOnSessionStart\"\n        }\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"typescript.tsdk\": \"./node_modules/typescript/lib/\"\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": " {\n    // See https://go.microsoft.com/fwlink/?LinkId=733558\n    // for the documentation about the tasks.json format\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"type\": \"typescript\",\n            \"tsconfig\": \"tsconfig.json\",\n            \"problemMatcher\": [\n                \"$tsc\"\n            ],\n            \"group\": {\n                \"kind\": \"build\",\n                \"isDefault\": true\n            }\n        },\n        {\n            \"type\": \"npm\",\n            \"script\": \"test\",\n            \"problemMatcher\": []\n        }\n    ]\n}"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThis project uses [semantic-release](https://github.com/semantic-release/semantic-release)\nso commit messages should follow [Angular commit message conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines).\n\n## Supported Node Versions\n\nExegesis should run on node 10.x.x and up.\n\nIn a perfect world, no polyfills will be required.  Exegesis should *not* add\nany polyfills on its own.  If there are required polyfills, we should document\nwhat they are in README.md and let users of this library decide which polyfills\nthey want to include.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2018 Jason Walton \n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Exegesis OpenAPI Engine\n\n[![NPM version](https://badge.fury.io/js/exegesis.svg)](https://npmjs.org/package/exegesis)\n![Build Status](https://github.com/exegesis-js/exegesis/workflows/GitHub%20CI/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/exegesis-js/exegesis/badge.svg)](https://coveralls.io/r/exegesis-js/exegesis)\n[![Greenkeeper badge](https://badges.greenkeeper.io/exegesis-js/exegesis.svg)](https://greenkeeper.io/)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n\n> ## _exegesis_\n>\n> _n._ An explanation or critical interpretation of a text, especially an\n> API definition document.\n>\n> -- No dictionary ever\n\nThis library implements a framework-agnostic server side implementation of\n[OpenAPI 3.x](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#requestBodyObject).\n\n## Description\n\nExegesis is a library for implementing server-side OpenAPI 3.x The library has been\nwritten in such a way that hopefully it will also be used to implement future\nversions of OpenAPI, or possibly even other API description standards altogether.\n\nYou probably don't want to be using this library directly. Have a look at:\n\n- [exegesis-express](https://github.com/exegesis-js/exegesis-express) - Middleware\n  for serving OpenAPI 3.x APIs from [express](https://expressjs.com/) or\n  [connect](https://github.com/senchalabs/connect).\n- [exegesis-koa](https://github.com/confuser/exegesis-koa) - Middleware\n  for serving OpenAPI 3.x APIs from [koa](https://koajs.com/).\n\n## Features\n\n- Full support for OpenAPI 3.x.x (see [issues tagged with conformance](https://github.com/exegesis-js/exegesis/issues?q=is%3Aissue+is%3Aopen+label%3Aconformance) for areas which could use some improvement).\n- Built in support for \"application/json\" and \"application/x-www-form-urlencoded\" requests\n- Can use express [body parser middlewares](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#mimetypeparsers)\n- [Response validation](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#onresponsevalidationerror)\n- [Authentication support](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md)\n- [Plugins](https://github.com/exegesis-js/exegesis/tree/master/docs) allow easy extensibility\n- Easy support for [validating custom formats](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#customformats)\n\n## Tutorial\n\nCheck out the tutorial [here](https://github.com/exegesis-js/exegesis/blob/master/docs/Tutorial.md).\n\n## API\n\n### compileApi(openApiDoc, options[, done])\n\nThis function takes an API document and a set of\n[options](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md),\nand returns a connect-style middleware function which will execute the API.\n\n`openApiDoc` is either a path to your openapi.yaml or openapi.json file,\nor it can be a JSON object with the contents of your OpenAPI document. This\nshould have the [`x-exegesis-controller`](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Specification%20Extensions.md)\nextension defined on any paths you want to be able to access.\n\n`options` is described in detail [here](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md). At a\nminimum, you'll probably want to provide `options.controllers`, a path to where\nyour [controller modules](https://github.com/exegesis-js/exegesis/blob/master/docs/Exegesis%20Controllers.md)\ncan be found. If you have any security requirements defined, you'll also\nwant to pass in some [authenticators](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md).\nTo enable response validation, you'll want to provide a validation callback\nfunction via [`onResponseValidationError()`](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#onresponsevalidationerror).\nExegesis's functionality can also be extended using [plugins](https://github.com/exegesis-js/exegesis/tree/master/docs),\nwhich run on every request. Plugins let you add functionality like\n[role base authorization](https://github.com/exegesis-js/exegesis-plugin-roles),\nor CORS.\n\n### compileRunner(openApiDoc, options[, done])\n\nThis function is similar to `compileApi`; it takes an API document and a set of\n[options](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md),\nand returns a \"runner\". The runner is a `function runner(req, res)`, which takes\nin a standard node HTTP request and response. It will not modify the response,\nhowever. Instead it returns (either via callback or Promise) and `HttpResult`\nobject. This is a `{headers, status, body}` object, where `body` is a readable\nstream, read to be piped to the response.\n\n### writeHttpResult(httpResult, res[, done])\n\nA convenience function for writing an `HttpResult` from a runner out to the\nresponse.\n\n## Example\n\n```js\nimport * as path from 'path';\nimport * as http from 'http';\nimport * as exegesis from 'exegesis';\n\n// See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md\nconst options = {\n  controllers: path.resolve(__dirname, './src/controllers'),\n};\n\n// `compileApi()` can either be used with a callback, or if none is provided,\n// will return a Promise.\nexegesis.compileApi(\n  path.resolve(__dirname, './openapi/openapi.yaml'),\n  options,\n  (err, middleware) => {\n    if (err) {\n      console.error('Error creating middleware', err.stack);\n      process.exit(1);\n    }\n\n    const server = http.createServer((req, res) =>\n      middleware(req, res, (err) => {\n        if (err) {\n          res.writeHead(err.status || 500);\n          res.end(`Internal error: ${err.message}`);\n        } else {\n          res.writeHead(404);\n          res.end();\n        }\n      })\n    );\n\n    server.listen(3000);\n  }\n);\n```\n\n## Internal Workings\n\nInternally, when you \"compile\" an API, Exegesis produces an\n[ApiInterface](https://github.com/exegesis-js/exegesis/blob/f5266dfd27cdb40c5ebf8063303acbf483d78ed9/src/types/internal.ts#L50) object.\nThis is an object that, given a method, url, and headers, returns a\n[`resolvedOperation`](https://github.com/exegesis-js/exegesis/blob/f5266dfd27cdb40c5ebf8063303acbf483d78ed9/src/types/internal.ts#L21) -\nessentially a collection of functions that will parse and validate the body and\nparameters, has the controller that executes the functionality, etc... The only\ncurrent implementation for an ApiInterface is the\n[`oas3/OpenApi` class](https://github.com/exegesis-js/exegesis/blob/master/src/oas3/OpenApi.ts).\nEssentially this class's job is to take in an OpenAPI 3.x.x document, and turn it\nan ApiInterface that Exegesis can use. In theory, however, we could parse some\nother API document format, produce an ApiInterface, and Exegsis would still be\nable to run it.\n"
  },
  {
    "path": "docs/Exegesis Controllers.md",
    "content": "# Introduction to Controllers\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Introduction to Controllers](#introduction-to-controllers)\n  - [Writing Controllers](#writing-controllers)\n  - [Specifying a Controller to Run](#specifying-a-controller-to-run)\n  - [What's in a Context?](#whats-in-a-context)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\n## Writing Controllers\n\nExegesis controllers are functions that take in a context, and produce a result\nto return to the client. This is one of the simplest controllers you can\nwrite:\n\n```js\nexport function myController(context) {\n  const name = context.params.query.name;\n  return { message: `Hello ${name}` };\n}\n```\n\nThis will return the object provided as a JSONdocs response. You can return a\nJSON object, a string, a buffer, or a readable stream. You can also more\nexplicitly set the body by setting `res.body` or calling `res.setBody()`,\n`res.json()`, or `res.pureJson()`:\n\n```js\nexport function myController(context) {\n  const name = context.params.query.name;\n  context.res\n      .status(200)\n      .set('content-type', 'application/json');\n      .setBody({message: `Hello ${name}`});\n}\n```\n\nControllers can be asynchronous, supporting either callbacks or Promises:\n\n```js\nexport function myAsyncController(context, callback) {\n  callback(null, { message: 'Hello World!' });\n}\n\nexport function myPromiseController(context) {\n  return Promise.resolve({ message: 'Hello World!' });\n}\n```\n\nControllers can, of course, also return non-JSON data:\n\n```js\nexport function myController(context) {\n  const name = context.params.query.name;\n  context.res\n    .status(200)\n    .setHeader('content-type', 'text/xml')\n    .setBody(`<message>Hello ${name}</message>`);\n}\n```\n\nNote, however, that response validation will not be done if the body is not a\nJSON object.\n\n## Specifying a Controller to Run\n\nControllers are defined inside modules (.js files). In order to resolve a\ncontroller, you specify both the name of the module and the name of the\nfunction to call within the module. Here's a quick example:\n\n```yaml\nopenapi: 3.0.3\ninfo:\n  title: Example\n  version: 1.0.0\npaths:\n  \"/users\"\n    x-exegesis-controller: userController\n    get:\n      operationId: getUsers\n```\n\nHere, we'd find a module named \"userController.js\", and then we'd call\n`getUsers(context)` within that module.\n\nIf you have a path that takes input in multiple different formats, you can\nalso specify the `operationId` in the MediaType object, using\n`x-exegesis-operationId`:\n\n```yaml\nopenapi: 3.0.3\ninfo:\n  title: Example\n  version: 1.0.0\npaths:\n  \"/users\"\n    x-exegesis-controller: userController\n    post:\n      content:\n        application/json:\n          schema: {}\n          x-exegesis-operationId: getUsersJson\n        multipart/form-data:\n          schema: {}\n          x-exegesis-operationId: getUsersMultipart\n```\n\nYou may specify `x-exegesis-controller` in any of the following:\n\n- [OpenAPI Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oasObject)\n- [Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject)\n- [Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathItemObject)\n- [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operationObject)\n- [Media Type Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#mediaTypeObject) within the Operation Object's `requestBody.content[string]`.\n\nExegesis will start from the Media Type Object specified by a given request,\nwalk its way upwards to find the \"closest\" `x-exegesis-controller`, and then\ndo the same for `x-exegesis-operationId`. This will uniquely identify which\ncontroller module and function to call.\n\n## What's in a Context?\n\n- `context.req` - The Node `http.IncomingMessage` from Node.js.\n- `context.res` - A `Exegesis Response` object. This is very similar to a\n  `http.ServerResponse`, and has many functions that will be familiar if you're\n  used to express.\n- `context.origRes` - This is the original `http.ServerResponse` from Node.js.\n  In general you should not write directly to `context.origRes`. Exegesis will\n  be unable to do response validation if you write directly to the origRes\n  object.\n- `context.params` - This is a `{query, header, path, server, cookie}` object.\n  Each member is an object where the keys are parameter names, and values are\n  the parsed parameters.\n- `context.parameterLocations` - This is a mirror of the `context.params` object,\n  but instead of parameter values, this has parameter locations. (`server`\n  parameters are not preset in `parameterLocations` in the current release.)\n- `context.requestBody` - The parsed message body, if one is present.\n- `context.security` - This is a dictionary where keys are security schemes,\n  and values are `{user, roles, scope}` objects (as returned by the associated\n  [authenticator](./OAS3%20Security.md)). For OAS3, there will only be objects\n  here for the first matched security requirement.\n- `context.user` - This is a user, as returned by an authenticator. For OAS3\n  this will only be present if the matched security requirement had exactly\n  one security scheme.\n- `context.api` - This is an object containing details about which parts of\n  the OpenAPI document were resolved to service your request. For OAS3 this\n  will be an object with the following fields:\n\n  - `openApiDoc` - The OpenAPI document for your API. This is a \"bundled\" object,\n    with all external $refs resolved, and only internal $refs remaining.\n  - `serverObject` - The Server Object which was matched.\n  - `serverPtr` - A JSON Pointer to the server object in `openApiDoc`.\n  - `pathItemObject` - The Path Item Object which was matched.\n  - `pathItemPath` - A JSON Pointer to the path item object.\n  - `operationObject` - The matched Operation Object.\n  - `operationPtr` - A JSON Pointer to the Operation Object.\n  - `requestBodyMediaTypeObject` - The matched MediaType Object from the operation's\n    `requestBody`, or null if none was matched or the Operation has no requestBody.\n  - `requestBodyMediaTypePtr` - A JSON Pointer to the MediaType Object.\n\n- `context.makeError(statusCode, message)` is a convenience function which\n  will create a new Error object with a status. Note that this does not\n  throw the error - your code has to do that.\n\n- `context.makeValidationError(message, location)` is a convenience function which\n  will creates a new validation error. `location` should be a parameter location\n  from `context.parameterLocations`. Note that this does not throw the error - your\n  code has to do that.\n\n- `context.route.path` is a string with the path that was matched from the API document.\n  This is similar to `req.route.path` in Express.\n\n- `context.baseUrl` is a string with the baseUrl that was stripped off to match\n  `context.route.path`. This is similar to `req.baseUrl` in Express.\n"
  },
  {
    "path": "docs/Exegesis Plugins.md",
    "content": "# Plugins\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Plugins](#plugins)\n  - [Writing a Plugin](#writing-a-plugin)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\nPlugins can be used to add functionality to Exegesis.\n\nToday Exegesis only supports OpenAPI 3.x.x (OAS3), however the core of Exegesis\nwas designed to allow it to handle other API specifications. Exegesis\ndivides the execution of an API up into the following phases:\n\n- **Routing** - Any REST base API will define a set of URLs or URIs which map to\n  various functions and resources. Exegesis first examines the URL to work out\n  which resource is being accessed. In OAS3, this means matching a Server\n  Object and Path Object.\n- **Security** - After routing is finished, but before we do any work parsing input,\n  Exegesis will figure out if the request is authenticated and authorized to\n  access the given route. In OAS3, this means checking the Security Object\n  associated with the route, and verifying that at least one of the Security\n  Requirements has been met.\n- **Input Parsing and Validation** - Exegesis takes a \"lazy\" approach to parsing\n  both parameters and the request body; the body and parameters are parsed\n  and validated the first time they are accessed. This allows authenticators\n  and plugins access to the body and parameters if required, but also means\n  if no one asks for the parameters or the body, we won't bother to parse\n  them. However, to make writing controllers easier, if they have not be parsed\n  by this point Exegesis explicitly parses them and stores them in\n  `context.params` and `context.requestBody`.\n- **Controller** - Exegesis calls into a controller to run business logic\n  associated with the resource being accessed.\n- **Response Validation** - Exegesis optionally validates that the response\n  has been correctly generated. For OAS3, this means checking the response\n  against the response schema.\n\nFinally, the response is written out back to the client.\n\nPlugins allow you to add functionality before (almost) any of these phases,\nand even to modify the API document before it is compiled.\n\n## Writing a Plugin\n\nPlugins are published on NPM with the prefix `exegesis-plugin-` to make it\neasy to find plugins.\n\nTo write a plugin, you write a JavaScript module which exports a default function.\nThis function accepts a single parameter which is an object with the options to\nconfigure your plugin. It returns a `{info, makeExegesisPlugin(data)}` object.\n`makeExegesisPlugin` takes a single parameter, an `{apiDoc}` object. Right now\nthe API document will always be an OAS3 document, however this may not be the\ncase in the future, so plugins should take care to verify the document they are\nbeing passed is in the format they expect.\n\nThe `makeExegesisPlugin` function should return an `ExegesisPluginInstance`\nobject. This has functions which will be called at various phases for each\nrequest. See the example below for a list of functions that can be defined.\nEach function can either take a callback function as the last parameter, or\nreturn a Promise. See the example below.\n\nPlugins are free to modify the `apiDoc` object in the `makeExegesisPlugin()`\nfunction (although they may not replace it with an entirely new object). This\nis the only time that plugins may modify the document.\n\nIn any phase, plugins can also generate a response by writing to\n`context.res.body`, or can add headers to `context.res.headers`. Plugins must\n_not_ write to `context.origRes`. Writing a response will cause subsequent\nphases and plugins to be skipped, and if the controller has not yet been called,\nthe controller will be skipped. The exception to this is the `postController()`\nfunction, which will always be called for a plugin, even if a previous phase has\nwritten a response.\n\n```js\nimport * as semver from 'semver';\n\nfunction makeExegesisPlugin({apiDoc}) {\n  // Verify the apiDoc is an OpenAPI 3.x.x document, because this plugin\n  // doesn't know how to handle anything else.\n  if (!apiDoc.openapi) {\n    throw new Error(\"OpenAPI definition is missing 'openapi' field\");\n  }\n  if (!semver.satisfies(apiDoc.openapi, '>=3.0.0 <4.0.0')) {\n    throw new Error(`OpenAPI version ${apiDoc.openapi} not supported`);\n  }\n\n  // Can make modifications to apiDoc at this point, such as adding new\n  // routes, or modifying documentation - whatever you want to do.  Just\n  // keep in mind that other plugins might make changes, also, either before\n  // or after this.  If you need the \"final\" apiDoc, see `preCompile`.\n\n  // Return an ExegesisPluginInstance.\n  return {\n    // Called exactly once, before Exegesis \"compiles\" the API document.\n    // Plugins must not modify apiDoc here.\n    preCompile({apiDoc, options}) {\n    }\n\n    // Called before routing.  Note that the context hasn't been created yet,\n    // so you just get a raw `req` and `res` object here.\n    preRouting({req, res}) {\n    }\n\n    // Called immediately after the routing phase.  Note that this is\n    // called before Exegesis verifies routing was valid - the\n    // `pluginContext.api` object will have information about the\n    // matched route, but will this information may be incomplete.\n    // For example, for OAS3 we may have matched a route, but not\n    // matched an operation within the route. Or we may have matched\n    // an operation but that operation may have no controller defined.\n    // (If we failed to match a route at all, this will not be called.)\n    //\n    // If your API added a route to the API document, this function is a\n    // good place to write a reply.\n    //\n    // Note that calling `pluginContext.getParams()` or `pluginContext.getRequestBody()`\n    // will throw here if routing was not successful.\n    postRouting(pluginContext) {\n    }\n\n    // Called for each request, after security phase and before input\n    // is parsed and the controller is run.  This is a good place to\n    // do extra security checks.  The `exegesis-plugin-roles` plugin,\n    // for example, generates a 403 response here if the authenticated\n    // user has insufficient privliedges to access this path.\n    //\n    // Note that this function will not be called if a previous plugin\n    // has already written a response.\n    postSecurity(pluginContext) {\n    }\n\n    // Called immediately after the controller has been run, but before\n    // any response validation.  This is a good place to do custom\n    // response validation.  If you have to deal with something weird\n    // like XML, this is where you'd handle it.\n    //\n    // This function can modify the contents of the response.\n    postController(context) {\n    }\n\n    // Called after the response validation step.  This is the last step before\n    // the response is converted to JSON and written to the output.\n    postResponseValidation(context) {\n    }\n  };\n}\n\nexport default function plugin(options) {\n  return {\n    info: {\n      // This should match the name of your npm package.\n      name: 'exegesis-plugin-example'\n    },\n    makeExegesisPlugin\n  };\n}\n```\n"
  },
  {
    "path": "docs/OAS3 Parameter Parsing.md",
    "content": "# Parameter Parsing\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Parameter Parsing](#parameter-parsing)\n  - [Parameter Parser Functions](#parameter-parser-functions)\n  - [Cookie Parameters](#cookie-parameters)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\nParameters in OpenAPI 3 are serialized using URI Templates from RFC 6570.\nThe RFC explains exactly how to convert various values into strings\n(a process the RFC calls \"expansion\") but says nothing about going in the\nopposite direction (a process the RFC doesn't even give a name, but which\nwe will call \"parsing\" for this discussion).\n\nSince parameter parsing happens on every incoming request, the goal is to\noptimize as much of parameter parsing as possible ahead-of-time. We refer\nto this process here as \"compiling\" the parser.\n\nThe Exegesis context is expecting a `parameters` object that consists of one\nkey for each of the \"in\"s (path, query, header, cookie), and each of those\nobjects has parameter names for keys, mapping to the associated values:\n\n```js\nparameters = {\n  path: {\n    id: '5acc0981eaad142f3a754c77',\n  },\n  query: {\n    min: 6,\n    users: ['tom', 'dick', 'harry'],\n    deepObject: { a: 7, b: [2, 4] },\n  },\n  header: {},\n  cookie: {},\n};\n```\n\nThe path resolver extracts all the values of the path parameters for us as raw\nstrings and stores them in an object. Node.js does the same for us for\nheaders. So, our goal is: given an object of path parameters, an object\nof headers, and a query string, produce the above in as short a time as possible.\n\nThe first thing to note about URI template expansion is that, using \"[form-style\nquery expansion](https://tools.ietf.org/html/rfc6570#section-3.2.8)\", without\nthe \"explode\" modifier the array `['a', 'b', 'c', 'd']` would be expanded as\n`?var=a,b,c,d`, and the object `{a: 'b', c: 'd'}` would be expanded as exactly\nthe same thing. So the first thing we need to know about a parameter, before\nwe can parse the value, is the target type we're trying to parse the parameter\nas. Unfortunately, OpenAPI lets us use JSON-schema to define the type, and it's\neasy with `oneOf` or `anyOf` to construct a schema that could validate both\nan array and an object.\n\nSo, the general approach taken, when compiling a parser for an object, is:\n\n- If the object can only be a single type, and that type is not an object,\n  then we parse the result and produce either a string or an array of strings.\n  We attempt to type-coerce the resulting object to the correct basic type. If\n  this fails, we throw a validation error. We don't do full validation here -\n  we leave that for the validation step. So if the type of the object is\n  \"array\", then you'll get an array of strings here. We leave it to the\n  validation step to do further type coercion as required.\n- If the object can be multiple types, and none of those types is \"object\",\n  then we'll parse the object into a string or an array and let validation\n  handle type coercion.\n- If the object can only be a single type, and that type is an object,\n  then we attempt to parse the result into an object. If this fails (because\n  the \"array\" of values has an odd length) then we throw a validation error.\n  Again, we do no formal validation here - the object may not at all match\n  the schema provided, but at least it will be an object.\n- If the object can be multiple types and at least one of those types is\n  \"object\", then in a future version we'll do something clever here, like try\n  to parse it as an array and do full validation and if that fails move on to\n  trying to parse it as an object. Right now though, we throw an exception\n  when compiling the schema. See\n  [discussion here](https://github.com/OAI/OpenAPI-Specification/issues/1535#issuecomment-380032898).\n\n## Parameter Parser Functions\n\nAt run time, parameter parsers are functions that take in some input, and produce\na value. These functions are synchronous, because it makes the parameter\ncode easy to work with, and we don't have the overhead of creating Promises or\ndealing with callbacks.\n\nParser functions take in an object from a specific \"in\", where keys are\nparameter names, and values are either a string or an array of strings. A\n\"path parser\" function will only receive values parsed out of the path, a\n\"query parser\" will only receive values parsed out of the query string, etc.\n\nParsers also receive a second parameter, a `parameterContext`, which has\ninformation about where the parameter came from, and in the case of query\nparameters has access to the original query string.\n\nNote that values passed to parameter parsers will not be passed through\n`decodeURIComponent()` first; RFC 6570 requires that characters from the\n\"reserved set\" be %-encoded. So, for a \"simple\" list, a value that contains\na \",\" will have that \",\" encoded. This means we need to split the raw\nvalue on \",\" and then pass each resulting string through `decodeURIComponent()`\nto correctly parse a list.\n\nThere are two special cases with query parameters. The first is where you\nhave a query parameter which represents an object, and the \"explode\" option is\nset. For example:\n\n```yaml\nparameter:\n  name: 'myParam'\n  in: query\n  style: form\n  explode: true\n  schema:\n    type: object\n    properties:\n      a: { type: string }\n      b: { type: string }\n```\n\nThis will be expanded in the query string as '?a=foo&b=bar'. Note that the name\nof our parameter, 'myParam', doesn't even appear in the query string.\n\nAs a result, query parameter parsers for exploded objects are passed the entire\nset of extracted values as their 'value', and simply return it. We let validation\ntake care of working out if the are extra fields in the object that shouldn't be\nthere.\n\nThe second special case is for query parameters where the style is set to \"deepObject\".\nIn this case, we parse the entire query string with the `qs` library, find the\nvalue for the parameter name, and return this as the parsed object. Here we\ndon't worry about %-encoding anything, we just let `qs` handle everything.\n\nNote that the same query parsers are used to handle `application/x-www-form-urlencoded`\nbodies.\n\n## Cookie Parameters\n\nThe format for cookie parameters is ambiguous in OpenAPI 3.0. The specification\nsays one thing, but the documentation on swagger.io says something else.\nUntil [OpenAPI-Specification #1528](https://github.com/OAI/OpenAPI-Specification/issues/1528)\nis resolved, Exegesis will probably not support cookie parameters. If you have a specific\nuse case, please raise an issue, and we'll see if we can help you out.\n"
  },
  {
    "path": "docs/OAS3 Security.md",
    "content": "# OAS3 Security\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [OAS3 Security](#oas3-security)\n  - [Authenticators](#authenticators)\n    - [Example: Basic Auth](#example-basic-auth)\n    - [Example: Basic Auth with Passport](#example-basic-auth-with-passport)\n  - [Example](#example)\n    - [Using Multiple Authentication Types](#using-multiple-authentication-types)\n      - [Scenarios](#scenarios)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\nEach operation in OAS3 can have a list of [Security Requirement Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#securityRequirementObject)\nassociated with it, in `security` (or inherited from the root document's `security`).\nEach security requirement object has a list of security schemes; in order to\naccess an operation, a request must satisfy all security schemes for at least\none of the objects in the list.\n\nWhen compiling your API, Exegesis will takes an `authenticators` option which\nmaps security schemes to authenticators. An `authenticator` is a\nfunction which tries to authenticate the user using a given scheme.\n\n## Authenticators\n\nAn authenticator is a function which authenticates a request.\n\n```js\nasync function promiseAuthenticator(pluginContext, info) {...}\nfunction callbackAuthenticator(pluginContext, info, done) {...}\n```\n\nFor example:\n\n```js\nasync function sessionAuthenticator(pluginContext, info) {\n  const session = pluginContext.req.headers.session;\n  if (!session) {\n    return { type: 'missing', statusCode: 401, message: 'Session key required' };\n  } else if (session === 'secret') {\n    return { type: 'success', user: { name: 'jwalton', roles: ['read', 'write'] } };\n  } else {\n    // Session was supplied, but it's invalid.\n    return { type: 'invalid', statusCode: 401, message: 'Invalid session key' };\n  }\n}\n\nconst options: exegesis.ExegesisOptions = {\n  controllers: path.resolve(__dirname, './controllers'),\n  authenticators: {\n    sessionKey: sessionAuthenticator,\n  },\n};\n```\n\nAuthenticators are very similar to controllers, except their role is to\nreturn authentication information. Note that the `context` passed to an\nauthenticator is a \"plugin context\" - this differs from a regular context\nin that `body` and `params` will be undefined as they have not been\nparsed yet (although access to the body and parameters are available via\nthe async functions `getRequestBody()` and `getParams()`). Authenticators are also\npassed an `info` object, which is either a `{in, name}` object describing\nwhat field the authentication information should be stored in, or else a\n`{scheme}` object describing the HTTP authentication scheme being used,\nas described in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-5.1).\n\nIf the user is successfully authenticated, an authenticator should return a\n`{type: \"success\", user, roles, scopes}` object. `user` is an arbitrary object\nrepresenting the authenticated user; it will be made available to the controller\nvia the context. `roles` is a list of roles which the user has (used by\n[exegesis-plugin-roles](https://github.com/exegesis-js/exegesis-plugin-roles)),\nand `scopes` is a list of OAuth scopes the user is authorized for. Authenticators\nmay also add additional data to this object (for example, when authenticating\nvia OAuth, you might set the `user` to the user the OAuth token is for, and also\nset an `oauthClient` property to identify that this user was authenticated by\nOAuth.)\n\nIf the user did not provide credentials, the authenticator should return a\n`{type: 'missing', challenge, status, message}` object. If\n`challenge` is specified it must be an\n[RFC 7235 challenge](https://tools.ietf.org/html/rfc7235#section-2.1), suitable\nfor including in a WWW-Authenticate header.\n\nIf the user provided authentication credentials, but they are invalid,\nthe authenticator should return a `{type: 'invalid', challenge, status, message}`\nobject.\n\nIf a authenticator returns `undefined`, this is treated like 'missing'.\n\nWhen Exegesis routes a request, it will run the relevant authenticators\nand decide whether or not to allow the request. Note that if an operation has\nno `security`, then no authenticators will be run.\n\nIf a request successfully matches a security requirement object then Exegesis\nwill create a `context.security` object with the details of the matched schemes.\nThis will be available to the controller which handles the operation.\n\nAuthenticators are run prior to body parsing, however the body is available via\nthe async function `context.getRequestBody()` if it is needed.\n\n### Example: Basic Auth\n\n```js\nimport basicAuth from 'basic-auth';\nimport bcrypt from 'bcrypt';\n\n// Note that authenticators can either return a Promise, or take a callback.\nasync function basicAuthSecurity(pluginContext, info) {\n  const credentials = basicAuth(pluginContext.req);\n  if (!credentials) {\n    // The request failed to provide a basic auth header.\n    return { type: 'missing', challenge: info.scheme };\n  }\n\n  const { name, pass } = credentials;\n  const user = await db.User.find({ name });\n  if (!user) {\n    return {\n      type: 'invalid',\n      challenge: info.scheme,\n      message: `User ${name} not found`,\n    };\n  }\n  if (!(await bcrypt.compare(pass, user.password))) {\n    return {\n      type: 'invalid',\n      challenge: info.scheme,\n      message: `Invalid password for ${name}`,\n    };\n  }\n\n  return {\n    type: 'success',\n    user,\n    roles: user.roles, // e.g. `['admin']`, or `[]` if this user has no roles.\n    scopes: [], // Ignored in this case, but if `basicAuth` was an OAuth\n    // security scheme, we'd fill this with `['readOnly', 'readWrite']`\n    // or similar.\n  };\n}\n```\n\n### Example: Basic Auth with Passport\n\nHere's the exact same example, but using [Passport](http://www.passportjs.org/):\n\n```js\nimport exegesisPassport from 'exegesis-passport';\nimport passport from 'passport';\nimport { BasicStrategy } from 'passport-http';\nimport bcrypt from 'bcrypt';\n\n// Note the name of the auth scheme here should match the name of the security\n// role.\npassport.use('basicAuth', new BasicStrategy(\n  function(name, password, done) {\n    db.User.find({name}, (err, user) => {\n      if (err) {return done(err);}\n      bcrypt.compare(password, user.password, (err, matched) => {\n        if (err) {return done(err);}\n        return done(null, matched ? user : false);\n      }\n    });\n  }\n));\n\nconst basicAuthAuthenticator = passportSecurity('basicAuth');\n```\n\n## Example\n\nHere's an example of the securitySchemes section from an OpenAPI document:\n\n```yaml\n  securitySchemes:\n    basicAuth: \n      description: A request with a username and password\n      type: http\n      scheme: basic\n    oauth:\n      description: A request with an oauth token.\n      type: oauth2\n      flows:\n        authorizationCode:\n          authorizationUrl: https://api.exegesis.io/oauth/authorize\n          tokenUrl: https://api.exegesis.io/oauth/token\n          scopes:\n            readOnly: \"Read only scope.\"\n            readWrite: \"Read/write scope.\"\n```\n\nOperations have a list of security requirements:\n\n```yaml\npaths:\n  '/kittens':\n    get:\n      description: Get a list of kittens\n      security:\n        - basicAuth: []\n        - oauth: ['readOnly']\n```\n\nThe \"get\" operation can only be executed if the request matches one of the two\nlisted security requirements.\n\nIf a user authenticated using `basicAuth`, then the controller would have\naccess to the object returned by the authenticator via `context.security.basicAuth`.\nSimilarly, if the request used OAuth then `context.security.oauth` would be\npopulated with the result of the OAuth authenticator.\n\n### Using Multiple Authentication Types\n\nSome REST APIs support several authentication types. The security section lets you combine the security requirements\nusing logical OR and AND to achieve the desired result.\n\nWhile the [Security requirement object section of the Open API Spec](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#security-requirement-object)\nspecifies that `only one of Security Requirement Objects in the list needs to be satisfied to authorize the request` this\nlibrary follows the principal of least privilege by **failing authorization if _any_ of the authenticators return an `invalid` result**.\nOne side affect of this decision is that all authenticators will run for every request to ensure that there are no invalid results.\n\n#### Scenarios\n\n```yaml\nsecurity: # A OR B\n  - A\n  - B\n```\n\n- The request will authenticate if **either** `A` or `B` return a `success` result and **none** return an `invalid` result.\n- `A` will run first then `B`\n- The authentication process will return the result of the first successful authenticator.\n- If a authenticator returns an invalid result the authentication process will be halted and the invalid result will be returned.\n\n```yaml\nsecurity: # A AND B\n  - A\n    B\n```\n\n- The request will authenticate only if **both** `A` and `B` return a `success` result and **none** return an `invalid` result.\n- `A` will run first then `B`\n- The authentication process will return the result of both of the successful authenticators.\n- If an authenticator returns an invalid result the authentication process will be halted and the invalid result will be returned.\n\n```yaml\nsecurity: # (A AND B) OR (C AND D)\n  - A\n    B\n  - C\n    D\n```\n\n- The request will authenticate only if (`A` and `B`) OR (`C` AND `D`) return a `success` result and **none** return an `invalid` result.\n"
  },
  {
    "path": "docs/OAS3 Specification Extensions.md",
    "content": "# Controllers\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Controllers](#controllers)\n  - [x-exegesis-controller](#x-exegesis-controller)\n  - [x-exegesis-operationId](#x-exegesis-operationid)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\n## x-exegesis-controller\n\nControls which module defines the controller for an operation.\n`x-exegesis-controller` may contains \"/\"s if controllers are organized in a\nhierarchy.\n\nAllowed in:\n\n- [OpenAPI Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oasObject)\n- [Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject)\n- [Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathItemObject)\n- [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operationObject)\n- [Media Type Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#mediaTypeObject) within the Operation Object's `requestBody.content[string]`.\n\nDefinitions at lower levels override definitions at higher levels.\n\n## x-exegesis-operationId\n\nControls which operation is called within a controller.\n\nAllowed in:\n\n- [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operationObject) - If `operationId` and `x-exegesis-operationId` are both specified, then `x-exegesis-operationId` takes precedence.\n- [Media Type Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#mediaTypeObject) within the Operation Object's `requestBody.content[string]`.\n\nDefinitions at lower levels override definitions at higher levels. Definitions in Operation or Media Type Object also override `operationId` in the Operation Object.\n"
  },
  {
    "path": "docs/Options.md",
    "content": "# Exegesis Options\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Exegesis Options](#exegesis-options)\n  - [controllers](#controllers)\n  - [allowMissingControllers](#allowmissingcontrollers)\n  - [authenticators](#authenticators)\n  - [mimeTypeParsers](#mimetypeparsers)\n  - [defaultMaxBodySize](#defaultmaxbodysize)\n  - [customFormats](#customformats)\n  - [ignoreServers](#ignoreservers)\n  - [autoHandleHttpErrors](#autohandlehttperrors)\n  - [onResponseValidationError](#onresponsevalidationerror)\n  - [validateDefaultResponses](#validatedefaultresponses)\n  - [treatReturnedJsonAsPure](#treatreturnedjsonaspure)\n  - [allErrors](#allerrors)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\n## controllers\n\nControllers are functions that Exegesis executes to handle incoming requests.\nYou can read about controllers\n[here](https://github.com/exegesis-js/exegesis/blob/master/docs/Exegesis%20Controllers.md).\n\nThe `controllers` option tells Exegesis how to find controller functions to\ncall. This can either be the name of a folder containing controller modules,\nor it can be an object where keys are controller names. If it is a folder,\nyou can additionally specify `controllersPattern`, which is a glob pattern\ntelling Exegesis which files to load.\n\nFor example: suppose you have a folder in your project named \"src/controllers\",\nwhich contains \"Pets.js\" and \"Users.ts\". If you're loading your OpenAPI\ndocument in \"/src/index.js\", you could specify `controllers` as:\n\n```js\nimport * as path from 'path';\n\nconst options = {\n  controllers: path.resolve(__dirname, 'controllers'),\n  controllersPattern: '**/*.@(ts|js)',\n};\n```\n\nThen you can use [`x-exegesis-controller: Pets`](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Specification%20Extensions.md)\nin your OpenAPI document to reference the \"Pets.js\" module, and use either\n`operationId` or `x-exegesis-operationId` to reference a function within the\nmodule.\n\n## allowMissingControllers\n\nIf false, then if any operations do not define a controller, Exegesis will raise\nan error when the API is being compiled. If true, then Exegesis will simply\npretend any operations that don't have a controller do not exist, and will not\nhandle them.\n\nDefaults to true.\n\n## authenticators\n\nAn object specifying authenticators. Keys are security scheme names from your\nOpenAPI document, values are authenticator functions. See [OAS3 Security](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md)\nfor details.\n\n## mimeTypeParsers\n\nAn object where keys are either mime types or mime type wildcards (e.g. 'text/\\*'),\nand values are parsers.\n\nThis option is used to control how Exegesis [parses message bodies and certain\nparameters](./OAS3%20Parameter%20Parsing.md). By default, parsers are provided for 'text/\\*' and\n'application/json'; however you can override either of these.\n\nOpenAPI 3.x defines special handling for 'application/x-www-form-urlencoded',\nand Exegesis will automatically generate an appropriate parser for this content,\nhowever you can override the built-in implementation by supplying your own\nparser for this media type.\n\nA parser is either an object of the form:\n\n```js\n{\n    /**\n     * Synchronous function which parses a string.  A BodyParser must implement\n     * this function to be used for parameter parsing.\n     *\n     * @param {string} encoded - The encoded value to parse.\n     * @returns - The decoded value.\n     */\n    parseString(encoded) {...}\n}\n```\n\nOr:\n\n```js\n{\n    /**\n     * Async function which parses an incoming HTTP request.  This is essentially\n     * here so you can use express/connect body parsers.\n     *\n     * @param {http.IncomingMessage} req - The request to read.  This function\n     *   should add `req.body` after parsing the body.  If `req.body` is already\n     *   present, this function can ignore the body and just call `next()`.\n     * @param {http.ServerResponse} res - The response object.  Well behaved\n     *   body parsers should *not* write anything to the response or modify it\n     *   in any way.\n     * @param next - Callback to call when complete.  If no value is returned\n     *   via the callback then `req.body` will be used as the body.\n     */\n    parseReq(req, res, next) {...}\n}\n```\n\nIn order to be used for parsing parameters, a parser must implement\n`parseString()`. A parser that does not implement `parseReq()` can\nstill be used for parsing request bodies.\n\n## defaultMaxBodySize\n\nIf a `MimeTypeParser` provided in `mimeTypeParsers` does not support\n`parseReq()`, this defines the maximum size (in bytes) of a body that will be parsed.\nBodies longer than this will result in a \"413 - Payload Too Large\" error.\nBuilt in body parsers will also respect this option.\n\n## customFormats\n\nIf you use the \"format\" specifier in your OpenAPI document with custom defined\nformats, you must provide validation functions for each format used.\n\n`customFormats` is an object where keys are format names. Values can be one of:\n\n- A RegExp for checking a string.\n- A `function(string) : boolean` for checking a string, which returns\n  false the the string is invalid.\n- A `{validate, type}` object, where `type` is either \"string\" or \"number\",\n  and validate is a `function(string) : boolean`.\n\n## ignoreServers\n\nOpenAPI 3.x lets you specify what servers your API is available on. For example:\n\n```yaml\nservers:\n  - url: '/api/v2'\n```\n\nBy default, Exegesis will take 'servers' into account when routing requests,\nso if you have the above servers section, and a path in your API called\n\"/users\", then exegesis will only match the route if the incoming requests has\nthe URL \"/api/v2/users\".\n\nIf you have path templates in your servers, the variables will be available to\nyour controllers via `context.params.server`.\n\nIf you specify the `ignoreServers` option, however, exegesis will ignore the\nservers section, and route purely based on your paths.\n\n## autoHandleHttpErrors\n\nBy default, ExegesisRunner will turn `exegesis.HttpError`s (such as errors\ngenerated from `context.makeError()`, `exegesis.ValidationError`s, or any error\nwith a `.status`) into JSON replies with appropriate error messages. If you want\nto handle these errors yourself, set this value to false. Alternatively, you can set the value\nto a `function(err, { req })` function that handles the error returning a `HttpResult` object.\nSee [customErrorHandler](../test/integration/integration/customErrorHandler.ts) for an example.\n\nNote that all `HttpError`s will have a `.status` property with a suggested\nnumeric HTTP response code.\n\n## onResponseValidationError\n\nThis is a function to call when response validation fails. If you provide this\nfunction, Exegesis will validate the responses that controllers generate before\nthey are sent to the client. If you throw an exception in this function, a\n500 error will be generated and the reply will not be sent.\n\nNote that when bodies are strings, buffers, or streams, Exegesis will not try\nto parse your body to see if it conforms to the response schema; only JSON\nobjects are validated.\n\nIf provided, this should be a `function(result)` function, where:\n\n- `result.errors` is a list of validation errors. Validation errors\n  are `{type, message, location: {in: 'response', name: 'body', docPath}}` objects.\n- (for OAS3) `result.isDefault` is true if we validated against a 'default' status code.\n- `result.context` is the context passed to the controller.\n\nA note about response validation and performance; if you call `context.res.json()`\nor return an object from your controller, then the object or any nested objects\ncould have a `toJSON()` method on them. This would happen when, for example,\nyou return a Mongoose object, or use a Mongoose object as a child of your object.\nIn order to correctly validate the response for such an object, Exegesis must\nfirst serialize the object to JSON, and then deserialize it again to get the\nactual transmitted object. This is murderous for performance. Checking to see\nif an object has any toJSON() functions is also not great for performance. In\norder to get around this, you can call `context.res.pureJson()` to set the JSON\nreply.\n\nNote that in a future major release of Exegesis, returning a JSON object will\nhave the same behavior as calling `context.res.pureJson()` instead of having the\nsame behavior as calling `context.res.json()`.\n\n## validateDefaultResponses\n\nControls how Exegesis validates responses. If this is set to false, then in\nOAS3 Exegesis will not do validation for responses unless the response status\ncode matches an explicit status code in the responses object (not the \"default\"\nstatus code). If this is set to true, then all responses will be validated.\n\nThis option is ignored if `onResponseValidationError` is not set. If\n`onResponseValidationError` is set, the default is true.\n\n## treatReturnedJsonAsPure\n\nIf true, then when a controller returns a JSON object, exegesis will call\n`context.res.pureJson(val)` to set the body of the response. If false, Exegesis\nwill call `context.res.json(val)`. See `onResponseValidationError()` for\na discussion about the difference between these.\n\nThis defaults to false, but in a future release it will default to true.\n\n## strictValidation\n\nIf true, then this will put ajv into [\"strict mode\"](https://ajv.js.org/strict-mode.html).\n\n## allErrors\n\nIf set, then when encountering a validation error Exegesis will return\nall errors found in the document instead of just the first error. This\ncauses Exegesis to spend more time on requests with errors in them, so\nfor performance reasons this is disabled by default.\n\n## lazyCompileValidationSchemas\n\nResponse and request schemas are compiled by ajv to make validation faster. However compilation is slow\nand can cause compilation of API to take long time. Enabling this will cause validation schemas\ncompilation to be executed when the validator is needed.\n"
  },
  {
    "path": "docs/Tutorial.md",
    "content": "# Exegesis Tutorial\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Exegesis Tutorial](#exegesis-tutorial)\n  - [Project](#project)\n  - [OpenAPI](#openapi)\n  - [An Exegesis Server](#an-exegesis-server)\n  - [The Controller](#the-controller)\n  - [Giving It a Try](#giving-it-a-try)\n\n<!-- /TOC -->\n<!-- markdownlint-enable MD007 -->\n\nThis is a tutorial which will teach you how to create an OpenAPI 3.0.3 document,\nand host it with Exegesis on node.js. You can find complete source for this\ntutorial in the [samples](https://github.com/exegesis-js/exegesis/tree/master/samples)\ndirectory, in both JavaScript and TypeScript.\n\nOpenAPI 3.0.3 is the successor to Swagger - version 2.0 was known as the Swagger Specification.\nWhile there are a few choices for implementing OpenAPI 2.0/Swagger on node.js,\nExegesis is the first complete server-framework for implementing version 3.0.X of the spec.\n\n## Project\n\nFirst, let's create the scaffold of our project:\n\n```sh\nmkdir exegesis-tutorial\ncd exegesis-tutorial\nmkdir controllers\nnpm init -y\nnpm install express exegesis-express\n```\n\nThis creates a project folder named \"exegesis-tutorial\", a sub-folder\nnamed \"controllers\" (where we'll put our controller implementations - the code\nthat gets run when someone accesses our API), creates a package.json file, and\ninstalls the dependencies we'll need (express and exegesis-express).\n\n## OpenAPI\n\nThe heart of any OpenAPI-based API is the OpenAPI document, which describes\nall the paths and parameters your application accepts. We'll store this\nin a file called \"openapi.yaml\". This is the simplest OpenAPI 3.0.3 document\nyou can write:\n\n```yaml\nopenapi: 3.0.3\ninfo:\n  title: My API\n  version: 1.0.0\npaths:\n```\n\nThe `openapi: 3.0.3` part tells us this is an OpenAPI document, and conforms\nto [version 3.0.3](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md)\nof the spec. There's a \"paths\" section where we can list all the paths our API\nexposes.\n\nThis has all the required fields to be an OpenAPI document, and will pass\nvalidation, but as API documents go, this one is pretty boring. It doesn't\nactually do anything. Let's fix that by adding a path:\n\n```yaml\nopenapi: 3.0.3\ninfo:\n  title: My API\n  version: 1.0.0\npaths:\n  '/greet':\n    get:\n      summary: Greets the user\n      operationId: getGreeting\n      x-exegesis-controller: greetController\n      parameters:\n        - description: The name of the user to greet.\n          name: name\n          in: query\n          required: true\n          schema:\n            type: string\n      responses:\n        200:\n          description: A greeting for the user.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message:\n                    type: string\n        default:\n          description: Unexpected error.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message:\n                    type: string\n```\n\nThat got big fast! Let's break this down into parts.\n\nWe've added a new \"/greet\" to our \"paths\" section, with a \"get\" operation:\n\n```yaml\npaths:\n  '/greet':\n    get:\n      summary: Greets the user\n      operationId: getGreeting\n      x-exegesis-controller: greetController\n```\n\nThis means clients can send an HTTP GET to \"/greet\" to run this operation. This\noperation also has an `operationId` of \"getGreeting\". This is a string that\nuniquely identifies this operation, across the entire document. No other\noperation is allowed to have this operationId.\n\nThere's also one extra special part we've added here, the \"x-exegesis-controller\".\nAnything that starts with an \"x-\" in an OpenAPI document is called a\n\"specification extension\". In this case, we're using an\n[Exegesis-specific extension](./OAS3%20Specification%20Extensions.md)\nto tell Exegesis what JS module contains the code for this controller.\n\nThe get operation also has one parameter, the \"name\" parameter:\n\n```yaml\nparameters:\n  - description: The name of the user to greet.\n    name: name\n    in: query\n    required: true\n    schema:\n      type: string\n```\n\nThis parameter must be present, and must be a string. Exegesis will generate\na validation error back to the client if this field isn't present, or is the\nwrong type.\n\nFinally, there's a responses section:\n\n```yaml\n200:\n  description: A greeting for the user.\n  content:\n    application/json:\n      schema:\n        type: object\n        required:\n          - message\n        properties:\n          message:\n            type: string\n```\n\nThis says we can reply with a 200 response, and if we do, the response is going\nto be a JSON object with a single property, \"message\". There's also a \"default\"\nresponse, which is what the client can expect if our response is not a 200\nresponse. You can list as many different response codes here as you wish.\n\n## An Exegesis Server\n\nNow that we have a simple OpenAPI document, we need to have a server which\nimplements it. Starting with the existing project, save the above OpenAPI\ndocument as \"openapi.yaml\". Then create an index.js file. This file is mostly\nboilerplate:\n\n```js\nconst express = require('express');\nconst exegesisExpress = require('exegesis-express');\nconst http = require('http');\nconst path = require('path');\n\nasync function createServer() {\n  // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md\n  const options = {\n    controllers: path.resolve(__dirname, 'controllers'),\n    allowMissingControllers: false,\n  };\n\n  // This creates an exegesis middleware, which can be used with express,\n  // connect, or even just by itself.\n  const exegesisMiddleware = await exegesisExpress.middleware(\n    path.resolve(__dirname, './openapi.yaml'),\n    options\n  );\n\n  const app = express();\n\n  // If you have any body parsers, this should go before them.\n  app.use(exegesisMiddleware);\n\n  // Return a 404\n  app.use((req, res) => {\n    res.status(404).json({ message: `Not found` });\n  });\n\n  // Handle any unexpected errors\n  app.use((err, req, res, next) => {\n    res.status(500).json({ message: `Internal error: ${err.message}` });\n  });\n\n  const server = http.createServer(app);\n\n  return server;\n}\n\ncreateServer()\n  .then(server => {\n    server.listen(3000);\n    console.log('Listening on port 3000');\n    console.log('Try visiting http://localhost:3000/greet?name=Jason');\n  })\n  .catch(err => {\n    console.error(err.stack);\n    process.exit(1);\n  });\n```\n\nThe interesting bit here is really the [options](./Options.md)\nwe pass to Exegesis:\n\n```js\nconst options = {\n  controllers: path.resolve(__dirname, 'controllers'),\n  allowMissingControllers: false,\n};\n```\n\n`controllers` gives the path to the \"controllers\" folder, where we store our\ncontroller implementations. `allowMissingControllers: false` tells Exegesis\nto throw an error at startup if any of our paths don't have a controller.\nThere are lots of other handy [options](./Options.md)\nyou can pass here. Note that if you want to enable response validation,\nyou must pass the [`onResponseValidationError`](./Options.md#onresponsevalidationerror)\noption.\n\n## The Controller\n\nNow we have an OpenAPI document, and we have the boilerplate that starts\nthe server, but the exciting part is the \"controller\" - this is the code\nthat actually implements our OpenAPI document. You may recall in our\nOpenAPI document we specified:\n\n```yaml\noperationId: getGreeting\nx-exegesis-controller: greetController\n```\n\nSo now were going to use the folder named \"controllers\" that was created when\nyou initially created the project. In that folder we're going to create a file\ncalled \"greetController.js\":\n\n```js\n// This function has the same name as an operationId in the OpenAPI document.\nexports.getGreeting = function getGreeting(context) {\n  const name = context.params.query.name;\n  return { message: `Hello ${name}` };\n};\n```\n\nThis controller is pretty simple - is just reads in the name parameter, and\nreturns a JSON object. Controllers can optionally return a Promise, take a\ncallback as a second parameter, or even just write a response directly\nto `context.res`.\n\nThe `context` variable here is an [\"Exegesis Context\"](./Exegesis%20Controllers.md),\nwhich contains lots of [helpful info](./Exegesis%20Controllers.md#whats-in-a-context)\nfor when you're writing a controller.\n\n## Giving It a Try\n\nStart the server with:\n\n```sh\nnode index.js\n```\n\nThen, try pointing your browser at [http://localhost:3000/greet?name=Jason](http://localhost:3000/greet?name=Jason),\nand you should see a greeting!\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"exegesis\",\n    \"version\": \"0.0.0-semantic-release\",\n    \"description\": \"Parses OpenAPI documents\",\n    \"main\": \"lib/index.js\",\n    \"types\": \"lib/index.d.ts\",\n    \"files\": [\n        \"lib/**/*\"\n    ],\n    \"scripts\": {\n        \"test\": \"npm run build && npm run lint && npm run test:unittest\",\n        \"test:pre-commit\": \"pretty-quick --staged && npm run build && npm run lint && npm run test:unittest-pc\",\n        \"build\": \"tsc\",\n        \"clean\": \"rm -rf lib coverage\",\n        \"test:unittest\": \"tsc -p test && nyc mocha 'test/**/*.@(ts|js)'\",\n        \"test:unittest-pc\": \"tsc -p test && mocha --reporter progress 'test/**/*.@(ts|js)'\",\n        \"lint\": \"npm run lint:source && npm run lint:tests\",\n        \"lint:source\": \"eslint --ext .ts src\",\n        \"lint:tests\": \"eslint --ext .ts test\",\n        \"prepare\": \"husky install && npm run build\",\n        \"prepublishOnly\": \"npm run build && npm test\",\n        \"semantic-release\": \"semantic-release\"\n    },\n    \"lint-staged\": {\n        \"(src/test)/**/*.(js|jsx|ts|tsx)\": [\n            \"eslint\"\n        ]\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/exegesis-js/exegesis.git\"\n    },\n    \"keywords\": [\n        \"OpenAPI\",\n        \"swagger\",\n        \"OAS3\"\n    ],\n    \"author\": \"Jason Walton\",\n    \"license\": \"MIT\",\n    \"bugs\": {\n        \"url\": \"https://github.com/exegesis-js/exegesis/issues\"\n    },\n    \"homepage\": \"https://github.com/exegesis-js/exegesis#readme\",\n    \"devDependencies\": {\n        \"@jwalton/semantic-release-config\": \"^1.0.0\",\n        \"@types/body-parser\": \"^1.16.8\",\n        \"@types/chai\": \"^4.1.7\",\n        \"@types/chai-as-promised\": \"^7.1.0\",\n        \"@types/content-type\": \"^1.1.3\",\n        \"@types/deep-freeze\": \"^0.1.1\",\n        \"@types/json-schema\": \"^7.0.3\",\n        \"@types/lodash\": \"^4.14.132\",\n        \"@types/mocha\": \"^10.0.0\",\n        \"@types/node\": \"^18.7.23\",\n        \"@types/qs\": \"^6.5.1\",\n        \"@types/semver\": \"^7.1.0\",\n        \"@typescript-eslint/eslint-plugin\": \"^7.3.1\",\n        \"@typescript-eslint/parser\": \"^7.3.1\",\n        \"chai\": \"^4.2.0\",\n        \"chai-as-promised\": \"^7.1.1\",\n        \"eslint\": \"^8.57.0\",\n        \"husky\": \"^8.0.1\",\n        \"lint-staged\": \"^13.0.3\",\n        \"mocha\": \"^10.1.0\",\n        \"nyc\": \"^15.1.0\",\n        \"prettier\": \"^2.0.5\",\n        \"pretty-quick\": \"^3.0.0\",\n        \"semantic-release\": \"^21.0.1\",\n        \"supertest-fetch\": \"^1.2.2\",\n        \"ts-node\": \"^10.4.0\",\n        \"typescript\": \"^5.0.3\"\n    },\n    \"dependencies\": {\n        \"@apidevtools/json-schema-ref-parser\": \"^9.0.3\",\n        \"ajv\": \"^8.3.0\",\n        \"ajv-formats\": \"^2.1.0\",\n        \"body-parser\": \"^1.18.3\",\n        \"content-type\": \"^1.0.4\",\n        \"deep-freeze\": \"0.0.1\",\n        \"events-listener\": \"^1.1.0\",\n        \"glob\": \"^10.3.10\",\n        \"json-ptr\": \"^3.0.1\",\n        \"json-schema-traverse\": \"^1.0.0\",\n        \"lodash\": \"^4.17.11\",\n        \"openapi3-ts\": \"^3.1.1\",\n        \"promise-breaker\": \"^6.0.0\",\n        \"qs\": \"^6.6.0\",\n        \"raw-body\": \"^2.3.3\",\n        \"semver\": \"^7.0.0\"\n    },\n    \"engines\": {\n        \"node\": \">=10.0.0\",\n        \"npm\": \">5.0.0\"\n    }\n}\n"
  },
  {
    "path": "samples/javascript-example/controllers/greetController.js",
    "content": "exports.getGreeting = function getGreeting(context) {\n    const name = context.params.query.name;\n    return {message: `Hello ${name}`};\n}\n"
  },
  {
    "path": "samples/javascript-example/index.js",
    "content": "const express = require('express');\nconst exegesisExpress = require('exegesis-express');\nconst http = require('http');\nconst path = require('path');\n\nasync function createServer() {\n    // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md\n    const options = {\n        controllers: path.resolve(__dirname, './controllers'),\n        allowMissingControllers: false\n    };\n\n    // This creates an exegesis middleware, which can be used with express,\n    // connect, or even just by itself.\n    const exegesisMiddleware = await exegesisExpress.middleware(\n        path.resolve(__dirname, './openapi.yaml'),\n        options\n    );\n\n    const app = express();\n\n    // If you have any body parsers, this should go before them.\n    app.use(exegesisMiddleware);\n\n    // Return a 404\n    app.use((req, res) => {\n        res.status(404).json({message: `Not found`});\n    });\n\n    // Handle any unexpected errors\n    app.use((err, req, res, next) => {\n        res.status(500).json({message: `Internal error: ${err.message}`});\n    });\n\n    const server = http.createServer(app);\n\n    return server;\n}\n\ncreateServer()\n.then(server => {\n    server.listen(3000);\n    console.log(\"Listening on port 3000\");\n    console.log(\"Try visiting http://localhost:3000/greet?name=Jason\");\n})\n.catch(err => {\n    console.error(err.stack);\n    process.exit(1);\n});\n"
  },
  {
    "path": "samples/javascript-example/openapi.yaml",
    "content": "openapi: 3.0.1\ninfo:\n  title: My API\n  version: 1.0.0\npaths:\n  '/greet':\n    get:\n      summary: Greets the user\n      operationId: getGreeting\n      x-exegesis-controller: greetController\n      parameters:\n        - description: The name of the user to greet.\n          name: name\n          in: query\n          required: true\n          schema:\n            type: string\n      responses:\n        200:\n          description: A greeting for the user.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message:\n                    type: string\n        default:\n          description: Unexpected error.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message:\n                    type: string\n"
  },
  {
    "path": "samples/javascript-example/package.json",
    "content": "{\n  \"name\": \"exegesis-tutorial\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Exegesis tutorial sample\",\n  \"main\": \"index.js\",\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"exegesis-express\": \"^1.0.0\",\n    \"express\": \"^4.16.3\"\n  }\n}\n"
  },
  {
    "path": "samples/typescript-example/README.md",
    "content": "#Exegesis-Typescript-Example\n\nA sample setup of a Typescript based API which uses Exegesis to work with the OpenAPI spec v3.\n\nI made this because on the exegesis repo there is a sample setup however there is only a javascript version available. \n\n# Usage\nClone the repo and cd into the working directory :\n\n```\ngit clone https://github.com/Ryan-Gordon/exegesis-typescript-example.git \n\n```\n\nThen install the dependancies with `npm install`. Yarn is okay too!\n\nLastly run the API using :  \n\n```\nnpm run dev\n```\n\nPlease submit an issue if you have any problems, head over to the [Exegesis Repo](https://github.com/exegesis-js/exegesis) for other examples."
  },
  {
    "path": "samples/typescript-example/controllers/greetController.ts",
    "content": "/**\n * Here a simple function is exported.\n * \n * If you wanted a class with its own state and functions you will need something like:\n * \n * class GreetController {\n * \n * }\n * export default GreetController;\n */\n\n//Export the getGreeting function to be used by the API.\n//All controllers will be imported meaning this exported function will be put into scope.\nexports.getGreeting = function getGreeting(context) {\n    const name = context.params.query.name;\n    return {message: `Hello ${name}`};\n}"
  },
  {
    "path": "samples/typescript-example/openapi.yaml",
    "content": "openapi: 3.0.1\ninfo:\n  title: My API\n  version: 1.0.0\npaths:\n  '/greet':\n    get:\n      summary: Greets the user\n      operationId: getGreeting\n      x-exegesis-controller: greetController\n      parameters:\n        - description: The name of the user to greet.\n          name: name\n          in: query\n          required: true\n          schema:\n            type: string\n      responses:\n        200:\n          description: A greeting for the user.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message:\n                    type: string\n    default:\n      description: Unexpected error.\n      content:\n        application/json:\n          schema:\n            type: object\n            required:\n              - message\n            properties:\n              message:\n                type: string"
  },
  {
    "path": "samples/typescript-example/package.json",
    "content": "{\n  \"name\": \"exegesis-typescript-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"dev\": \"ts-node ./server.ts\",\n    \"start\": \"nodemon ./dist/server.js\",\n    \"prod\": \"npm run build && npm run start\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@types/express\": \"^4.16.0\",\n    \"exegesis-express\": \"^1.0.0\",\n    \"express\": \"^4.16.3\"\n  }\n}\n"
  },
  {
    "path": "samples/typescript-example/server.ts",
    "content": "//Import librarys used\nimport * as express from 'express';\nimport * as exegesisExpress from 'exegesis-express';\nimport * as path from 'path';\n\n//You may choose HTTP or HTTPS, if HTTPS you need a SSL Cert\nimport * as http from 'http';\nimport * as https from 'https';\n\nconst PORT = 3000;\n\n\n\nasync function createServer() {\n    // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md\n    const options = {\n        controllers: path.resolve(__dirname, './controllers'),\n        controllersPattern: \"**/*.@(ts|js)\"\n    };\n\n    // This creates an exegesis middleware, which can be used with express,\n    // connect, or even just by itself.\n    const exegesisMiddleware = await exegesisExpress.middleware(\n        path.resolve(__dirname, './openapi.yaml'),\n        options\n    );\n\n    const app = express();\n\n    // If you have any body parsers, this should go before them.\n    app.use(exegesisMiddleware);\n\n    // Return a 404\n    app.use((req, res) => {\n        res.status(404).json({message: `Not found`});\n    });\n\n    // Handle any unexpected errors\n    app.use((err, req, res, next) => {\n        res.status(500).json({message: `Internal error: ${err.message}`});\n    });\n    //Used to create a HTTP Server\n    const server = http.createServer(app);\n\n    /**\n     * If you want to run a HTTPS server instead you must:\n     * + Get a SSL Cert and Key to use\n     * + Change the server type from http to https as shown below\n     *\n\n    const httpsOptions = {\n        key: fs.readFileSync('./config/key.pem'),\n        cert: fs.readFileSync('./config/cert.pem')\n    }\n    const server = https.createServer(httpsOptions,app);\n\n    */\n    return server;\n}\n//Run our createServer function\ncreateServer()\n.then(server => {\n    server.listen(PORT);\n    console.log(\"Listening on port 3000\");\n    console.log(\"Try visiting http://localhost:3000/greet?name=Jason\");\n})\n.catch(err => {\n    console.error(err.stack);\n    process.exit(1);\n});\n"
  },
  {
    "path": "samples/typescript-example/tsconfig.json",
    "content": "// tsconfig.json\n{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"moduleResolution\": \"node\",\n        \"pretty\": true,\n        \"sourceMap\": true,\n        \"target\": \"es6\",\n        \"outDir\": \"./dist\",\n        \"baseUrl\": \"./\"\n    },\n    \"include\": [\n        \"lib/**/*.ts\"\n    ],\n    \"exclude\": [\n        \"node_modules\"\n    ]\n}"
  },
  {
    "path": "src/bodyParsers/BodyParserWrapper.ts",
    "content": "import http from 'http';\nimport contentType from 'content-type';\nimport getRawBody from 'raw-body';\n\nimport { httpHasBody } from '../utils/httpUtils';\nimport { MimeTypeParser, StringParser, HttpIncomingMessage, Callback } from '../types';\n\nexport default class BodyParserWrapper implements MimeTypeParser {\n    private _parser: StringParser;\n    private _maxBodySize: number;\n\n    constructor(parser: StringParser, maxBodySize: number) {\n        this._parser = parser;\n        this._maxBodySize = maxBodySize;\n    }\n\n    parseString(value: string) {\n        return this._parser.parseString(value);\n    }\n\n    parseReq(req: HttpIncomingMessage, _res: http.ServerResponse, done: Callback<any>): void {\n        if (req.body) {\n            // Already parsed;\n            return done();\n        }\n\n        // Make sure we have a body to parse\n        if (!httpHasBody(req.headers)) {\n            return done();\n        }\n\n        // Work out the encoding\n        let encoding = 'utf-8';\n        const parsedContentType = contentType.parse(req);\n        if (parsedContentType && parsedContentType.parameters) {\n            encoding = (parsedContentType.parameters.encoding || encoding).toLowerCase();\n        }\n\n        // Read the body\n        getRawBody(req, { limit: this._maxBodySize, encoding }, (err, str) => {\n            if (err) {\n                return done(err);\n            }\n\n            req.body = this._parser.parseString(str);\n            done(null, req.body);\n        });\n    }\n}\n"
  },
  {
    "path": "src/bodyParsers/JsonBodyParser.ts",
    "content": "import http from 'http';\nimport expressBodyParser from 'body-parser';\n\nimport { MimeTypeParser, ReqParserFunction, Callback } from '../types';\n\nexport default class JsonBodyParser implements MimeTypeParser {\n    private _bodyParserMiddlware: ReqParserFunction;\n\n    constructor(maxBodySize: number) {\n        // FIXME: https://github.com/expressjs/body-parser/issues/304\n        this._bodyParserMiddlware = expressBodyParser.json({\n            inflate: true,\n            limit: maxBodySize,\n            type: '*/*',\n        }) as ReqParserFunction;\n    }\n\n    parseString(value: string) {\n        return JSON.parse(value);\n    }\n\n    parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Callback<void>): void {\n        this._bodyParserMiddlware(req, res, done);\n    }\n}\n"
  },
  {
    "path": "src/bodyParsers/TextBodyParser.ts",
    "content": "import http from 'http';\nimport expressBodyParser from 'body-parser';\n\nimport { MimeTypeParser, ReqParserFunction, Callback } from '../types';\n\nexport default class TextBodyParser implements MimeTypeParser {\n    private _bodyParserMiddlware: ReqParserFunction;\n\n    constructor(maxBodySize: number) {\n        // FIXME: https://github.com/expressjs/body-parser/issues/304\n        this._bodyParserMiddlware = expressBodyParser.text({\n            inflate: true,\n            limit: maxBodySize,\n            type: '*/*',\n        }) as ReqParserFunction;\n    }\n\n    parseString(value: string) {\n        return value;\n    }\n\n    parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Callback<void>): void {\n        this._bodyParserMiddlware(req, res, done);\n    }\n}\n"
  },
  {
    "path": "src/controllers/invoke.ts",
    "content": "import pb from 'promise-breaker';\nimport { Controller, ControllerModule, ExegesisContext } from '../types';\nimport { isReadable } from '../utils/typeUtils';\n\nexport function invokeController(\n    controllerModule: ControllerModule | undefined,\n    controller: Controller,\n    context: ExegesisContext\n): Promise<any> {\n    return pb.apply(controller, controllerModule, [context]).then((result) => {\n        if (!context.res.ended) {\n            if (result === undefined || result === null) {\n                context.res.end();\n            } else if (\n                typeof result === 'string' ||\n                result instanceof Buffer ||\n                isReadable(result)\n            ) {\n                context.res.setBody(result);\n            } else if (context.options.treatReturnedJsonAsPure) {\n                context.res.pureJson(result);\n            } else {\n                context.res.json(result);\n            }\n        }\n\n        return result;\n    });\n}\n"
  },
  {
    "path": "src/controllers/loadControllers.ts",
    "content": "import fs from 'fs';\nimport * as glob from 'glob';\nimport path from 'path';\n\nimport { Controllers, ControllerModule } from '../types';\n\n/**\n * Load a set of controllers.\n *\n * @param folder - The folder to load controllers from.\n * @param [pattern] - A glob pattern for controllers to load.  Defaults to only\n *   .js files.\n * @param [loader] - The function to call to load each controller.  Defaults to\n *   `require`.\n *\n * @example\n *   // Assuming controllers has files \"foo.js\" and \"bar/bar.js\", then `controllers`\n *   // will be a `{\"foo\", \"foo.js\", \"bar/bar.js\", \"bar/bar\"}` object.\n *   const controllers = loadControllersSync('controlers', '**\\/*.js');\n */\nexport function loadControllersSync(\n    folder: string,\n    pattern: string = '**/*.js',\n    loader: (path: string) => ControllerModule = require\n): Controllers {\n    const controllerNames = glob.sync(pattern, { cwd: folder });\n\n    return controllerNames.reduce<Controllers>((result, controllerName) => {\n        const fullPath = path.resolve(folder, controllerName);\n        if (fs.statSync(fullPath).isDirectory()) {\n            // Skip directories.\n            return result;\n        }\n        try {\n            // Add the file at the full path\n            const mod = loader(fullPath);\n            result[controllerName] = mod;\n\n            // Add the file at the full path, minus the extension\n            const ext = path.extname(controllerName);\n            result[controllerName.slice(0, -ext.length)] = mod;\n\n            // If the file is an \"index\" file, then add it at the folder\n            // name (unless there's already something there.)\n            const basename = path.basename(controllerName, ext);\n            if (basename === 'index') {\n                const indexFolder = controllerName.slice(0, -(ext.length + basename.length + 1));\n                result[indexFolder] = result[indexFolder] || mod;\n            }\n        } catch (err) {\n            throw new Error(`Could not load controller '${fullPath}': ${err}`);\n        }\n        return result;\n    }, {});\n}\n"
  },
  {
    "path": "src/core/ExegesisContextImpl.ts",
    "content": "import * as http from 'http';\nimport pb from 'promise-breaker';\nimport deepFreeze from 'deep-freeze';\nimport {\n    ParametersByLocation,\n    ParametersMap,\n    ExegesisContext,\n    AuthenticationSuccess,\n    HttpIncomingMessage,\n    ExegesisPluginContext,\n    Callback,\n    ParameterLocations,\n    ParameterLocation,\n    ExegesisOptions,\n    ResolvedOperation,\n    ExegesisRoute,\n} from '../types';\nimport ExegesisResponseImpl from './ExegesisResponseImpl';\nimport { HttpError, ValidationError } from '../errors';\n\nconst EMPTY_PARAMS = deepFreeze({\n    query: Object.create(null),\n    header: Object.create(null),\n    server: Object.create(null),\n    path: Object.create(null),\n    cookie: Object.create(null),\n});\n\nconst EMPTY_PARAM_LOCATIONS: ParameterLocations = deepFreeze<ParameterLocations>({\n    query: Object.create(null),\n    header: Object.create(null),\n    path: Object.create(null),\n    cookie: Object.create(null),\n});\n\nconst EMPTY_ROUTE = deepFreeze({\n    path: '',\n});\n\nexport default class ExegesisContextImpl<T> implements ExegesisContext, ExegesisPluginContext {\n    readonly req: HttpIncomingMessage;\n    readonly origRes: http.ServerResponse;\n    readonly res: ExegesisResponseImpl;\n    readonly options: ExegesisOptions;\n    params: ParametersByLocation<ParametersMap<any>>;\n    requestBody: any;\n    security?: { [scheme: string]: AuthenticationSuccess };\n    user: any | undefined;\n    api: T;\n    parameterLocations: ParameterLocations = EMPTY_PARAM_LOCATIONS;\n    route: ExegesisRoute = EMPTY_ROUTE;\n    baseUrl: string = '';\n\n    private _operation: ResolvedOperation | undefined;\n    private _paramsResolved: boolean = false;\n    private _bodyResolved: boolean = false;\n\n    constructor(\n        req: http.IncomingMessage, // http2.Http2ServerRequest,\n        res: http.ServerResponse, // http2.Http2ServerResponse,\n        api: T,\n        options: ExegesisOptions\n    ) {\n        const responseValidationEnabled = !!options.onResponseValidationError;\n        this.req = req as HttpIncomingMessage;\n        this.origRes = res;\n        this.res = new ExegesisResponseImpl(res, responseValidationEnabled);\n        this.api = api;\n        this.options = options;\n\n        // Temporarily set params to EMPTY_PARAMS.  While we're being a\n        // 'plugin context', this will be empty, but it will be filled in\n        // before we get to the controllers.\n        this.params = EMPTY_PARAMS;\n    }\n\n    _setOperation(baseUrl: string, path: string, operation: ResolvedOperation) {\n        this.baseUrl = baseUrl;\n        this.route = { path };\n        this._operation = operation;\n        this.parameterLocations = operation.parameterLocations;\n\n        // Set `req.baseUrl` and `req.path` to make this behave like Express.\n        const req = this.req as any;\n        if (req.baseUrl) {\n            req.baseUrl = `${req.baseUrl}${baseUrl}`;\n        } else {\n            req.baseUrl = baseUrl;\n        }\n        req.route = { path };\n    }\n\n    makeError(statusCode: number, message: string): HttpError {\n        return new HttpError(statusCode, message);\n    }\n\n    makeValidationError(message: string, parameterLocation: ParameterLocation) {\n        return new ValidationError([{ message, location: parameterLocation }]);\n    }\n\n    /**\n     * Returns true if the response has already been sent.\n     */\n    isResponseFinished() {\n        return this.res.ended || this.origRes.headersSent;\n    }\n\n    getParams(): Promise<ParametersByLocation<ParametersMap<any>>>;\n    getParams(done: Callback<ParametersByLocation<ParametersMap<any>>>): void;\n    getParams(done?: Callback<any>): Promise<ParametersByLocation<ParametersMap<any>>> | void {\n        return pb.addCallback(done, () => {\n            if (!this._paramsResolved) {\n                if (!this._operation) {\n                    throw new Error('Cannot get parameters - no resolved operation.');\n                }\n                this.params = this._operation.parseParameters();\n                const errors = this._operation.validateParameters(this.params);\n                if (errors && errors.length > 0) {\n                    const err = new ValidationError(errors);\n                    throw err;\n                }\n                this._paramsResolved = true;\n            }\n            return this.params;\n        });\n    }\n\n    getRequestBody(): Promise<any>;\n    getRequestBody(done: Callback<any>): void;\n    getRequestBody(done?: Callback<any>): Promise<any> | void {\n        return pb.addCallback(done, async () => {\n            if (!this._operation) {\n                throw new Error('Cannot get parameters - no resolved operation.');\n            }\n\n            if (!this._bodyResolved) {\n                let body: any;\n\n                // Parse the body.\n                if (this._operation.bodyParser) {\n                    const bodyParser = this._operation.bodyParser;\n                    body = await pb.call((done: Callback<void>) =>\n                        bodyParser.parseReq(this.req, this.origRes, done)\n                    );\n                    body = body || this.req.body;\n                }\n\n                // Validate the body.  We need to validate the body even if we\n                // didn't parse a body, since this is where we check if the\n                // body is required.\n                if (this._operation.validateBody) {\n                    const validationResult = this._operation.validateBody(body);\n                    if (validationResult.errors && validationResult.errors.length > 0) {\n                        throw new ValidationError(validationResult.errors);\n                    }\n\n                    body = validationResult.value;\n                }\n\n                // Assign the body to the appropriate places\n                this.requestBody = this.req.body = body;\n                this._bodyResolved = true;\n            }\n            return this.requestBody;\n        });\n    }\n}\n"
  },
  {
    "path": "src/core/ExegesisResponseImpl.ts",
    "content": "import * as http from 'http';\nimport * as net from 'net';\nimport * as types from '../types';\nimport { HttpHeaders } from '../types';\n\nexport default class ExegesisResponseImpl implements types.ExegesisResponse {\n    private _body: any = undefined;\n    _afterController: boolean = false;\n\n    statusCode: number = 200;\n    statusMessage: string | undefined = undefined;\n    headers: types.HttpHeaders = Object.create(null);\n    ended: boolean = false;\n    connection: net.Socket;\n    socket: net.Socket;\n    headersSent: boolean = false;\n    private _responseValidationEnabled: boolean;\n\n    constructor(\n        res: http.ServerResponse /* | http2.Http2ServerResponse */,\n        responseValidationEnabled: boolean\n    ) {\n        if (!res.socket) {\n            throw new Error('Response is already ended');\n        }\n        this.connection = this.socket = res.socket;\n        this._responseValidationEnabled = responseValidationEnabled;\n    }\n\n    setStatus(status: number) {\n        if (this.ended) {\n            throw new Error('Trying to set status after response has been ended.');\n        }\n        this.statusCode = status;\n        return this;\n    }\n\n    status(status: number) {\n        return this.setStatus(status);\n    }\n\n    header(header: string, value: number | string | string[]) {\n        this.setHeader(header, value);\n        return this;\n    }\n\n    set(header: string, value: number | string | string[]) {\n        this.setHeader(header, value);\n        return this;\n    }\n\n    json(json: any) {\n        this.set('content-type', 'application/json');\n        if (this._responseValidationEnabled) {\n            // Must stringify here, since the object or any of it's\n            // nested values could have a toJSON().  Note this means\n            // we'll have to parse it again when we do validation.\n            this.setBody(JSON.stringify(json));\n        } else {\n            this.setBody(json);\n        }\n        return this;\n    }\n\n    pureJson(json: any) {\n        this.set('content-type', 'application/json').setBody(json);\n        return this;\n    }\n\n    setBody(body: any): this {\n        if (this.ended && !this._afterController) {\n            throw new Error('Trying to set body after response has been ended.');\n        }\n        this.body = body;\n        return this;\n    }\n\n    set body(body: any) {\n        this._body = body;\n        this.end();\n    }\n\n    get body(): any {\n        return this._body;\n    }\n\n    end() {\n        this.headersSent = true;\n        this.ended = true;\n    }\n\n    redirect(status: number, url: string): this;\n    redirect(url: string): this;\n    redirect(a: number | string, b?: string): this {\n        if (typeof a === 'string' && !b) {\n            this.writeHead(302, { Location: a });\n        } else if (typeof a === 'number' && typeof b === 'string') {\n            this.writeHead(a, { Location: b });\n        } else {\n            throw new Error('Invalid arguments to redirect');\n        }\n        this.end();\n\n        return this;\n    }\n\n    setHeader(name: string, value: number | string | string[]) {\n        if (this.ended && !this._afterController) {\n            throw new Error('Trying to set header after response has been ended.');\n        }\n        this.headers[name.toLowerCase()] = value;\n    }\n\n    getHeader(name: string) {\n        return this.headers[name];\n    }\n\n    getHeaderNames() {\n        return Object.keys(this.headers);\n    }\n\n    getHeaders() {\n        return Object.assign({}, this.headers);\n    }\n\n    hasHeader(name: string) {\n        return !!this.headers[name];\n    }\n\n    removeHeader(name: string) {\n        if (this.ended && !this._afterController) {\n            throw new Error('Trying to remove header after response has been ended.');\n        }\n        delete this.headers[name];\n    }\n\n    writeHead(statusCode: number, statusMessage?: string | HttpHeaders, headers?: HttpHeaders) {\n        if (statusMessage && typeof statusMessage !== 'string') {\n            headers = statusMessage;\n            statusMessage = undefined;\n        }\n        this.statusCode = statusCode;\n\n        if (headers) {\n            for (const headerName of Object.keys(headers)) {\n                this.setHeader(headerName, headers[headerName]);\n            }\n        }\n        this.headersSent = true;\n    }\n}\n"
  },
  {
    "path": "src/core/PluginsManager.ts",
    "content": "import * as exegesis from '../types';\nimport pb from 'promise-breaker';\nimport http from 'http';\n\nfunction callFn(\n    plugin: exegesis.ExegesisPluginInstance,\n    fnName: keyof exegesis.ExegesisPluginInstance,\n    param: any\n) {\n    const fnLength = (plugin as any)[fnName].length;\n    if (fnLength < 2) {\n        return (plugin as any)[fnName](param);\n    } else {\n        pb.call((done: exegesis.Callback<void>) => (plugin as any)[fnName](param, done));\n    }\n}\n\nexport default class PluginsManager {\n    private readonly _plugins: exegesis.ExegesisPluginInstance[];\n    private readonly _preRoutingPlugins: exegesis.ExegesisPluginInstance[];\n    private readonly _postRoutingPlugins: exegesis.ExegesisPluginInstance[];\n    private readonly _postSecurityPlugins: exegesis.ExegesisPluginInstance[];\n    private readonly _postControllerPlugins: exegesis.ExegesisPluginInstance[];\n    private readonly _postResponseValidation: exegesis.ExegesisPluginInstance[];\n\n    constructor(apiDoc: any, plugins: exegesis.ExegesisPlugin[]) {\n        this._plugins = plugins.map((plugin) => plugin.makeExegesisPlugin({ apiDoc }));\n\n        this._preRoutingPlugins = this._plugins.filter((p) => !!p.preRouting);\n        this._postRoutingPlugins = this._plugins.filter((p) => !!p.postRouting);\n        this._postSecurityPlugins = this._plugins.filter((p) => !!p.postSecurity);\n        this._postControllerPlugins = this._plugins.filter((p) => !!p.postController);\n        this._postResponseValidation = this._plugins.filter((p) => !!p.postResponseValidation);\n    }\n\n    async preCompile(data: { apiDoc: any; options: exegesis.ExegesisOptions }) {\n        for (const plugin of this._plugins) {\n            if (plugin.preCompile) {\n                await callFn(plugin, 'preCompile', data);\n            }\n        }\n    }\n\n    async preRouting(data: { req: http.IncomingMessage; res: http.ServerResponse }) {\n        for (const plugin of this._preRoutingPlugins) {\n            await callFn(plugin, 'preRouting', data);\n        }\n    }\n\n    async postRouting(pluginContext: exegesis.ExegesisPluginContext) {\n        for (const plugin of this._postRoutingPlugins) {\n            await callFn(plugin, 'postRouting', pluginContext);\n        }\n    }\n\n    async postSecurity(pluginContext: exegesis.ExegesisPluginContext) {\n        for (const plugin of this._postSecurityPlugins) {\n            await callFn(plugin, 'postSecurity', pluginContext);\n        }\n    }\n\n    async postController(context: exegesis.ExegesisContext) {\n        for (const plugin of this._postControllerPlugins) {\n            await callFn(plugin, 'postController', context);\n        }\n    }\n\n    async postResponseValidation(context: exegesis.ExegesisContext) {\n        for (const plugin of this._postResponseValidation) {\n            await callFn(plugin, 'postResponseValidation', context);\n        }\n    }\n}\n"
  },
  {
    "path": "src/core/exegesisRunner.ts",
    "content": "import * as http from 'http';\nimport { Readable } from 'stream';\nimport { asError, HttpError } from '../errors';\n\nimport { invokeController } from '../controllers/invoke';\nimport stringToStream from '../utils/stringToStream';\nimport { ValidationError } from '../errors';\nimport bufferToStream from '../utils/bufferToStream';\nimport { isReadable } from '../utils/typeUtils';\nimport {\n    ApiInterface,\n    ExegesisRunner,\n    HttpResult,\n    ExegesisContext,\n    ResponseValidationCallback,\n    ResolvedOperation,\n    ExegesisOptions,\n    ExegesisResponse,\n} from '../types';\nimport ExegesisContextImpl from './ExegesisContextImpl';\nimport PluginsManager from './PluginsManager';\nimport { IValidationError } from '../types/validation';\nimport { HandleErrorFunction } from '../types/options';\n\nasync function handleSecurity(operation: ResolvedOperation, context: ExegesisContext) {\n    const authenticated = await operation.authenticate(context);\n    context.security = authenticated;\n    if (authenticated) {\n        const matchedSchemes = Object.keys(authenticated);\n        if (matchedSchemes.length === 1) {\n            context.user = authenticated[matchedSchemes[0]].user;\n        }\n    }\n}\n\nfunction setDefaultContentType(res: ExegesisResponse) {\n    const body = res.body;\n    if (res.headers['content-type']) {\n        // Nothing to do!\n    } else if (body === undefined || body === null) {\n        // Do nothing\n    } else if (body instanceof Buffer) {\n        res.headers['content-type'] = 'text/plain';\n    } else if (typeof body === 'string') {\n        res.headers['content-type'] = 'text/plain';\n    } else if (isReadable(body)) {\n        res.headers['content-type'] = 'text/plain';\n    } else {\n        res.headers['content-type'] = 'application/json';\n    }\n}\n\nfunction resultToHttpResponse(context: ExegesisContext, result: any): HttpResult {\n    let output: Readable | undefined;\n    const headers = context.res.headers;\n\n    if (result) {\n        if (result instanceof Buffer) {\n            output = bufferToStream(result);\n        } else if (typeof result === 'string') {\n            output = stringToStream(result);\n        } else if (isReadable(result)) {\n            output = result;\n        } else {\n            if (!headers['content-type']) {\n                headers['content-type'] = 'application/json';\n            }\n            output = stringToStream(JSON.stringify(result), 'utf-8');\n        }\n    }\n\n    return {\n        status: context.res.statusCode,\n        headers,\n        body: output,\n    };\n}\n\nfunction handleError(err: Error) {\n    if (err instanceof ValidationError) {\n        // TODO: Allow customization of validation error?  Or even\n        // just throw the error instead of turning it into a message?\n        const jsonError = {\n            message: 'Validation errors',\n            errors: err.errors.map((error: IValidationError) => {\n                return {\n                    message: error.message,\n                    location: error.location,\n                };\n            }),\n        };\n        return {\n            status: err.status,\n            headers: { 'content-type': 'application/json' },\n            body: stringToStream(JSON.stringify(jsonError), 'utf-8'),\n        };\n    } else if (Number.isInteger((err as any).status)) {\n        return {\n            status: (err as any).status,\n            headers: (err as any).headers || { 'content-type': 'application/json' },\n            body: stringToStream(JSON.stringify({ message: err.message }), 'utf-8'),\n        };\n    } else {\n        throw err;\n    }\n}\n\n/**\n * Returns a `(req, res) => Promise<boolean>` function, which handles incoming\n * HTTP requests.  The returned function will return true if the request was\n * handled, and false otherwise.\n *\n * @returns runner function.\n */\nexport default async function generateExegesisRunner<T>(\n    api: ApiInterface<T>,\n    options: {\n        autoHandleHttpErrors: boolean | HandleErrorFunction;\n        plugins: PluginsManager;\n        onResponseValidationError?: ResponseValidationCallback;\n        validateDefaultResponses: boolean;\n        originalOptions: ExegesisOptions;\n    }\n): Promise<ExegesisRunner> {\n    const plugins = options.plugins;\n\n    return async function exegesisRunner(\n        req: http.IncomingMessage,\n        res: http.ServerResponse\n    ): Promise<HttpResult | undefined> {\n        const method = req.method || 'get';\n        const url = req.url || '/';\n\n        let result: HttpResult | undefined;\n\n        try {\n            await plugins.preRouting({ req, res });\n\n            const resolved = api.resolve(method, url, req.headers);\n            if (!resolved) {\n                return result;\n            }\n\n            if (!resolved.operation) {\n                const error: any = new Error(`Method ${method} not allowed for ${url}`);\n                error.status = 405;\n                error.headers = {\n                    allow: resolved.allowedMethods.join(',').toUpperCase(),\n                    'content-type': 'application/json',\n                };\n\n                return handleError(error);\n            }\n\n            const context = new ExegesisContextImpl<T>(\n                req,\n                res,\n                resolved.api,\n                options.originalOptions\n            );\n\n            if (!context.isResponseFinished()) {\n                await plugins.postRouting(context);\n            }\n\n            const { operation } = resolved;\n\n            context._setOperation(resolved.baseUrl, resolved.path, operation);\n\n            if (!operation.controller) {\n                throw new Error(`No controller found for ${method} ${url}`);\n            }\n\n            await handleSecurity(operation, context);\n\n            if (!context.isResponseFinished()) {\n                await plugins.postSecurity(context);\n            }\n\n            if (!context.isResponseFinished()) {\n                // Fill in context.params and context.requestBody.\n                await context.getParams();\n                await context.getRequestBody();\n            }\n\n            if (!context.isResponseFinished()) {\n                await invokeController(operation.controllerModule, operation.controller, context);\n            }\n\n            if (!context.origRes.headersSent) {\n                // Set _afterController to allow postController() plugins to\n                // modify the response.\n                context.res._afterController = true;\n                await plugins.postController(context);\n            }\n\n            if (!context.origRes.headersSent) {\n                // Before response validation, if there is a body and no\n                // content-type has been set, set a reasonable default.\n                setDefaultContentType(context.res);\n\n                if (options.onResponseValidationError) {\n                    const responseValidationResult = resolved.operation.validateResponse(\n                        context.res,\n                        options.validateDefaultResponses\n                    );\n                    try {\n                        if (\n                            responseValidationResult.errors &&\n                            responseValidationResult.errors.length\n                        ) {\n                            options.onResponseValidationError({\n                                errors: responseValidationResult.errors,\n                                isDefault: responseValidationResult.isDefault,\n                                context,\n                            });\n                        }\n                    } catch (e) {\n                        const err = asError(e) as HttpError;\n                        (err as any).status = err.status || 500;\n                        throw err;\n                    }\n                }\n                await plugins.postResponseValidation(context);\n            }\n\n            if (!context.origRes.headersSent) {\n                result = resultToHttpResponse(context, context.res.body);\n            }\n\n            return result;\n        } catch (e) {\n            const err = asError(e);\n\n            if (options.autoHandleHttpErrors) {\n                if (options.autoHandleHttpErrors instanceof Function) {\n                    return options.autoHandleHttpErrors(err, { req });\n                }\n                return handleError(err);\n            } else {\n                throw err;\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "src/errors.ts",
    "content": "import { IValidationError } from './types';\n\nexport class ExtendableError extends Error {\n    constructor(message: string) {\n        super(message);\n        this.name = this.constructor.name;\n        if (typeof Error.captureStackTrace === 'function') {\n            Error.captureStackTrace(this, this.constructor);\n        } else {\n            this.stack = new Error(message).stack;\n        }\n    }\n}\n\nexport class HttpError extends ExtendableError {\n    readonly status: number;\n\n    constructor(status: number, message: string) {\n        super(message);\n        this.status = status;\n    }\n}\n\nexport class HttpBadRequestError extends HttpError {\n    constructor(message: string) {\n        super(400, message);\n    }\n}\n\nexport class ValidationError extends HttpBadRequestError {\n    errors: IValidationError[];\n\n    constructor(errors: IValidationError[] | IValidationError) {\n        if (!Array.isArray(errors)) {\n            errors = [errors];\n        }\n        super(errors.length === 1 ? errors[0].message : 'Multiple validation errors');\n        this.errors = errors;\n    }\n}\n\nexport class HttpNotFoundError extends HttpError {\n    constructor(message: string) {\n        super(404, message);\n    }\n}\n\nexport class HttpPayloadTooLargeError extends HttpError {\n    constructor(message: string) {\n        super(413, message);\n    }\n}\n\n/**\n * Ensures the passed in `err` is of type Error.\n */\nexport function asError(err: any): Error {\n    if (err instanceof Error) {\n        return err;\n    } else {\n        const newErr = new Error(err);\n        if (err.status) {\n            (newErr as any).status = err.status;\n        }\n        return newErr;\n    }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import * as http from 'http';\nimport * as oas3 from 'openapi3-ts';\nimport pb from 'promise-breaker';\nimport { pipeline } from 'stream';\nimport $RefParser from '@apidevtools/json-schema-ref-parser';\n\nimport { compileOptions } from './options';\nimport { compile as compileOpenApi } from './oas3';\nimport generateExegesisRunner from './core/exegesisRunner';\nimport {\n    ApiInterface,\n    ExegesisOptions,\n    Callback,\n    ExegesisRunner,\n    HttpResult,\n    HttpIncomingMessage,\n    MiddlewareFunction,\n    OAS3ApiInfo,\n} from './types';\nexport { HttpError, ValidationError } from './errors';\nimport { OpenAPIObject } from 'openapi3-ts';\nimport PluginsManager from './core/PluginsManager';\n\n// Export all our public types.\nexport * from './types';\n\n/**\n * Reads a JSON or YAML file and bundles all $refs, resulting in a single\n * document with only internal refs.\n *\n * @param openApiDocFile - The file containing the document, or a JSON object.\n * @returns - Returns the bundled document\n */\nfunction bundle(openApiDocFile: string | unknown): Promise<any> {\n    const refParser = new $RefParser();\n\n    return refParser.bundle(openApiDocFile as any, { dereference: { circular: false } });\n}\n\nasync function compileDependencies(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions\n) {\n    const compiledOptions = compileOptions(options);\n    const bundledDoc = await bundle(openApiDoc);\n\n    const plugins = new PluginsManager(bundledDoc, (options || {}).plugins || []);\n\n    await plugins.preCompile({ apiDoc: bundledDoc, options });\n\n    const apiInterface = await compileOpenApi(bundledDoc as OpenAPIObject, compiledOptions);\n\n    return { compiledOptions, apiInterface, plugins };\n}\n\n/**\n * Compiles an API interface for the given openApiDoc using the options.\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param options - Options.  See docs/options.md\n * @returns - a Promise which returns the compiled API interface\n */\nexport function compileApiInterface(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions\n): Promise<ApiInterface<OAS3ApiInfo>>;\n\n/**\n * Compiles an API interface for the given openApiDoc using the options.\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param options - Options.  See docs/options.md\n * @param done Callback which returns the compiled API interface\n */\nexport function compileApiInterface(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions,\n    done: Callback<ApiInterface<OAS3ApiInfo>>\n): void;\n\nexport function compileApiInterface(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions,\n    done?: Callback<ApiInterface<OAS3ApiInfo>>\n): Promise<ApiInterface<OAS3ApiInfo>> {\n    return pb.addCallback(done, async () => {\n        return (await compileDependencies(openApiDoc, options)).apiInterface;\n    });\n}\n\n/**\n * Returns a \"runner\" function - call `runner(req, res)` to get back a\n * `HttpResult` object.\n *\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param [options] - Options.  See docs/options.md\n * @returns - a Promise<ExegesisRunner>.  ExegesisRunner is a\n *   `function(req, res)` which will handle an API call, and return an\n *   `HttpResult`, or `undefined` if the request could not be handled.\n */\nexport function compileRunner(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options?: ExegesisOptions\n): Promise<ExegesisRunner>;\n\n/**\n * Returns a \"runner\" function - call `runner(req, res)` to get back a\n * `HttpResult` object.\n *\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param options - Options.  See docs/options.md\n * @param done - Callback which retunrs an ExegesisRunner.  ExegesisRunner is a\n *   `function(req, res)` which will handle an API call, and return an\n *   `HttpResult`, or `undefined` if the request could not be handled.\n */\nexport function compileRunner(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions | undefined,\n    done: Callback<ExegesisRunner>\n): void;\n\nexport function compileRunner(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options?: ExegesisOptions,\n    done?: Callback<ExegesisRunner>\n): Promise<ExegesisRunner> {\n    return pb.addCallback(done, async () => {\n        options = options || {};\n        const { compiledOptions, apiInterface, plugins } = await compileDependencies(\n            openApiDoc,\n            options\n        );\n        return generateExegesisRunner(apiInterface, {\n            autoHandleHttpErrors: compiledOptions.autoHandleHttpErrors,\n            plugins,\n            onResponseValidationError: compiledOptions.onResponseValidationError,\n            validateDefaultResponses: compiledOptions.validateDefaultResponses,\n            originalOptions: options,\n        });\n    });\n}\n\n/**\n * Convenience function which writes an `HttpResult` obtained from an\n * ExegesisRunner out to an HTTP response.\n *\n * @param httpResult - Result to write.\n * @param res - The response to write to.\n * @returns - a Promise which resolves on completion.\n */\nexport function writeHttpResult(httpResult: HttpResult, res: http.ServerResponse): Promise<void>;\n\n/**\n * Convenience function which writes an `HttpResult` obtained from an\n * ExegesisRunner out to an HTTP response.\n *\n * @param httpResult - Result to write.\n * @param res - The response to write to.\n * @param callback - Callback to call on completetion.\n */\nexport function writeHttpResult(\n    httpResult: HttpResult,\n    res: http.ServerResponse,\n    done: Callback<void>\n): void;\n\nexport function writeHttpResult(\n    httpResult: HttpResult,\n    res: http.ServerResponse,\n    done?: Callback<void>\n): Promise<void> {\n    return pb.addCallback(done, async () => {\n        Object.keys(httpResult.headers).forEach((header) =>\n            res.setHeader(header, httpResult.headers[header])\n        );\n        res.statusCode = httpResult.status;\n\n        if (httpResult.body) {\n            const body = httpResult.body;\n            await pb.call((done2: (err: NodeJS.ErrnoException | null) => void) =>\n                pipeline(body, res, done2)\n            );\n        } else {\n            res.end();\n        }\n    });\n}\n\n/**\n * Returns a connect/express middleware function which implements the API.\n *\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param [options] - Options.  See docs/options.md\n * @returns - a Promise<MiddlewareFunction>.\n */\nexport function compileApi(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options?: ExegesisOptions\n): Promise<MiddlewareFunction>;\n\n/**\n * Returns a connect/express middleware function which implements the API.\n *\n * @param openApiDoc - A string, representing a path to the OpenAPI document,\n *   or a JSON object.\n * @param options - Options.  See docs/options.md\n * @param done - callback which returns the MiddlewareFunction.\n */\nexport function compileApi(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options: ExegesisOptions | undefined,\n    done: Callback<MiddlewareFunction>\n): void;\n\nexport function compileApi(\n    openApiDoc: string | oas3.OpenAPIObject,\n    options?: ExegesisOptions | undefined,\n    done?: Callback<MiddlewareFunction> | undefined\n): Promise<MiddlewareFunction> {\n    return pb.addCallback(done, async () => {\n        const runner = await compileRunner(openApiDoc, options);\n\n        return function exegesisMiddleware(\n            req: HttpIncomingMessage,\n            res: http.ServerResponse,\n            next: Callback<void>\n        ) {\n            runner(req, res)\n                .then((result) => {\n                    let answer: Promise<void> | undefined;\n\n                    if (!result) {\n                        if (next) {\n                            next();\n                        }\n                    } else if (res.headersSent) {\n                        // Someone else has already written a response.  :(\n                    } else if (result) {\n                        answer = writeHttpResult(result, res);\n                    } else {\n                        if (next) {\n                            next();\n                        }\n                    }\n                    return answer;\n                })\n                .catch((err) => {\n                    if (next) {\n                        next(err);\n                    } else {\n                        res.statusCode = err.status || 500;\n                        res.end('error');\n                    }\n                });\n        };\n    });\n}\n"
  },
  {
    "path": "src/oas3/Oas3CompileContext.ts",
    "content": "import * as ld from 'lodash';\n\nimport * as oas3 from 'openapi3-ts';\nimport * as jsonPtr from 'json-ptr';\nimport { resolveRef } from '../utils/json-schema-resolve-ref';\n\nimport { ExegesisCompiledOptions } from '../options';\n\n/**\n * A path to an object within a JSON document.\n */\nexport type JsonPath = string[];\nexport type ReadOnlyJsonPath = readonly string[];\n\n/**\n * This has common stuff that we want to pass all the way down through the OAS\n * heirarchy.  This also keeps track of the `path` that a given object was\n * generated from.\n */\nexport default class Oas3CompileContext {\n    readonly path: JsonPath;\n    readonly jsonPointer: string;\n    readonly openApiDoc: oas3.OpenAPIObject;\n    readonly options: ExegesisCompiledOptions;\n\n    /**\n     * Create a new Oas3CompileContext.\n     *\n     * @param openApiDoc - A fully resolved OpenAPI document, with no $refs.\n     * @param path - The path to the object represented by this context.\n     * @param options - Options.\n     */\n    constructor(\n        openApiDoc: oas3.OpenAPIObject,\n        path: ReadOnlyJsonPath,\n        options: ExegesisCompiledOptions\n    );\n    constructor(parent: Oas3CompileContext, relativePath: ReadOnlyJsonPath);\n    constructor(a: any, path: ReadOnlyJsonPath, options?: ExegesisCompiledOptions) {\n        if (a instanceof Oas3CompileContext) {\n            // TODO: Could make this WAY more efficient with Object.create().\n            const parent = a;\n            this.path = parent.path.concat(path);\n            this.openApiDoc = parent.openApiDoc;\n            this.options = parent.options;\n        } else if (options) {\n            this.path = path.slice();\n            this.openApiDoc = a;\n            this.options = options;\n        } else {\n            throw new Error('Invalid parameters to Oas3CompileContext constructor');\n        }\n        this.jsonPointer = jsonPtr.encodePointer(this.path);\n    }\n\n    childContext(relativePath: JsonPath | string) {\n        if (ld.isArray(relativePath)) {\n            return new Oas3CompileContext(this, relativePath);\n        } else {\n            return new Oas3CompileContext(this, [relativePath]);\n        }\n    }\n\n    resolveRef(ref: string | any) {\n        return resolveRef(this.openApiDoc, ref);\n    }\n}\n"
  },
  {
    "path": "src/oas3/OpenApi.ts",
    "content": "import { parse as parseUrl } from 'url';\nimport * as semver from 'semver';\nimport * as http from 'http';\nimport * as oas3 from 'openapi3-ts';\n\nimport { ExegesisCompiledOptions } from '../options';\nimport {\n    ApiInterface,\n    ResolvedPath,\n    ParsedParameterValidator,\n    ResolvedOperation,\n    ParametersMap,\n    OAS3ApiInfo,\n    ExegesisContext,\n    AuthenticationSuccess,\n    ExegesisResponse,\n} from '../types';\nimport Paths from './Paths';\nimport Servers from './Servers';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport { EXEGESIS_CONTROLLER, EXEGESIS_OPERATION_ID } from './extensions';\nimport RequestMediaType from './RequestMediaType';\nimport { HttpBadRequestError } from '../errors';\nimport { httpHasBody, requestMayHaveBody } from '../utils/httpUtils';\nimport { HTTP_METHODS } from './Path';\n\nexport default class OpenApi implements ApiInterface<OAS3ApiInfo> {\n    readonly openApiDoc: oas3.OpenAPIObject;\n    private readonly _options: ExegesisCompiledOptions;\n    private _servers?: Servers;\n    private _paths: Paths;\n\n    /**\n     * Creates a new OpenApi object.\n     *\n     * @param openApiDoc - The complete JSON definition of the API.\n     *   The passed in definition should be a complete JSON object with no $refs.\n     */\n    constructor(openApiDoc: oas3.OpenAPIObject, options: ExegesisCompiledOptions) {\n        if (!openApiDoc.openapi) {\n            throw new Error(\"OpenAPI definition is missing 'openapi' field\");\n        }\n        if (!semver.satisfies(openApiDoc.openapi, '>=3.0.0 <4.0.0')) {\n            throw new Error(`OpenAPI version ${openApiDoc.openapi} not supported`);\n        }\n\n        this.openApiDoc = openApiDoc;\n        this._options = options;\n\n        // TODO: Optimize this case when no `servers` were present in openApi doc,\n        // or where we don't need to match servers (only server is {url: '/'})?\n        if (!options.ignoreServers && openApiDoc.servers) {\n            this._servers = new Servers(openApiDoc.servers);\n        }\n\n        const exegesisController = openApiDoc[EXEGESIS_CONTROLLER];\n\n        this._paths = new Paths(\n            new Oas3CompileContext(openApiDoc, ['paths'], options),\n            exegesisController\n        );\n    }\n\n    resolve(\n        method: string,\n        url: string,\n        headers: http.IncomingHttpHeaders\n    ): ResolvedPath<OAS3ApiInfo> | undefined {\n        const parsedUrl = parseUrl(url);\n        const pathname = parsedUrl.pathname || '';\n        const host = parsedUrl.hostname || headers['host'] || '';\n        const contentType = headers['content-type'];\n\n        let pathToResolve: string | undefined;\n        let oaServer: oas3.ServerObject | undefined;\n        let serverParams: ParametersMap<string | string[]> | undefined;\n        let baseUrl = '';\n\n        if (!this._servers) {\n            pathToResolve = pathname;\n        } else {\n            const serverData = this._servers.resolveServer(host, pathname);\n            if (serverData) {\n                oaServer = serverData.oaServer;\n                pathToResolve = serverData.pathnameRest;\n                serverParams = serverData.serverParams;\n                baseUrl = serverData.baseUrl;\n            }\n        }\n\n        if (pathToResolve) {\n            const resolvedPath = this._paths.resolvePath(pathToResolve);\n            if (resolvedPath) {\n                const { path, rawPathParams } = resolvedPath;\n                const operation = path.getOperation(method);\n                let mediaType: RequestMediaType | undefined;\n\n                if (operation && contentType) {\n                    mediaType = operation.getRequestMediaType(contentType);\n                    if (!mediaType && (httpHasBody(headers) || requestMayHaveBody(method))) {\n                        throw new HttpBadRequestError(`Invalid content-type: ${contentType}`);\n                    }\n                } else if (\n                    operation &&\n                    operation.bodyRequired &&\n                    operation.validRequestContentTypes\n                ) {\n                    throw new HttpBadRequestError(\n                        `Missing content-type. ` +\n                            `Expected one of: ${operation.validRequestContentTypes}`\n                    );\n                }\n\n                let resolvedOperation: ResolvedOperation | undefined;\n                if (operation) {\n                    const parseParameters = function () {\n                        return operation.parseParameters({\n                            headers,\n                            rawPathParams,\n                            serverParams,\n                            queryString: parsedUrl.query || undefined,\n                        });\n                    };\n\n                    const validateParameters: ParsedParameterValidator = (parameterValues) =>\n                        operation.validateParameters(parameterValues);\n\n                    const bodyParser = mediaType && mediaType.parser;\n                    const validateBody = mediaType && mediaType.validator;\n\n                    const validateResponse = (\n                        response: ExegesisResponse,\n                        validateDefaultResponses: boolean\n                    ) => operation.validateResponse(response, validateDefaultResponses);\n\n                    const exegesisControllerName =\n                        (mediaType && mediaType.oaMediaType[EXEGESIS_CONTROLLER]) ||\n                        operation.exegesisController;\n\n                    const operationId =\n                        (mediaType && mediaType.oaMediaType[EXEGESIS_OPERATION_ID]) ||\n                        operation.operationId;\n\n                    const controllerModule =\n                        exegesisControllerName && this._options.controllers[exegesisControllerName];\n\n                    const controller =\n                        operationId && controllerModule && controllerModule[operationId];\n\n                    const authenticate = (\n                        context: ExegesisContext\n                    ): Promise<{ [scheme: string]: AuthenticationSuccess } | undefined> => {\n                        return operation.authenticate(context);\n                    };\n\n                    resolvedOperation = {\n                        parseParameters,\n                        validateParameters,\n                        parameterLocations: operation.parameterLocations,\n                        bodyParser,\n                        validateBody,\n                        validateResponse,\n                        exegesisControllerName,\n                        operationId,\n                        controllerModule,\n                        controller,\n                        authenticate,\n                    };\n                }\n\n                const allowedMethods = HTTP_METHODS.filter((method) => path.getOperation(method));\n\n                return {\n                    operation: resolvedOperation,\n                    api: {\n                        openApiDoc: this.openApiDoc,\n                        serverPtr: undefined, // FIXME\n                        serverObject: oaServer,\n                        pathItemPtr: path.context.jsonPointer,\n                        pathItemObject: path.oaPath,\n                        operationPtr: operation && operation.context.jsonPointer,\n                        operationObject: operation && operation.oaOperation,\n                        requestBodyMediaTypePtr: mediaType && mediaType.context.jsonPointer,\n                        requestBodyMediaTypeObject: mediaType && mediaType.oaMediaType,\n                    },\n                    allowedMethods,\n                    path: resolvedPath.pathKey,\n                    baseUrl,\n                };\n            }\n        }\n\n        return undefined;\n    }\n}\n"
  },
  {
    "path": "src/oas3/Operation.ts",
    "content": "import deepFreeze from 'deep-freeze';\nimport ld from 'lodash';\nimport pb from 'promise-breaker';\nimport * as oas3 from 'openapi3-ts';\n\nimport { MimeTypeRegistry } from '../utils/mime';\nimport { contentToRequestMediaTypeRegistry } from './oasUtils';\nimport RequestMediaType from './RequestMediaType';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport Parameter from './Parameter';\nimport { RawValues, parseParameterGroup, parseQueryParameters } from './parameterParsers';\nimport {\n    ParametersMap,\n    ParametersByLocation,\n    IValidationError,\n    ExegesisContext,\n    AuthenticationSuccess,\n    Dictionary,\n    AuthenticationFailure,\n    AuthenticationResult,\n    ExegesisResponse,\n    ResponseValidationResult,\n    ParameterLocations,\n} from '../types';\nimport { EXEGESIS_CONTROLLER, EXEGESIS_OPERATION_ID } from './extensions';\nimport Responses from './Responses';\nimport SecuritySchemes from './SecuritySchemes';\n\n// `delete` might have a body. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE\nconst METHODS_WITH_BODY = ['post', 'put', 'patch', 'delete'];\n\nfunction isAuthenticationFailure(result: any): result is AuthenticationFailure {\n    return !!(result.type === 'invalid' || result.type === 'missing');\n}\n\nfunction getMissing(required: string[], have: string[] | undefined) {\n    if (!have || have.length === 0) {\n        return required;\n    } else {\n        return required.filter((r) => !have.includes(r));\n    }\n}\n\nfunction validateController(\n    context: Oas3CompileContext,\n    controller: string | undefined,\n    operationId: string | undefined\n) {\n    if (!controller && !context.options.allowMissingControllers) {\n        throw new Error(`Missing ${EXEGESIS_CONTROLLER} for ${context.jsonPointer}`);\n    }\n    if (!operationId && !context.options.allowMissingControllers) {\n        throw new Error(\n            `Missing operationId or ${EXEGESIS_OPERATION_ID} for ${context.jsonPointer}`\n        );\n    }\n    if (controller && operationId) {\n        if (!context.options.controllers[controller]) {\n            throw new Error(\n                `Could not find controller ${controller} defined in ${context.jsonPointer}`\n            );\n        } else if (!context.options.controllers[controller][operationId]) {\n            throw new Error(\n                `Could not find operation ${controller}#${operationId} defined in ${context.jsonPointer}`\n            );\n        }\n    }\n}\n\n/*\n * Validate that all operations/request bodies have a controller and\n * operationId defined.\n */\nfunction validateControllers(\n    context: Oas3CompileContext,\n    requestBody: oas3.RequestBodyObject | undefined,\n    opController: string | undefined,\n    operationId: string | undefined\n) {\n    if (requestBody) {\n        for (const mediaType of Object.keys(requestBody.content)) {\n            const mediaContext = context.childContext(['requestBody', 'content', mediaType]);\n            const mediaTypeObject = requestBody.content[mediaType];\n            const mediaController = mediaTypeObject[EXEGESIS_CONTROLLER] || opController;\n            const mediaOperationId = mediaTypeObject[EXEGESIS_OPERATION_ID] || operationId;\n            validateController(mediaContext, mediaController, mediaOperationId);\n        }\n    } else {\n        validateController(context, opController, operationId);\n    }\n}\n\nexport default class Operation {\n    readonly context: Oas3CompileContext;\n    readonly oaOperation: oas3.OperationObject;\n    readonly oaPath: oas3.PathItemObject;\n    readonly exegesisController: string | undefined;\n    readonly operationId: string | undefined;\n    readonly securityRequirements: oas3.SecurityRequirementObject[];\n    readonly parameterLocations: ParameterLocations;\n\n    /**\n     * If this operation has a `requestBody`, this is a list of content-types\n     * the operation understands.  If this operation does not expect a request\n     * body, then this is undefined.  Note this list may contain wildcards.\n     */\n    readonly validRequestContentTypes: string[] | undefined;\n\n    readonly bodyRequired: boolean;\n\n    private readonly _requestBodyContentTypes: MimeTypeRegistry<RequestMediaType>;\n    private readonly _parameters: ParametersByLocation<Parameter[]>;\n    private readonly _responses: Responses;\n    private readonly _securitySchemes: SecuritySchemes;\n\n    constructor(\n        context: Oas3CompileContext,\n        oaOperation: oas3.OperationObject,\n        oaPath: oas3.PathItemObject,\n        method: string,\n        exegesisController: string | undefined,\n        parentParameters: Parameter[]\n    ) {\n        this.context = context;\n        this.oaOperation = oaOperation;\n        this.oaPath = oaPath;\n        this.exegesisController = oaOperation[EXEGESIS_CONTROLLER] || exegesisController;\n        this.operationId = oaOperation[EXEGESIS_OPERATION_ID] || oaOperation.operationId;\n\n        this.securityRequirements = oaOperation.security || context.openApiDoc.security || [];\n\n        this._securitySchemes = new SecuritySchemes(context.openApiDoc);\n\n        this._responses = new Responses(context.childContext('responses'), oaOperation.responses);\n\n        for (const securityRequirement of this.securityRequirements) {\n            for (const schemeName of Object.keys(securityRequirement)) {\n                if (!context.options.authenticators[schemeName]) {\n                    throw new Error(\n                        `Operation ${context.jsonPointer} references security scheme \"${schemeName}\" ` +\n                            `but no authenticator was provided.`\n                    );\n                }\n            }\n        }\n\n        const requestBody =\n            oaOperation.requestBody && METHODS_WITH_BODY.includes(method)\n                ? (context.resolveRef(oaOperation.requestBody) as oas3.RequestBodyObject)\n                : undefined;\n\n        validateControllers(context, requestBody, this.exegesisController, this.operationId);\n\n        if (requestBody) {\n            this.validRequestContentTypes = Object.keys(requestBody.content);\n            this.bodyRequired = requestBody.required || false;\n\n            const contentContext = context.childContext(['requestBody', 'content']);\n            this._requestBodyContentTypes = contentToRequestMediaTypeRegistry(\n                contentContext,\n                { in: 'request', name: 'body', docPath: contentContext.jsonPointer },\n                requestBody.required || false,\n                requestBody.content\n            );\n        } else {\n            this._requestBodyContentTypes = new MimeTypeRegistry<RequestMediaType>();\n            this.bodyRequired = false;\n        }\n\n        const localParameters = (this.oaOperation.parameters || []).map(\n            (parameter, index) =>\n                new Parameter(context.childContext(['parameters', '' + index]), parameter)\n        );\n        const allParameters = parentParameters.concat(localParameters);\n\n        this._parameters = allParameters.reduce(\n            (result: ParametersByLocation<Parameter[]>, parameter: Parameter) => {\n                (result as any)[parameter.oaParameter.in].push(parameter);\n                return result;\n            },\n            { query: [], header: [], path: [], server: [], cookie: [] }\n        );\n\n        this.parameterLocations = deepFreeze(\n            allParameters.reduce(\n                (result: ParameterLocations, parameter: Parameter) => {\n                    (result as any)[parameter.oaParameter.in] = parameter.location;\n                    return result;\n                },\n                { query: {}, header: {}, path: {}, cookie: {} }\n            )\n        );\n    }\n\n    /**\n     * Given a 'content-type' from a request, return a `MediaType` object that\n     * matches, or `undefined` if no objects match.\n     *\n     * @param contentType - The content type from the 'content-type' header on\n     *   a request.\n     * @returns - The MediaType object to handle this request, or undefined if\n     *   no MediaType is set for the given contentType.\n     */\n    getRequestMediaType(contentType: string): RequestMediaType | undefined {\n        return this._requestBodyContentTypes.get(contentType);\n    }\n\n    /**\n     * Parse parameters for this operation.\n     * @param params - Raw headers, raw path params and server params from\n     *   `PathResolver`, and the raw queryString.\n     * @returns parsed parameters.\n     */\n    parseParameters(params: {\n        headers: RawValues | undefined;\n        rawPathParams: RawValues | undefined;\n        serverParams: RawValues | undefined;\n        queryString: string | undefined;\n    }): ParametersByLocation<ParametersMap<any>> {\n        const { headers, rawPathParams, queryString } = params;\n\n        return {\n            query: parseQueryParameters(this._parameters.query, queryString),\n            header: parseParameterGroup(this._parameters.header, headers || {}),\n            server: params.serverParams || {},\n            path: rawPathParams ? parseParameterGroup(this._parameters.path, rawPathParams) : {},\n            cookie: {},\n        };\n    }\n\n    validateParameters(\n        parameterValues: ParametersByLocation<ParametersMap<any>>\n    ): IValidationError[] | null {\n        // TODO: We could probably make this a lot more efficient by building the schema\n        // for the parameter tree.\n        let errors: IValidationError[] | null = null;\n        for (const parameterLocation of Object.keys(parameterValues)) {\n            const parameters: Parameter[] = (this._parameters as any)[\n                parameterLocation\n            ] as Parameter[];\n            const values = (parameterValues as any)[parameterLocation] as ParametersMap<any>;\n\n            for (const parameter of parameters) {\n                const innerResult = parameter.validate(values[parameter.oaParameter.name]);\n                if (innerResult && innerResult.errors && innerResult.errors.length > 0) {\n                    errors = errors || [];\n                    errors = errors.concat(innerResult.errors);\n                } else {\n                    values[parameter.oaParameter.name] = innerResult.value;\n                }\n            }\n        }\n\n        return errors;\n    }\n\n    /**\n     * Validate a response.\n     *\n     * @param response - The response generated by a controller.\n     * @param validateDefaultResponses - true to validate all responses, false\n     *   to only validate non-default responses.\n     */\n    validateResponse(\n        response: ExegesisResponse,\n        validateDefaultResponses: boolean\n    ): ResponseValidationResult {\n        return this._responses.validateResponse(\n            response.statusCode,\n            response.headers,\n            response.body,\n            validateDefaultResponses\n        );\n    }\n\n    private async _runAuthenticator(\n        schemeName: string,\n        triedSchemes: Dictionary<AuthenticationResult>,\n        exegesisContext: ExegesisContext,\n        requiredScopes: string[]\n    ): Promise<AuthenticationResult> {\n        if (!(schemeName in triedSchemes)) {\n            const authenticator = this.context.options.authenticators[schemeName];\n            const info = this._securitySchemes.getInfo(schemeName);\n\n            const result: AuthenticationResult = (await pb.call(\n                authenticator,\n                null,\n                exegesisContext,\n                info\n            )) || { type: 'missing', status: 401 };\n\n            if (\n                result.type !== 'success' &&\n                result.type !== 'invalid' &&\n                result.type !== 'missing'\n            ) {\n                throw new Error(\n                    `Invalid result ${result.type} from authenticator for ${schemeName}`\n                );\n            }\n\n            if (isAuthenticationFailure(result)) {\n                result.status = result.status || 401;\n                if (result.status === 401 && !result.challenge) {\n                    result.challenge = this._securitySchemes.getChallenge(schemeName);\n                }\n            }\n\n            triedSchemes[schemeName] = result;\n        }\n\n        let result = triedSchemes[schemeName];\n\n        if (!isAuthenticationFailure(result)) {\n            // For OAuth3, need to verify we have the oauth scopes defined in the API doc.\n            const missingScopes = getMissing(requiredScopes, result.scopes);\n            if (missingScopes.length > 0) {\n                result = {\n                    type: 'invalid',\n                    status: 403,\n                    message:\n                        `Authenticated using '${schemeName}' but missing ` +\n                        `required scopes: ${missingScopes.join(', ')}.`,\n                };\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * Checks a single security requirement from an OAS3 `security` field.\n     *\n     * @param triedSchemes - A cache where keys are names of security schemes\n     *   we've already tried, and values are the results returned by the\n     *   authenticator.\n     * @param errors - An array of strings - we can push any errors we encounter\n     *   to this list.\n     * @param securityRequirement - The security requirement to check.\n     * @param exegesisContext - The context for the request to check.\n     * @returns - If the security requirement matches, this returns a\n     *   `{type: 'authenticated', result}` object, where result is an object\n     *   where keys are security schemes and the values are the results from\n     *   the authenticator.  If the requirements are not met, returns a\n     *   `{type: 'missing', failure}` object or a `{type: 'invalid', failure}`,\n     *   object where `failure` is the the failure that caused this security\n     *   requirement to not pass.\n     */\n    private async _checkSecurityRequirement(\n        triedSchemes: Dictionary<AuthenticationResult>,\n        securityRequirement: oas3.SecurityRequirementObject,\n        exegesisContext: ExegesisContext\n    ) {\n        const requiredSchemes = Object.keys(securityRequirement);\n\n        const result: Dictionary<any> = Object.create(null);\n        let failure: AuthenticationFailure | undefined;\n        let failedSchemeName: string | undefined;\n\n        for (const scheme of requiredSchemes) {\n            if (exegesisContext.isResponseFinished()) {\n                // Some authenticator has written a response.  We're done.  :(\n                break;\n            }\n\n            const requiredScopes = securityRequirement[scheme];\n            const authResult = await this._runAuthenticator(\n                scheme,\n                triedSchemes,\n                exegesisContext,\n                requiredScopes\n            );\n\n            if (isAuthenticationFailure(authResult)) {\n                // Couldn't authenticate.  Try the next one.\n                failure = authResult;\n                failedSchemeName = scheme;\n                break;\n            }\n\n            result[scheme] = authResult;\n        }\n\n        if (failure) {\n            return { type: failure.type, failure, failedSchemeName };\n        } else if (result) {\n            return { type: 'authenticated', result };\n        } else {\n            return undefined;\n        }\n    }\n\n    async authenticate(\n        exegesisContext: ExegesisContext\n    ): Promise<{ [scheme: string]: AuthenticationSuccess } | undefined> {\n        if (this.securityRequirements.length === 0) {\n            // No auth required\n            return {};\n        }\n        let firstFailureResult: AuthenticationFailure | undefined;\n        const challenges: { [schemeName: string]: string | undefined } = {};\n        let firstAuthenticatedResult: Dictionary<AuthenticationSuccess> | undefined;\n\n        const triedSchemes: Dictionary<AuthenticationSuccess> = Object.create(null);\n\n        for (const securityRequirement of this.securityRequirements) {\n            const securityRequirementResult = await this._checkSecurityRequirement(\n                triedSchemes,\n                securityRequirement,\n                exegesisContext\n            );\n\n            if (!securityRequirementResult) {\n                break;\n            } else if (securityRequirementResult.type === 'authenticated') {\n                firstAuthenticatedResult =\n                    firstAuthenticatedResult || securityRequirementResult.result;\n            } else if (\n                securityRequirementResult.type === 'missing' ||\n                securityRequirementResult.type === 'invalid'\n            ) {\n                const failure = securityRequirementResult.failure;\n                if (!failure) {\n                    throw new Error('Missing failure.');\n                }\n                if (!securityRequirementResult.failedSchemeName) {\n                    throw new Error('Missing failed scheme name.');\n                }\n\n                // No luck with this security requirement.\n                if (failure.status === 401 && failure.challenge) {\n                    challenges[securityRequirementResult.failedSchemeName] = failure.challenge;\n                }\n\n                if (securityRequirementResult.type === 'invalid') {\n                    firstFailureResult = firstFailureResult || failure;\n                    break;\n                }\n            } else {\n                /* istanbul ignore this */\n                throw new Error('Invalid result from `_checkSecurityRequirement()`');\n            }\n\n            if (exegesisContext.isResponseFinished()) {\n                // We're done!\n                break;\n            }\n        }\n\n        if (firstAuthenticatedResult && !firstFailureResult) {\n            // Successs!\n            return firstAuthenticatedResult;\n        } else if (exegesisContext.isResponseFinished()) {\n            // Someone already wrote a response.\n            return undefined;\n        } else {\n            const authSchemes = this.securityRequirements.map((requirement) => {\n                const schemes = Object.keys(requirement);\n                return schemes.length === 1 ? schemes[0] : `(${schemes.join(' + ')})`;\n            });\n\n            const authChallenges = ld(this.securityRequirements)\n                .map((requirement: any): string[] => Object.keys(requirement))\n                .flatten()\n                .map(\n                    (schemeName: string) =>\n                        challenges[schemeName] || this._securitySchemes.getChallenge(schemeName)\n                )\n                .filter((challenge) => challenge !== undefined)\n                .value() as string[];\n\n            const message =\n                (firstFailureResult && firstFailureResult.message) ||\n                `Must authenticate using one of the following schemes: ${authSchemes.join(', ')}.`;\n\n            exegesisContext.res\n                .setStatus((firstFailureResult && firstFailureResult.status) || 401)\n                .set('WWW-Authenticate', authChallenges)\n                .setBody({ message });\n\n            return undefined;\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Parameter.ts",
    "content": "import { JSONSchema4, JSONSchema6 } from 'json-schema';\nimport { ParameterLocation, ValidatorFunction, oas3 } from '../types';\nimport { extractSchema } from '../utils/jsonSchema';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport { generateRequestValidator } from './Schema/validators';\nimport { isReferenceObject } from './oasUtils';\nimport { ParameterParser, generateParser } from './parameterParsers';\nimport * as urlEncodedBodyParser from './urlEncodedBodyParser';\n\nconst DEFAULT_STYLE: { [style: string]: string } = {\n    path: 'simple',\n    query: 'form',\n    cookie: 'form',\n    header: 'simple',\n};\n\nfunction getDefaultExplode(style: string): boolean {\n    return style === 'form';\n}\n\nfunction generateSchemaParser(self: Parameter, schema: JSONSchema4 | JSONSchema6) {\n    const style = self.oaParameter.style || DEFAULT_STYLE[self.oaParameter.in];\n    const explode =\n        self.oaParameter.explode === null || self.oaParameter.explode === undefined\n            ? getDefaultExplode(style)\n            : self.oaParameter.explode;\n    const allowReserved = self.oaParameter.allowReserved || false;\n\n    return generateParser({\n        required: self.oaParameter.required,\n        style,\n        explode,\n        allowReserved,\n        schema,\n    });\n}\n\nexport default class Parameter {\n    readonly context: Oas3CompileContext;\n    readonly oaParameter: oas3.ParameterObject;\n\n    readonly location: ParameterLocation;\n    readonly validate: ValidatorFunction;\n\n    /**\n     * Parameter parser used to parse this parameter.\n     */\n    readonly name: string;\n    readonly parser: ParameterParser;\n\n    constructor(\n        context: Oas3CompileContext,\n        oaParameter: oas3.ParameterObject | oas3.ReferenceObject\n    ) {\n        const resOaParameter = isReferenceObject(oaParameter)\n            ? (context.resolveRef(oaParameter.$ref) as oas3.ParameterObject)\n            : oaParameter;\n\n        this.location = {\n            in: resOaParameter.in,\n            name: resOaParameter.name,\n            docPath: context.jsonPointer,\n            path: '',\n        };\n        this.name = resOaParameter.name;\n\n        this.context = context;\n        this.oaParameter = resOaParameter;\n        this.validate = (value) => ({ errors: null, value });\n\n        // Find the schema for this parameter.\n        if (resOaParameter.schema) {\n            const schemaContext = context.childContext('schema');\n            const schema = extractSchema(context.openApiDoc, schemaContext.jsonPointer, {\n                resolveRef: context.resolveRef.bind(context),\n            });\n            this.parser = generateSchemaParser(this, schema);\n            this.validate = generateRequestValidator(\n                schemaContext,\n                this.location,\n                resOaParameter.required || false,\n                'application/x-www-form-urlencoded'\n            );\n        } else if (resOaParameter.content) {\n            // `parameter.content` must have exactly one key\n            const mediaTypeString = Object.keys(resOaParameter.content)[0];\n            const oaMediaType = resOaParameter.content[mediaTypeString];\n            const mediaTypeContext = context.childContext(['content', mediaTypeString]);\n\n            let parser = context.options.parameterParsers.get(mediaTypeString);\n\n            // OAS3 has special handling for 'application/x-www-form-urlencoded'.\n            if (!parser && mediaTypeString === 'application/x-www-form-urlencoded') {\n                parser = urlEncodedBodyParser.generateStringParser(\n                    mediaTypeContext,\n                    oaMediaType,\n                    this.location\n                );\n            }\n\n            if (!parser) {\n                throw new Error(\n                    'Unable to find suitable mime type parser for ' +\n                        `type ${mediaTypeString} in ${context.jsonPointer}/content`\n                );\n            }\n\n            // FIXME: We don't handle 'application/x-www-form-urlencoded' here\n            // correctly.\n            this.parser = generateParser({\n                required: resOaParameter.required || false,\n                schema: oaMediaType.schema,\n                contentType: mediaTypeString,\n                parser,\n                uriEncoded: ['query', 'path'].includes(resOaParameter.in),\n            });\n\n            if (oaMediaType.schema) {\n                this.validate = generateRequestValidator(\n                    mediaTypeContext.childContext('schema'),\n                    this.location,\n                    resOaParameter.required || false,\n                    mediaTypeString\n                );\n            }\n        } else {\n            throw new Error(\n                `Parameter ${resOaParameter.name} should have a 'schema' or a 'content'`\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Path.ts",
    "content": "import Operation from './Operation';\n\nimport Oas3CompileContext from './Oas3CompileContext';\nimport * as oas3 from 'openapi3-ts';\nimport Parameter from './Parameter';\nimport { EXEGESIS_CONTROLLER } from './extensions';\n\n// CONNECT not included, as it is not valid for OpenAPI 3.0.1.\nexport const HTTP_METHODS = [\n    'get',\n    'head',\n    'post',\n    'put',\n    'delete',\n    'options',\n    'trace',\n    'patch',\n] as const;\n\ninterface OperationsMap {\n    [key: string]: Operation;\n}\n\nexport default class Path {\n    readonly context: Oas3CompileContext;\n    readonly oaPath: oas3.PathItemObject;\n    private readonly _operations: OperationsMap;\n\n    constructor(\n        context: Oas3CompileContext,\n        oaPath: oas3.PathItemObject,\n        exegesisController: string | undefined\n    ) {\n        this.context = context;\n        if (oaPath.$ref) {\n            this.oaPath = context.resolveRef(oaPath.$ref) as oas3.PathItemObject;\n        } else {\n            this.oaPath = oaPath;\n        }\n        const parameters = (oaPath.parameters || []).map(\n            (p, i) => new Parameter(context.childContext(['parameters', '' + i]), p)\n        );\n\n        exegesisController = oaPath[EXEGESIS_CONTROLLER] || exegesisController;\n        this._operations = HTTP_METHODS.reduce((result: OperationsMap, method) => {\n            const operation = oaPath[method];\n            if (operation) {\n                result[method] = new Operation(\n                    context.childContext(method),\n                    operation,\n                    oaPath,\n                    method,\n                    exegesisController,\n                    parameters\n                );\n            }\n            return result;\n        }, Object.create(null));\n    }\n\n    getOperation(method: string): Operation | undefined {\n        return this._operations[method.toLowerCase()];\n    }\n}\n"
  },
  {
    "path": "src/oas3/Paths/PathResolver.ts",
    "content": "import { escapeRegExp } from 'lodash';\nconst TEMPLATE_RE = /^(.*?){(.*?)}(.*)$/;\n\nimport { ParametersMap } from '../../types';\n\nexport type PathParserFunction = (\n    pathname: string\n) => { matched: string; rawPathParams: ParametersMap<any> } | null;\n\n/**\n * @param path - The path to check.\n * @returns true if the specified path uses templating, false otherwise.\n */\nexport function hasTemplates(path: string): boolean {\n    return !!TEMPLATE_RE.exec(path);\n}\n\n/**\n * Given a path containing template parts (e.g. \"/foo/{bar}/baz\"), returns\n * a regular expression that matches the path, and a list of parameters found.\n *\n * @param path - The path to convert.\n * @param options.openEnded - If true, then the returned `regex` will\n *   accept extra input at the end of the path.\n *\n * @returns A `{regex, params, parser}`, where:\n * - `params` is a list of parameters found in the path.\n * - `regex` is a regular expression that will match the path.  When calling\n *   `match = regex.exec(str)`, each parameter in `params[i]` will be present\n *   in `match[i+1]`.\n * - `parser` is a `fn(str)` that, given a path, will return null if\n *   the string does not match, and a `{matched, pathParams}` object if the\n *   path matches.  `pathParams` is an object where keys are parameter names\n *   and values are strings from the `path`.  `matched` is full string matched\n *   by the regex.\n */\nexport function compileTemplatePath(\n    path: string,\n    options: {\n        openEnded?: boolean;\n    } = {}\n): {\n    params: string[];\n    regex: RegExp;\n    parser: PathParserFunction;\n} {\n    const params: string[] = [];\n\n    // Split up the path at each parameter.\n    const regexParts: string[] = [];\n    let remainingPath = path;\n    let tempateMatch;\n    do {\n        tempateMatch = TEMPLATE_RE.exec(remainingPath);\n        if (tempateMatch) {\n            regexParts.push(tempateMatch[1]);\n            params.push(tempateMatch[2]);\n            remainingPath = tempateMatch[3];\n        }\n    } while (tempateMatch);\n    regexParts.push(remainingPath);\n\n    const regexStr = regexParts.map(escapeRegExp).join('([^/]*)');\n    const regex = options.openEnded ? new RegExp(`^${regexStr}`) : new RegExp(`^${regexStr}$`);\n\n    const parser = (urlPathname: string) => {\n        const match = regex.exec(urlPathname);\n        if (match) {\n            return {\n                matched: match[0],\n                rawPathParams: params.reduce(\n                    (result: ParametersMap<string | string[]>, paramName, index) => {\n                        result[paramName] = match[index + 1];\n                        return result;\n                    },\n                    {}\n                ),\n            };\n        } else {\n            return null;\n        }\n    };\n\n    return { regex, params, parser };\n}\n\nexport default class PathResolver<T> {\n    // Static paths with no templating are stored in a hash, for easy lookup.\n    private readonly _staticPaths: { [key: string]: T };\n\n    // Paths with templates are stored in an array, with parser functions that\n    // recognize the path.\n    private readonly _dynamicPaths: {\n        parser: PathParserFunction;\n        value: T;\n        path: string;\n    }[];\n\n    // TODO: Pass in variable styles.  Some variable styles start with a special\n    // character, and we can check to see if the character is there or not.\n    // (Or, replace this whole class with a uri-template engine.)\n    constructor() {\n        this._staticPaths = Object.create(null);\n        this._dynamicPaths = [];\n    }\n\n    registerPath(path: string, value: T) {\n        if (!path.startsWith('/')) {\n            throw new Error(`Invalid path \"${path}\"`);\n        }\n\n        if (hasTemplates(path)) {\n            const { parser } = compileTemplatePath(path);\n            this._dynamicPaths.push({ value, parser, path });\n        } else {\n            this._staticPaths[path] = value;\n        }\n    }\n\n    /**\n     * Given a `pathname` from a URL (e.g. \"/foo/bar\") this will return the\n     * a static path if one exists, otherwise a path with templates if one\n     * exists.\n     *\n     * @param urlPathname - The pathname to search for.\n     * @returns A `{value, rawPathParams} object if a path is matched, or\n     *   undefined if there was no match.\n     */\n    resolvePath(urlPathname: string) {\n        let value: T | undefined = this._staticPaths[urlPathname];\n        let rawPathParams: ParametersMap<string | string[]> | undefined;\n        let path = urlPathname;\n\n        if (!value) {\n            for (const dynamicPath of this._dynamicPaths) {\n                const matched = dynamicPath.parser(urlPathname);\n                if (matched) {\n                    value = dynamicPath.value;\n                    rawPathParams = matched.rawPathParams;\n                    path = dynamicPath.path;\n                }\n            }\n        }\n\n        if (value) {\n            return {\n                value,\n                rawPathParams,\n                path,\n            };\n        } else {\n            return undefined;\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Paths/index.ts",
    "content": "import { isSpecificationExtension } from '../oasUtils';\nimport Oas3CompileContext from '../Oas3CompileContext';\nimport Path from '../Path';\nimport PathResolver from './PathResolver';\nimport { EXEGESIS_CONTROLLER } from '../extensions';\n\nimport { ParametersMap } from '../../types';\n\nexport interface ResolvedPath {\n    path: Path;\n    pathKey: string;\n    rawPathParams: ParametersMap<string | string[]> | undefined;\n}\n\nexport default class Paths {\n    private readonly _pathResolver: PathResolver<Path> = new PathResolver();\n\n    constructor(context: Oas3CompileContext, exegesisController: string | undefined) {\n        const { openApiDoc } = context;\n\n        exegesisController = openApiDoc.paths[EXEGESIS_CONTROLLER] || exegesisController;\n        for (const path of Object.keys(openApiDoc.paths)) {\n            const pathObject = new Path(\n                context.childContext(path),\n                openApiDoc.paths[path],\n                exegesisController\n            );\n\n            if (isSpecificationExtension(path)) {\n                // Skip extentions\n                continue;\n            }\n\n            this._pathResolver.registerPath(path, pathObject);\n        }\n    }\n\n    /**\n     * Given a `pathname` from a URL (e.g. \"/foo/bar\") this will return the\n     * PathObject from the OpenAPI document's `paths` section.\n     *\n     * @param urlPathname - The pathname to search for.  Note that any\n     *   URL prefix defined by the `servers` section of the OpenAPI doc needs\n     *   to be stripped before calling this.\n     * @returns A `{path, rawPathParams}` object.\n     *   `rawPathParams` will be an object where keys are parameter names from path\n     *   templating.  If the path cannot be resolved, returns null, although\n     *   note that if the path is resolved and the operation is not found, this\n     *   will return an object with a null `operationObject`.\n     */\n    resolvePath(urlPathname: string): ResolvedPath | undefined {\n        const result = this._pathResolver.resolvePath(urlPathname);\n        if (result) {\n            return {\n                path: result.value,\n                rawPathParams: result.rawPathParams,\n                pathKey: result.path,\n            };\n        } else {\n            return undefined;\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/README.md",
    "content": "# oas3\n\nThis represents a \"parsed\" OpenAPI 3.x.x document.\n\nThis is a rough view of the heirachy of objects in an OpenApi object:\n\n```text\nOpenApi\n+- Servers\n+- Paths\n   +- Path[]\n      +- Operation\n         +- Parameter\n         +- RequestMediaType\n         +- Responses\n           +- Response\n```\n\nEvery object has an Oas3CompileContext which has \"global\" information about\nconfiguration and about where that object came from.\n"
  },
  {
    "path": "src/oas3/RequestMediaType.ts",
    "content": "import ld from 'lodash';\nimport * as oas3 from 'openapi3-ts';\n\nimport { ValidatorFunction, ParameterLocation, BodyParser } from '../types';\nimport { generateRequestValidator } from './Schema/validators';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport * as urlEncodedBodyParser from './urlEncodedBodyParser';\n\nfunction generateAddDefaultParser(parser: BodyParser, def: any): BodyParser {\n    return {\n        parseReq(req, res, next) {\n            parser.parseReq(req, res, (err, result) => {\n                if (err) {\n                    return next(err);\n                }\n                // TODO: How to test this?  How do you even get here?  If there's\n                // no 'content-type' you'll never get to a RequestMediaType in\n                // the first place.  If the type is `application/json`, a 0-length\n                // body will be invalid.  If the type is `text/plain`, a 0-length\n                // body is the empty string, which is not undefined.  I don't\n                // think this is ever going to be called.\n                if (result === undefined && req.body === undefined) {\n                    req.body = ld.cloneDeep(def);\n                    next(null, req.body);\n                } else {\n                    next(err, result);\n                }\n            });\n        },\n    };\n}\n\nexport default class RequestMediaType {\n    readonly context: Oas3CompileContext;\n    readonly oaMediaType: oas3.MediaTypeObject;\n    readonly parser: BodyParser;\n    readonly validator: ValidatorFunction;\n\n    constructor(\n        context: Oas3CompileContext,\n        oaMediaType: oas3.MediaTypeObject,\n        mediaType: string,\n        parameterLocation: ParameterLocation,\n        parameterRequired: boolean\n    ) {\n        this.context = context;\n        this.oaMediaType = oaMediaType;\n\n        let parser = this.context.options.bodyParsers.get(mediaType);\n\n        // OAS3 has special handling for 'application/x-www-form-urlencoded'.\n        if (!parser && mediaType === 'application/x-www-form-urlencoded') {\n            parser = urlEncodedBodyParser.generateBodyParser(\n                context,\n                oaMediaType,\n                parameterLocation\n            );\n        }\n\n        if (!parser) {\n            throw new Error(\n                'Unable to find suitable mime type parser for ' +\n                    `type ${mediaType} in ${context.jsonPointer}`\n            );\n        }\n\n        const schema = oaMediaType.schema && context.resolveRef(oaMediaType.schema);\n\n        if (schema && 'default' in schema) {\n            this.parser = generateAddDefaultParser(parser, schema.default);\n        } else {\n            this.parser = parser;\n        }\n\n        if (schema) {\n            const schemaContext = context.childContext('schema');\n            this.validator = generateRequestValidator(\n                schemaContext,\n                parameterLocation,\n                parameterRequired,\n                mediaType\n            );\n        } else {\n            this.validator = (value) => ({ errors: null, value });\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Response.ts",
    "content": "import * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport { ValidatorFunction, IValidationError, ParameterLocation, HttpHeaders } from '../types';\nimport { MimeTypeRegistry } from '../utils/mime';\nimport { generateResponseValidator } from './Schema/validators';\nimport { isReadable } from '../utils/typeUtils';\n\nexport default class Responses {\n    readonly context: Oas3CompileContext;\n    private readonly _responseValidators: MimeTypeRegistry<ValidatorFunction>;\n    private readonly _hasResponses: boolean;\n    private readonly _location: ParameterLocation;\n\n    constructor(context: Oas3CompileContext, response: oas3.ResponseObject) {\n        this.context = context;\n        this._hasResponses = false;\n        this._responseValidators = new MimeTypeRegistry<ValidatorFunction>();\n        this._location = {\n            in: 'response',\n            name: 'body',\n            docPath: this.context.jsonPointer,\n        };\n\n        if (response.content) {\n            for (const mimeType of Object.keys(response.content)) {\n                this._hasResponses = true;\n                const mediaTypeObject = response.content[mimeType];\n                let validator;\n                if (mediaTypeObject.schema) {\n                    const schemaContext = context.childContext(['content', mimeType, 'schema']);\n                    validator = generateResponseValidator(\n                        schemaContext,\n                        this._location,\n                        true // Responses are always required.\n                    );\n                } else {\n                    validator = () => ({ errors: null, value: undefined });\n                }\n                this._responseValidators.set(mimeType, validator);\n            }\n        }\n    }\n\n    validateResponse(\n        statusCode: number,\n        headers: HttpHeaders,\n        body: any\n    ): IValidationError[] | null {\n        const contentType = headers['content-type'];\n\n        if (!contentType) {\n            if (body) {\n                return [\n                    {\n                        location: this._location,\n                        message: `Response for ${statusCode} is missing content-type.`,\n                    },\n                ];\n            } else if (this._hasResponses) {\n                return [\n                    {\n                        location: this._location,\n                        message: `Response for ${statusCode} expects body.`,\n                    },\n                ];\n            } else {\n                return null;\n            }\n        } else if (typeof contentType !== 'string') {\n            return [\n                {\n                    location: this._location,\n                    message: `Invalid content type for ${statusCode} response: ${contentType}`,\n                },\n            ];\n        } else {\n            const validator = this._responseValidators.get(contentType);\n            const isJson = contentType.startsWith('application/json');\n\n            if (body === null || body === undefined) {\n                return [\n                    {\n                        location: this._location,\n                        message: `Missing response body for ${statusCode}.`,\n                    },\n                ];\n            } else if (!validator) {\n                return [\n                    {\n                        location: this._location,\n                        message: `Unexpected content-type for ${statusCode} response: ${contentType}.`,\n                    },\n                ];\n            } else if (isJson) {\n                if (body instanceof Buffer || isReadable(body)) {\n                    // Can't validate this.\n                    return null;\n                }\n\n                try {\n                    let jsonData: any;\n                    if (typeof body === 'string') {\n                        if (body.trim() === '') {\n                            jsonData = undefined;\n                        } else {\n                            jsonData = JSON.parse(body);\n                        }\n                    } else {\n                        jsonData = body;\n                    }\n\n                    return validator(jsonData).errors;\n                } catch (err) {\n                    return [\n                        {\n                            location: this._location,\n                            message: `Could not parse content as JSON.`,\n                        },\n                    ];\n                }\n            } else if (typeof body === 'string' || body instanceof Buffer || isReadable(body)) {\n                // Can't validate this.\n                return null;\n            } else {\n                return validator(body).errors;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Responses.ts",
    "content": "import * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport Response from './Response';\nimport { ParameterLocation, ResponseValidationResult, HttpHeaders } from '../types';\n\nexport default class Responses {\n    readonly context: Oas3CompileContext;\n    private readonly _responses: { [statusCode: string]: Response };\n    private readonly _location: ParameterLocation;\n\n    constructor(context: Oas3CompileContext, responses: oas3.ResponsesObject) {\n        this.context = context;\n        this._location = {\n            in: 'response',\n            name: 'body',\n            docPath: this.context.jsonPointer,\n        };\n\n        this._responses = {};\n        for (const statusCode of Object.keys(responses)) {\n            const response: oas3.ResponseObject = context.resolveRef(responses[statusCode]);\n            this._responses[statusCode] = new Response(context.childContext(statusCode), response);\n        }\n    }\n\n    validateResponse(\n        statusCode: number,\n        headers: HttpHeaders,\n        body: any,\n        validateDefaultResponses: boolean\n    ): ResponseValidationResult {\n        const responseObject = this._responses[statusCode] || this._responses.default;\n        if (!responseObject) {\n            return {\n                errors: [\n                    {\n                        location: this._location,\n                        message: `No response defined for status code ${statusCode}.`,\n                    },\n                ],\n                isDefault: false,\n            };\n        } else {\n            const isDefault = !this._responses[statusCode];\n            if (isDefault && !validateDefaultResponses) {\n                return { errors: null, isDefault };\n            } else {\n                return {\n                    errors: responseObject.validateResponse(statusCode, headers, body),\n                    isDefault,\n                };\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/oas3/Schema/validators.ts",
    "content": "import Ajv, { ValidateFunction } from 'ajv';\nimport traveseSchema from 'json-schema-traverse';\nimport { IValidationError, ParameterLocation, ValidatorFunction } from '../../types';\nimport { resolveRef } from '../../utils/json-schema-resolve-ref';\nimport * as jsonPaths from '../../utils/jsonPaths';\nimport * as jsonSchema from '../../utils/jsonSchema';\nimport { MimeTypeRegistry } from '../../utils/mime';\nimport Oas3CompileContext from '../Oas3CompileContext';\n\n// urlencoded and form-data requests do not contain any type information;\n// for example `?foo=9` doesn't tell us if `foo` is the number 9, or the string\n// \"9\", so we need to use type coercion to make sure the data passed in matches\n// our schema.\nconst REQUEST_TYPE_COERCION_ALLOWED = new MimeTypeRegistry<boolean>({\n    'application/x-www-form-urlencoded': true,\n    'multipart/form-data': true,\n});\n\n// TODO tests\n// * readOnly\n// * readOnly with additionalProperties and value supplied\n// * readOnly not supplied but required\n// * writeOnly (all cases as readOnly)\n// * Make sure validation errors are correct format.\n\nfunction assertNever(x: never): never {\n    throw new Error('Unexpected object: ' + x);\n}\n\nfunction getParameterDescription(parameterLocation: ParameterLocation) {\n    let description = '';\n    switch (parameterLocation.in) {\n        case 'path':\n        case 'server':\n        case 'query':\n        case 'cookie':\n        case 'header':\n            description = `${parameterLocation.in} parameter \"${parameterLocation.name}\"`;\n            break;\n        case 'request':\n        case 'response':\n            description = `${parameterLocation.in} body`;\n            break;\n        default:\n            assertNever(parameterLocation.in);\n    }\n\n    return description;\n}\n\nfunction removeExamples(schema: any) {\n    // ajv will print \"schema id ignored\" to stdout if an example contains a filed\n    // named \"id\", so just axe all the examples.\n    traveseSchema(schema, (childSchema: any) => {\n        if (childSchema.example) {\n            delete childSchema.example;\n        }\n    });\n}\n\nexport function _fixNullables(schema: any) {\n    traveseSchema(schema, {\n        cb: {\n            post: (\n                childSchema: any,\n                _jsonPtr,\n                rootSchema: any,\n                _parentJsonPtr,\n                parentKeyword,\n                _parentSchema,\n                keyIndex\n            ) => {\n                if (childSchema.nullable) {\n                    let ref = rootSchema;\n                    let key = parentKeyword;\n                    if (key && keyIndex) {\n                        ref = ref[key];\n                        key = `${keyIndex}`;\n                    }\n                    if (ref && key) {\n                        ref[key] = {\n                            anyOf: [{ type: 'null' }, childSchema],\n                        };\n                    } else if (childSchema === schema) {\n                        schema = {\n                            anyOf: [{ type: 'null' }, schema],\n                        };\n                    }\n                }\n            },\n        },\n    });\n\n    return schema;\n}\n\nexport function _filterRequiredProperties(schema: any, propNameToFilter: string) {\n    traveseSchema(schema, (childSchema: any) => {\n        if (childSchema.properties && childSchema.required) {\n            for (const propName of Object.keys(childSchema.properties)) {\n                const prop = childSchema.properties[propName];\n\n                // Resolve the prop, in case it's a `{$ref: ....}`.\n                const resolvedProp = resolveRef(schema, prop);\n\n                if (resolvedProp && resolvedProp[propNameToFilter]) {\n                    childSchema.required = childSchema.required.filter(\n                        (r: string) => r !== propName\n                    );\n                }\n            }\n        }\n    });\n}\n\nfunction doValidate(\n    schemaPtr: string,\n    parameterLocation: ParameterLocation,\n    parameterRequired: boolean,\n    getAjvValidate: () => ValidateFunction,\n    json: any\n) {\n    const value = { value: json };\n    let errors: IValidationError[] | null = null;\n\n    if (json === null || json === undefined) {\n        if (parameterRequired) {\n            errors = [\n                {\n                    message: `Missing required ${getParameterDescription(parameterLocation)}`,\n                    location: {\n                        in: parameterLocation.in,\n                        name: parameterLocation.name,\n                        // docPath comes from parameter here, not schema, since the parameter\n                        // is the one that defines it is required.\n                        docPath: parameterLocation.docPath,\n                        path: '',\n                    },\n                },\n            ];\n        }\n    }\n\n    if (!errors) {\n        const ajvValidate = getAjvValidate();\n        ajvValidate(value);\n        if (ajvValidate.errors) {\n            errors = ajvValidate.errors.map((err) => {\n                let pathPtr = err.instancePath || '';\n                if (pathPtr.startsWith('/value')) {\n                    pathPtr = pathPtr.slice(6);\n                }\n\n                return {\n                    message: err.message || 'Unspecified error',\n                    location: {\n                        in: parameterLocation.in,\n                        name: parameterLocation.name,\n                        docPath: schemaPtr,\n                        path: pathPtr,\n                    },\n                    ajvError: err,\n                };\n            });\n        }\n    }\n\n    return { errors, value: value.value };\n}\n\nfunction createValidateGetter(schema: any, ajv: Ajv, lazy: boolean): () => ValidateFunction {\n    if (lazy) {\n        let validate: ValidateFunction | null = null;\n        return function () {\n            if (!validate) {\n                validate = ajv.compile(schema);\n            }\n            return validate;\n        };\n    } else {\n        const validate = ajv.compile(schema);\n        return function () {\n            return validate;\n        };\n    }\n}\n\nfunction generateValidator(\n    schemaContext: Oas3CompileContext,\n    parameterLocation: ParameterLocation,\n    parameterRequired: boolean,\n    propNameToFilter: string,\n    allowTypeCoercion: boolean\n): ValidatorFunction {\n    const { openApiDoc, jsonPointer: schemaPtr } = schemaContext;\n    const customFormats = schemaContext.options.customFormats;\n\n    let schema: any = jsonSchema.extractSchema(openApiDoc, schemaPtr);\n    _filterRequiredProperties(schema, propNameToFilter);\n    removeExamples(schema);\n    // TODO: Should we do this?  Or should we rely on the schema being correct in the first place?\n    // schema = _fixNullables(schema);\n\n    // So that we can replace the \"root\" value of the schema using ajv's type coercion...\n    traveseSchema(schema, (node: any) => {\n        if (node.$ref) {\n            if (node.$ref.startsWith('#')) {\n                node.$ref = `#/properties/value/${node.$ref.slice(2)}`;\n            } else {\n                node.$ref = jsonPaths.toUriFragment(`/properties/value/${node.$ref.slice(1)}`);\n            }\n        }\n    });\n    schema = {\n        type: 'object',\n        properties: {\n            value: schema,\n        },\n    };\n\n    const ajv = new Ajv({\n        useDefaults: true,\n        coerceTypes: allowTypeCoercion ? 'array' : false,\n        removeAdditional: allowTypeCoercion ? 'failing' : false,\n        allErrors: schemaContext.options.allErrors,\n        strict: schemaContext.options.strictValidation,\n    });\n\n    for (const key of Object.keys(customFormats)) {\n        ajv.addFormat(key, customFormats[key]);\n    }\n\n    const getValidate = createValidateGetter(\n        schema,\n        ajv,\n        schemaContext.options.lazyCompileValidationSchemas\n    );\n\n    return function (json: any) {\n        return doValidate(schemaPtr, parameterLocation, parameterRequired, getValidate, json);\n    };\n}\n\nexport function generateRequestValidator(\n    schemaContext: Oas3CompileContext,\n    parameterLocation: ParameterLocation,\n    parameterRequired: boolean,\n    mediaType: string\n): ValidatorFunction {\n    const allowTypeCoercion = mediaType\n        ? REQUEST_TYPE_COERCION_ALLOWED.get(mediaType) || false\n        : false;\n    return generateValidator(\n        schemaContext,\n        parameterLocation,\n        parameterRequired,\n        'readOnly',\n        allowTypeCoercion\n    );\n}\n\nexport function generateResponseValidator(\n    schemaContext: Oas3CompileContext,\n    parameterLocation: ParameterLocation,\n    parameterRequired: boolean\n): ValidatorFunction {\n    return generateValidator(\n        schemaContext,\n        parameterLocation,\n        parameterRequired,\n        'writeOnly',\n        false\n    );\n}\n"
  },
  {
    "path": "src/oas3/SecuritySchemes.ts",
    "content": "import ld from 'lodash';\nimport * as oas3 from 'openapi3-ts';\nimport { AuthenticatorInfo } from '..';\nimport { resolveRef } from '../utils/json-schema-resolve-ref';\n\nexport default class SecuritySchemes {\n    private readonly _securitySchemes: { [securityScheme: string]: oas3.SecuritySchemeObject };\n    private readonly _challenges: { [securityScheme: string]: string | undefined };\n    private readonly _infos: { [securityScheme: string]: AuthenticatorInfo };\n\n    constructor(openApiDoc: oas3.OpenAPIObject) {\n        const securitySchemes =\n            (openApiDoc.components && openApiDoc.components.securitySchemes) || {};\n        this._securitySchemes = securitySchemes.ref\n            ? resolveRef(openApiDoc, securitySchemes)\n            : securitySchemes;\n\n        this._challenges = ld.mapValues(this._securitySchemes, (scheme) => {\n            if (scheme.type === 'http') {\n                return scheme.scheme || 'Basic';\n            }\n            if (scheme.type === 'oauth2' || scheme.type === 'openIdConnect') {\n                return 'Bearer';\n            }\n            return undefined;\n        });\n\n        this._infos = ld.mapValues(this._securitySchemes, (scheme) => {\n            if (scheme.type === 'apiKey') {\n                return {\n                    in: scheme.in as any,\n                    name: scheme.name,\n                };\n            } else if (scheme.type === 'http') {\n                return {\n                    scheme: scheme.scheme,\n                };\n            } else {\n                return {};\n            }\n        });\n    }\n\n    getChallenge(schemeName: string): string | undefined {\n        return this._challenges[schemeName];\n    }\n\n    getInfo(schemeName: string): AuthenticatorInfo {\n        return this._infos[schemeName];\n    }\n}\n"
  },
  {
    "path": "src/oas3/Servers.ts",
    "content": "import * as oas3 from 'openapi3-ts';\nimport { compileTemplatePath, PathParserFunction } from './Paths/PathResolver';\n\nimport { ParametersMap } from '../types';\n\nconst FULL_URL_RE = /^(.*?):\\/\\/([^/]*?)(?:\\/(.*))?$/; // e.g. https://foo.bar/v1\nconst ABSOLUTE_URL_RE = /^(\\/.*)$/; // e.g. /v1\n\nexport interface ResolvedServer {\n    /**\n     * The server definition that was matched from the `servers`\n     * section of the OpenAPI document.\n     */\n    oaServer: oas3.ServerObject;\n    /**\n     * The values of any template parameters defined in\n     * the `server.url` of the matching `server` object.\n     */\n    serverParams: ParametersMap<string | string[]>;\n    /** The unmatched portion of the `pathname`. */\n    pathnameRest: string;\n    /** The matched portion of the `pathname`. */\n    baseUrl: string;\n}\n\ntype ServerParser = (host: string, pathname: string) => ResolvedServer | null;\n\nfunction generateServerParser(oaServer: oas3.ServerObject): ServerParser {\n    // Strip trailing '/'.\n    const serverUrl = oaServer.url.endsWith('/')\n        ? oaServer.url.slice(0, oaServer.url.length - 1)\n        : oaServer.url;\n\n    let result: ServerParser;\n    let match;\n\n    if (serverUrl === '') {\n        result = (_host, pathname) => ({\n            oaServer,\n            pathnameRest: pathname,\n            serverParams: Object.create(null),\n            baseUrl: '',\n        });\n    } else if ((match = FULL_URL_RE.exec(serverUrl))) {\n        const hostname = match[2];\n        const basepath = match[3] || '';\n        const { parser: hostnameAcceptFunction } = compileTemplatePath(hostname);\n        let basepathAcceptFunction: PathParserFunction;\n        if (basepath) {\n            basepathAcceptFunction = compileTemplatePath('/' + basepath, { openEnded: true })\n                .parser;\n        } else {\n            basepathAcceptFunction = () => ({\n                matched: '',\n                rawPathParams: Object.create(null),\n            });\n        }\n\n        result = (host, pathname) => {\n            const hostMatch = hostnameAcceptFunction(host);\n            const pathMatch = basepathAcceptFunction(pathname);\n            if (hostMatch && pathMatch) {\n                return {\n                    oaServer,\n                    pathnameRest: pathname.slice(pathMatch.matched.length),\n                    serverParams: Object.assign(\n                        {},\n                        hostMatch.rawPathParams,\n                        pathMatch.rawPathParams\n                    ),\n                    baseUrl: pathMatch.matched,\n                };\n            } else {\n                return null;\n            }\n        };\n    } else if ((match = ABSOLUTE_URL_RE.exec(serverUrl))) {\n        const basepath = match[1];\n        const { parser: basepathParser } = compileTemplatePath(basepath, { openEnded: true });\n\n        result = (_host, pathname) => {\n            const pathMatch = basepathParser(pathname);\n            if (pathMatch) {\n                return {\n                    oaServer,\n                    serverParams: pathMatch.rawPathParams,\n                    pathnameRest: pathname.slice(pathMatch.matched.length),\n                    baseUrl: pathMatch.matched,\n                };\n            } else {\n                return null;\n            }\n        };\n    } else {\n        // TODO: deal with relative URLs.\n        throw new Error(`Don't know how to deal with server URL ${oaServer.url}`);\n    }\n\n    return result;\n}\n\nexport default class Servers {\n    private readonly _servers: ServerParser[];\n\n    constructor(servers: oas3.ServerObject[] | undefined) {\n        servers = servers || [{ url: '/' }];\n        this._servers = servers.map((server) => generateServerParser(server));\n    }\n\n    /**\n     * Resolve the `server` that's being accessed.\n     *\n     * @param host - The hostname to match.\n     * @param pathname - The URL pathname to match.\n     * @returns If a matching `server` is found, returns a\n     * `ResolvedServer` object.  Returns `null` if no match was found.\n     */\n    resolveServer(host: string, pathname: string): ResolvedServer | null {\n        for (const server of this._servers) {\n            const result = server(host, pathname);\n            if (result) {\n                return result;\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/oas3/extensions.ts",
    "content": "export const EXEGESIS_CONTROLLER = 'x-exegesis-controller';\nexport const EXEGESIS_OPERATION_ID = 'x-exegesis-operationId';\n"
  },
  {
    "path": "src/oas3/index.ts",
    "content": "import oas3 from 'openapi3-ts';\n\nimport OpenApi from './OpenApi';\nimport { ExegesisCompiledOptions } from '../options';\n\nexport { OpenApi };\n\n/**\n * Reads an OpenAPI document from a YAML or JSON file.\n *\n * @param openApiDocFile - The file containing the OpenAPI document.\n * @returns - Returns the parsed OpenAPI document.\n */\nexport async function compile(\n    openApiDoc: oas3.OpenAPIObject,\n    options: ExegesisCompiledOptions\n): Promise<OpenApi> {\n    return new OpenApi(openApiDoc, options);\n}\n"
  },
  {
    "path": "src/oas3/oasUtils/index.ts",
    "content": "import { MimeTypeRegistry } from '../../utils/mime';\nimport RequestMediaType from '../RequestMediaType';\n\nimport * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from '../Oas3CompileContext';\nimport { ParameterLocation } from '../..';\n\nexport function isSpecificationExtension(key: string) {\n    return key.startsWith('x-');\n}\n\nexport function isReferenceObject(obj: any): obj is oas3.ReferenceObject {\n    return !!obj.$ref;\n}\n\n/**\n *\n * @param openApiDoc - The openApiDocument this `content` object is from.\n * @param path - The path to the `content` object.\n * @param content - The `content` object.\n */\nexport function contentToRequestMediaTypeRegistry(\n    context: Oas3CompileContext,\n    parameterLocation: ParameterLocation,\n    parameterRequired: boolean,\n    content?: oas3.ContentObject\n) {\n    const answer = new MimeTypeRegistry<RequestMediaType>();\n\n    if (content) {\n        for (const mediaType of Object.keys(content)) {\n            const oaMediaType = content[mediaType];\n            answer.set(\n                mediaType,\n                new RequestMediaType(\n                    context.childContext(mediaType),\n                    oaMediaType,\n                    mediaType,\n                    parameterLocation,\n                    parameterRequired\n                )\n            );\n        }\n    }\n\n    return answer;\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/README.md",
    "content": "# Parameter Parsing\n\nCookie parameters are not well defined by OAS3, so at the moment Exegesis\ndoesn't support them. See discussion\n[here](https://github.com/OAI/OpenAPI-Specification/issues/1528). If you\nreally need cookie parameters, please raise an issue.\n\nParameters in OAS3 come in two different flavors; parameters with a\n\"content-type\" which are parsed using a custom string parser (e.g. an\n\"application/json\" parameter) and parameters that are parsed using a \"style\".\nThis document covers styled parameters.\n\n## OAS3 Styles\n\n- matrix - Path-style expansion from RFC6570 - `{;var}`\n- form - Form-style Query expansion from RFC6570 - `{?var}`\n- label - Label expansion from RFC6570 - `{.var}`\n- simple - Simple string expansion from RFC6570 - `{var}`\n- spaceDelimited\n- pipeDelimited\n- deepObject - `qs` style form parameters.\n\nAllowed styles by parameter location:\n\n- path: 'matrix', 'label', 'simple'\n- query: 'form', 'spaceDelimited', pipeDelimited', 'deepObject'\n- cookie: 'form' ???\n- header: 'simple'\n\nFor `query` we also have to worry about 'allowEmptyValue' and 'allowReserved'.\nIt's not clear to me that these have any effect on the parsing side, though.\n\nTo parse a given parameter, we need to know:\n\n- The parameter name.\n- The style of the parameter.\n- If the parameter is exploded.\n- The type or types we are allowed to decode the parameter to (some combination\n  of \"string\", \"array\", \"object\" - we don't worry about numbers or integers -\n  strings can be converted to these other basic types).\n- For exploded parameters, it would be nice to have a list of expected property\n  names.\n\n## Implementation\n\nWhile OAS3 is largely based on RFC6570, aside from the query string, OAS3 never\nallows us to have two variables in the same expression (e.g. in URI Templates\nyou can do `{foo,bar}`).\n\nWe do a pre-processing step and extract all the variables out of the path\ninto a dictionary where variables are keyed by name (wouldn't be able to do this\nwith full RF6570 templates, because you can have multiple variables in the\nsame expression e.g. `/path/{foo,bar}`). We extract all the querystring\nvariables out by passing the querystring through a querty string parser (although\nwe do _not_ call `decodeURIComponent()` on values, since pct-encoded \",\"s are\ndifferent from \",\"s for RFC6570 expansions). node.js will extract all the\nheaders for us in a similar fashion.\n\nFor most cases, a parameter parser could take in just a string or array of\nstrings. Path-style expansions and query-style expansions throw a wrench into\nthis, however, because an exploded parameter will need to read values from keys\nother than the parameter name.\n\nSo a parameter parser is a function of the form:\n\n```js\n    function(name, rawValues, querystring)\n```\n\nGiven a function which just takes in a string or array of strings, we can\nconvert that into the above function trivially, so where possible parameter\nparsers just take in the raw value for their name.\n\nParsers always try to return _something_. If we're given a query parameter\nthat can only be a string, but the query parameter is in the query string more\nthan once, we'll produce an array of strings, and validation will take care of\nrealizing that something is wrong. Parsers can also return a \"string\" when\nan array is wanted, and we'll do type cooercion to fix the array after the fact.\n"
  },
  {
    "path": "src/oas3/parameterParsers/common.ts",
    "content": "import ld from 'lodash';\n\nexport function arrayToObject(values: string | string[] | undefined) {\n    if (!values) {\n        return values;\n    }\n\n    if (typeof values === 'string') {\n        // ???\n        return values;\n    }\n\n    const result: any = {};\n    for (let i = 0; i < values.length; i = i + 2) {\n        // Note if the array is an odd length, we'll end up with an \"undefined\" parameter at the end.\n        result[values[i]] = values[i + 1];\n    }\n\n    return result;\n}\n\n// Converts all simple types that are not \"string\" into \"string\".\nexport function removeSimpleTypes(allowedTypes: string[]) {\n    return ld.uniq(\n        allowedTypes.map((t) => {\n            if (t === 'object') {\n                return 'object';\n            } else if (t === 'array') {\n                return 'array';\n            } else {\n                return 'string';\n            }\n        })\n    );\n}\n\nexport function allowedTypesToMap(allowedTypes: string[]): any {\n    return allowedTypes.reduce<any>((m, t) => {\n        m[t] = true;\n        return m;\n    }, {});\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/delimitedParser.ts",
    "content": "// Implements 'spaceDelimited' and 'pipeDelimited' from OAS 3.\n\nimport { RawValues } from './types';\nimport * as exegesisTypes from '../../types';\n\nexport function generateDelimitedParser(delimiter: string) {\n    return function delimitedParser(\n        location: exegesisTypes.ParameterLocation,\n        rawParamValues: RawValues\n    ): string[] | undefined {\n        const value = rawParamValues[location.name];\n        if (value === null || value === undefined) {\n            return value;\n        } else if (Array.isArray(value)) {\n            // Client is supposed to send us a delimited string, but it looks\n            // like they sent us multiple copies of the var instead.  Just\n            // decode the array.\n            return value.map(decodeURIComponent);\n        } else {\n            return decodeURIComponent(value).split(delimiter);\n        }\n    };\n}\n\nexport const pipeDelimitedParser = generateDelimitedParser('|');\nexport const spaceDelimitedParser = generateDelimitedParser(' ');\n"
  },
  {
    "path": "src/oas3/parameterParsers/index.ts",
    "content": "import ld from 'lodash';\nimport querystring from 'querystring';\nimport qs from 'qs';\n\nimport { ParametersMap, ParameterLocation } from '../../types';\nimport { ValidationError } from '../../errors';\nimport { pipeDelimitedParser, spaceDelimitedParser } from './delimitedParser';\nimport { generateStructuredParser } from './structuredParser';\nimport { getSimpleStringParser } from './simpleStringParser';\nimport { generatePathStyleParser } from './pathStyleParser';\nimport {\n    RawStringParameterParser,\n    ParameterParser,\n    RawValues,\n    ParameterDescriptor,\n    MediaTypeParameterDescriptor,\n    StyledParameterDescriptor,\n} from './types';\n\nexport * from './types';\n\nfunction isMediaTypeParameterDescriptor(\n    descriptor: ParameterDescriptor\n): descriptor is MediaTypeParameterDescriptor {\n    return descriptor && (descriptor as any).contentType && (descriptor as any).parser;\n}\n\nexport function generateParser(parameterDescriptor: ParameterDescriptor): ParameterParser {\n    let answer: ParameterParser;\n    if (isMediaTypeParameterDescriptor(parameterDescriptor)) {\n        answer = generateMediaTypeParser(parameterDescriptor);\n    } else {\n        answer = generateStyleParser(parameterDescriptor);\n    }\n    return answer;\n}\n\nfunction generateMediaTypeParser(\n    parameterDescriptor: MediaTypeParameterDescriptor\n): ParameterParser {\n    // request and response are here for application/x-www-form-urlencoded.\n\n    let answer: ParameterParser = (location: ParameterLocation, values: RawValues): any => {\n        try {\n            let value = values[location.name];\n            if (value === undefined || value === null) {\n                return value;\n            }\n\n            if (parameterDescriptor.uriEncoded) {\n                if (Array.isArray(value)) {\n                    value = value.map(decodeURIComponent);\n                } else {\n                    value = decodeURIComponent(value);\n                }\n            }\n\n            if (Array.isArray(value)) {\n                return value.map((v) => parameterDescriptor.parser.parseString(v));\n            } else {\n                return parameterDescriptor.parser.parseString(value);\n            }\n        } catch (err) {\n            throw new ValidationError({\n                message:\n                    `Error parsing parameter ${location.name} of ` +\n                    `type ${parameterDescriptor.contentType}: ${err}`,\n                location,\n            });\n        }\n    };\n\n    if (parameterDescriptor.schema && parameterDescriptor.schema.default) {\n        answer = setDefault(answer, parameterDescriptor.schema.default);\n    }\n    return answer;\n}\n\nfunction generateStyleParser(descriptor: StyledParameterDescriptor) {\n    const { schema, explode } = descriptor;\n    let answer: ParameterParser;\n\n    switch (descriptor.style) {\n        case 'simple':\n            answer = toStructuredParser(getSimpleStringParser(schema, explode));\n            break;\n        case 'form':\n            answer = generateStructuredParser(schema, explode);\n            break;\n        case 'matrix':\n            answer = generatePathStyleParser(schema, explode);\n            break;\n        case 'spaceDelimited':\n            answer = spaceDelimitedParser;\n            break;\n        case 'pipeDelimited':\n            answer = pipeDelimitedParser;\n            break;\n        case 'deepObject':\n            answer = deepObjectParser;\n            break;\n        default:\n            throw new Error(`Don't know how to parse parameters with style ${descriptor.style}`);\n    }\n\n    if ('default' in descriptor.schema) {\n        answer = setDefault(answer, descriptor.schema.default);\n    }\n    return answer;\n}\n\nfunction setDefault(parser: ParameterParser, def: any) {\n    return function addDefault(\n        location: ParameterLocation,\n        rawParamValues: RawValues,\n        rawValue: string,\n        parserContext: any\n    ) {\n        const answer = parser(location, rawParamValues, rawValue, parserContext);\n        if (answer !== undefined) {\n            return answer;\n        } else {\n            return ld.cloneDeep(def);\n        }\n    };\n}\n\nfunction toStructuredParser(parser: RawStringParameterParser) {\n    return (location: ParameterLocation, rawParamValues: RawValues) => {\n        const value = rawParamValues[location.name];\n        if (Array.isArray(value)) {\n            return value.map(parser);\n        } else {\n            return parser(value);\n        }\n    };\n}\n\nfunction deepObjectParser(\n    location: ParameterLocation,\n    _rawParamValues: RawValues,\n    rawValue: string,\n    parserContext: any\n): any {\n    if (!parserContext.qsParsed) {\n        parserContext.qsParsed = qs.parse(rawValue);\n    }\n    const qsParsed = parserContext.qsParsed;\n    return qsParsed[location.name];\n}\n\nfunction _parseParameterGroup(\n    params: {\n        location: ParameterLocation;\n        parser: ParameterParser;\n    }[],\n    rawValues: RawValues,\n    rawQueryString: string\n): ParametersMap<any> {\n    const parserContext = {};\n    return params.reduce((result: any, { location, parser }) => {\n        result[location.name] = parser(location, rawValues, rawQueryString, parserContext);\n        return result;\n    }, {});\n}\n\nexport function parseParameterGroup(\n    params: {\n        location: ParameterLocation;\n        parser: ParameterParser;\n    }[],\n    rawValues: RawValues\n): ParametersMap<any> {\n    return _parseParameterGroup(params, rawValues, '');\n}\n\nexport function parseQueryParameters(\n    params: {\n        location: ParameterLocation;\n        parser: ParameterParser;\n    }[],\n    query: string | undefined\n) {\n    const rawValues = querystring.parse(query || '', '&', '=', {\n        decodeURIComponent: (val: string) => val,\n    });\n    return _parseParameterGroup(params, rawValues, query || '');\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/pathStyleParser.ts",
    "content": "import querystring from 'querystring';\n\nimport { ParameterParser, RawValues } from './types';\nimport { generateStructuredParser } from './structuredParser';\nimport { ParameterLocation } from '../../types';\n\nfunction parsePathParameter(\n    location: ParameterLocation,\n    value: string,\n    structuredParser: ParameterParser\n) {\n    if (value.startsWith(';')) {\n        value = value.slice(1);\n    }\n    const queryParsedValue = querystring.parse(value, ';', '=', {\n        decodeURIComponent: (val: string) => val,\n    });\n    return structuredParser(location, queryParsedValue, value, {});\n}\n\nexport function generatePathStyleParser(schema: any, explode: boolean): ParameterParser {\n    const structuredParser = generateStructuredParser(schema, explode);\n\n    return function pathStyleParser(location: ParameterLocation, rawParamValues: RawValues): any {\n        const value = rawParamValues[location.name];\n        let answer;\n        if (value === null || value === undefined) {\n            answer = value;\n        } else if (Array.isArray(value)) {\n            // This will never happen, since \"matrix\" parameters are only\n            // allowed in the path, and no one is going to define some\n            // crazy path like \"/foo/{bar}/{bar}\".\n            answer = value.map((v) => parsePathParameter(location, v, structuredParser));\n        } else {\n            answer = parsePathParameter(location, value, structuredParser);\n        }\n\n        return answer;\n    };\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/simpleStringParser.ts",
    "content": "import inferTypes from '../../utils/json-schema-infer-types';\nimport { arrayToObject, removeSimpleTypes, allowedTypesToMap } from './common';\nimport { RawStringParameterParser } from './types';\n\nexport function getSimpleStringParser(schema: any, explode: boolean): RawStringParameterParser {\n    const allowedTypes = removeSimpleTypes(inferTypes(schema));\n\n    if (allowedTypes.length === 1 && allowedTypes[0] === 'string') {\n        return simpleStringParser;\n    } else if (allowedTypes.length === 1 && allowedTypes[0] === 'array') {\n        return simpleArrayParser;\n    } else if (\n        allowedTypes.includes('string') &&\n        allowedTypes.includes('array') &&\n        !allowedTypes.includes('object')\n    ) {\n        return simpleStringArrayParser;\n    } else {\n        return generateGenericSimpleParser(schema, explode);\n    }\n}\n\n// This is for the case where the result is only allowed to be a string.\nexport function simpleStringParser(value: string | undefined): string | string[] | undefined {\n    return !value ? value : decodeURIComponent(value);\n}\n\n// This is for the case where the result allowed to be a string or an array.\nexport function simpleArrayParser(value: string | undefined): string[] | undefined {\n    return value === undefined || value === null ? value : value.split(',').map(decodeURIComponent);\n}\n\nexport function simpleStringArrayParser(value: string | undefined): string | string[] | undefined {\n    const result = simpleArrayParser(value);\n    if (!result) {\n        return result;\n    } else if (result.length === 0) {\n        return '';\n    } else if (result.length === 1) {\n        return result[0];\n    } else {\n        return result;\n    }\n}\n\nexport function generateGenericSimpleParser(schema: any, explode: boolean) {\n    const allowedTypes = removeSimpleTypes(inferTypes(schema));\n    const allowedTypesMap = allowedTypesToMap(allowedTypes);\n\n    return function genericSimplerParser(value: string | undefined): any {\n        const result = simpleArrayParser(value);\n        if (result === null || result === undefined) {\n            return value;\n        } else if (result.length === 0 && allowedTypesMap.string) {\n            return '';\n        } else if (result.length === 1 && allowedTypesMap.string) {\n            return result[0];\n        } else if (allowedTypesMap.array) {\n            return result;\n        } else if (!explode) {\n            // Has to be object\n            return arrayToObject(result);\n        } else {\n            // Exploded object\n            return result.reduce((object: any, pair: string) => {\n                const [k, v] = pair.split('=');\n                object[decodeURIComponent(k)] = decodeURIComponent(v);\n                return object;\n            }, {});\n        }\n    };\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/structuredParser.ts",
    "content": "import ld from 'lodash';\n\nimport { ParameterParser, RawValues } from './types';\nimport { removeSimpleTypes, allowedTypesToMap } from './common';\nimport inferTypes from '../../utils/json-schema-infer-types';\nimport {\n    simpleStringParser,\n    simpleArrayParser,\n    generateGenericSimpleParser,\n} from './simpleStringParser';\nimport { ParameterLocation } from '../../types';\n\n/**\n * A structured parser is a parser that handles RFC6570 path-style and\n * form-style query expansions.\n *\n * @param schema - The JSON Schema this parser is expecting.\n * @param explode - True if this is a parser for an \"exploded\" expansion.\n */\nexport function generateStructuredParser(schema: any, explode: boolean): ParameterParser {\n    const allowedTypes = removeSimpleTypes(inferTypes(schema));\n\n    if (allowedTypes.length === 1 && allowedTypes[0] === 'string') {\n        return structuredStringParser;\n    } else if (allowedTypes.length === 1 && allowedTypes[0] === 'array') {\n        if (explode) {\n            return explodedStructuredArrayParser;\n        } else {\n            return structuredArrayParser;\n        }\n    } else if (!explode) {\n        return generateGenericStructuredParser(schema);\n    } else {\n        return generateGenericExplodedStructuredParser(schema);\n    }\n}\n\nexport function structuredStringParser(\n    location: ParameterLocation,\n    rawParamValues: RawValues\n): string | string[] | undefined {\n    const value = rawParamValues[location.name];\n    if (!value) {\n        return value;\n    } else if (Array.isArray(value)) {\n        // This is supposed to be a string.  -_-\n        return value.map(decodeURIComponent);\n    } else {\n        return value ? simpleStringParser(value) : value;\n    }\n}\n\nexport function structuredArrayParser(\n    location: ParameterLocation,\n    rawParamValues: RawValues\n): string | string[] | undefined {\n    const value = rawParamValues[location.name];\n    if (value === undefined || value === null) {\n        return value;\n    } else if (Array.isArray(value)) {\n        // We *should* not receive multiple form headers.  If this happens,\n        // it's probably because the client used explode when they shouldn't\n        // have.\n        return explodedStructuredArrayParser(location, rawParamValues);\n    } else {\n        return value ? simpleArrayParser(value) : value;\n    }\n}\n\nexport function explodedStructuredArrayParser(\n    location: ParameterLocation,\n    rawParamValues: RawValues\n): string | string[] | undefined {\n    const value = rawParamValues[location.name];\n    if (value === undefined || value === null) {\n        return value;\n    } else if (Array.isArray(value)) {\n        return value.map(decodeURIComponent);\n    } else {\n        return [decodeURIComponent(value)];\n    }\n}\n\nfunction generateGenericStructuredParser(schema: any): ParameterParser {\n    const genericSimpleParser = generateGenericSimpleParser(schema, false);\n\n    return function genericStructuredParser(\n        location: ParameterLocation,\n        rawParamValues: RawValues\n    ): any {\n        const value = rawParamValues[location.name];\n        if (value === undefined || value === null) {\n            return value;\n        }\n        if (Array.isArray(value)) {\n            // Unexploded parameters should not be an array.  Parse each member\n            // of the array, and return an array of arrays?\n            return value.map(genericSimpleParser);\n        }\n        return genericSimpleParser(value);\n    };\n}\n\nfunction explodedKeysStructuredParser(values: RawValues) {\n    return ld.mapValues(values, (v) => {\n        if (Array.isArray(v)) {\n            return v.map(decodeURIComponent);\n        } else if (v) {\n            return decodeURIComponent(v);\n        } else {\n            return v;\n        }\n    });\n}\n\nfunction generateGenericExplodedStructuredParser(schema: any) {\n    const allowedTypes = removeSimpleTypes(inferTypes(schema));\n    const allowedTypesMap = allowedTypesToMap(allowedTypes);\n\n    return function genericStructuredParser(\n        location: ParameterLocation,\n        rawParamValues: RawValues\n    ): any {\n        const value = rawParamValues[location.name];\n        if (value === undefined || value === null) {\n            if (allowedTypesMap.object) {\n                // TODO: Could use a list of allowed parameters to control what we return here.\n                return explodedKeysStructuredParser(rawParamValues);\n            } else {\n                return value;\n            }\n        }\n\n        // We have a parameter with the same name as the one we're looking for - probably not an object.\n        if (Array.isArray(value)) {\n            if (!allowedTypesMap.array) {\n                return explodedKeysStructuredParser(rawParamValues);\n            } else {\n                return value.map(simpleStringParser);\n            }\n        } else if (allowedTypesMap.string) {\n            return simpleStringParser(value);\n        } else if (allowedTypesMap.array) {\n            return [simpleStringParser(value)];\n        } else {\n            return explodedKeysStructuredParser(rawParamValues);\n        }\n    };\n}\n"
  },
  {
    "path": "src/oas3/parameterParsers/types.ts",
    "content": "import { ParametersMap, ParameterLocation, StringParser } from '../../types';\n\n/**\n * A descriptor for a parameter that has a \"style\".\n */\nexport interface StyledParameterDescriptor {\n    style: string;\n    explode: boolean;\n    schema: any;\n    required?: boolean;\n    allowReserved?: boolean;\n}\n\n/**\n * A descriptor for a parameter that has a content type associated with it.\n * This could be, for example, a Parameter Object with a `content`, or an\n * Encoding Object with a `contentType`.\n */\nexport interface MediaTypeParameterDescriptor {\n    contentType: string;\n    schema?: any;\n    parser: StringParser;\n    required?: boolean;\n    uriEncoded?: boolean;\n}\n\nexport type ParameterDescriptor = StyledParameterDescriptor | MediaTypeParameterDescriptor;\n\n/**\n * A dictionary where names are parameter names, and values are unparsed strings\n * (or arrays of strings for \"exploded\" parameters, where a parameter appears\n * multiple times in a query string, or there are multiple headers with the\n * given name.)\n *\n * When coming from a path or a query string, strings may contain pct-encoded\n * characters.\n */\nexport type RawValues = ParametersMap<string | string[] | undefined>;\n\n/**\n * A parameter parser that takes in a string and returns a value.\n */\nexport interface RawStringParameterParser {\n    (value: string | undefined): any;\n}\n\n/**\n * A parameter parser that takes in a RawValues dictionary and produces a\n * result.  Generally `ParameterParser` will operation on\n * `rawParamValues[location.name]`.\n */\nexport interface ParameterParser {\n    (\n        location: ParameterLocation,\n        rawParamValues: RawValues,\n        rawValue: string,\n        parserContext: any\n    ): any;\n}\n"
  },
  {
    "path": "src/oas3/urlEncodedBodyParser.ts",
    "content": "import * as oas3 from 'openapi3-ts';\nimport * as jsonPtr from 'json-ptr';\nimport querystring from 'querystring';\nimport BodyParserWrapper from '../bodyParsers/BodyParserWrapper';\nimport { MimeTypeParser, ParameterLocation, StringParser } from '../types';\nimport { resolveRef } from '../utils/json-schema-resolve-ref';\nimport { extractSchema } from '../utils/jsonSchema';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport {\n    ParameterDescriptor,\n    ParameterParser,\n    generateParser,\n    parseQueryParameters,\n} from './parameterParsers';\n\n// OAS3 has special handling for 'application/x-www-form-urlencoded'.  Parameters\n// and bodies of this type are allowed to define an `encoding` section with\n// special treatment for specific properties.  This handles generating a parser\n// for this content-type.\n\n// Find a property in a JSON Schema.\nfunction findProperty(\n    path: string[],\n    schema: oas3.SchemaObject,\n    propertyName: string\n): string[] | undefined {\n    if (schema.properties && schema.properties[propertyName]) {\n        return path;\n    }\n\n    const allOf = schema.allOf || [];\n    for (let index = 0; index < allOf.length; index++) {\n        const childSchema = resolveRef(schema, allOf[index]);\n        const answer = findProperty(path.concat(['allOf', `${index}`]), childSchema, propertyName);\n        if (answer) {\n            return answer;\n        }\n    }\n\n    return undefined;\n}\n\nexport function generateStringParser(\n    context: Oas3CompileContext,\n    mediaType: oas3.MediaTypeObject,\n    parameterLocation: ParameterLocation\n): StringParser {\n    const parameterParsers: {\n        location: ParameterLocation;\n        parser: ParameterParser;\n    }[] = [];\n\n    if (mediaType.encoding) {\n        if (!mediaType.schema) {\n            throw new Error(\n                `Media Type Object ${context.jsonPointer} with 'content' must have a 'schema'`\n            );\n        }\n\n        // Find the schema object for the mediaType.\n        const schema = resolveRef(context.openApiDoc, `${context.jsonPointer}/schema`);\n\n        // The encoding object describes how parameters should be parsed from the document.\n        for (const parameterName of Object.keys(mediaType.encoding)) {\n            const encoding: oas3.EncodingPropertyObject = mediaType.encoding[parameterName];\n\n            const parameterSchemaPath = findProperty([], schema, parameterName);\n            if (!parameterSchemaPath) {\n                throw new Error(\n                    `Cannot find parameter ${parameterName} in schema for ${context.jsonPointer}`\n                );\n            }\n\n            const parameterSchema = extractSchema(\n                context.openApiDoc,\n                jsonPtr.encodePointer(parameterSchemaPath)\n            );\n\n            let parameterDescriptor: ParameterDescriptor;\n            if (encoding.contentType) {\n                const parser = context.options.parameterParsers.get(encoding.contentType);\n                if (!parser) {\n                    throw new Error(\n                        `No string parser found for ${encoding.contentType} in ${context.jsonPointer}`\n                    );\n                }\n                parameterDescriptor = {\n                    contentType: encoding.contentType,\n                    parser,\n                    uriEncoded: true,\n                    schema: parameterSchema,\n                };\n            } else {\n                parameterDescriptor = {\n                    style: encoding.style || 'form',\n                    explode: encoding.explode || false,\n                    allowReserved: encoding.allowReserved || false,\n                    schema: parameterSchema,\n                };\n            }\n\n            parameterParsers.push({\n                location: {\n                    in: parameterLocation.in,\n                    name: parameterName,\n                    docPath: context.childContext(['encoding', parameterName]).jsonPointer,\n                },\n                parser: generateParser(parameterDescriptor),\n            });\n        }\n    }\n\n    return {\n        parseString(encoded: string): any {\n            const rawResult = querystring.parse(encoded);\n            const parsedResult = parseQueryParameters(parameterParsers, encoded);\n            return Object.assign(rawResult, parsedResult);\n        },\n    };\n}\n\nexport function generateBodyParser(\n    context: Oas3CompileContext,\n    mediaType: oas3.MediaTypeObject,\n    parameterLocation: ParameterLocation\n): MimeTypeParser {\n    const stringParser = generateStringParser(context, mediaType, parameterLocation);\n    return new BodyParserWrapper(stringParser, context.options.defaultMaxBodySize);\n}\n"
  },
  {
    "path": "src/options.ts",
    "content": "import ajvFormats from 'ajv-formats';\nimport ld from 'lodash';\nimport BodyParserWrapper from './bodyParsers/BodyParserWrapper';\nimport JsonBodyParser from './bodyParsers/JsonBodyParser';\nimport TextBodyParser from './bodyParsers/TextBodyParser';\nimport { loadControllersSync } from './controllers/loadControllers';\nimport {\n    Authenticators,\n    BodyParser,\n    Controllers,\n    CustomFormats,\n    ExegesisOptions,\n    MimeTypeParser,\n    ResponseValidationCallback,\n    StringParser,\n} from './types';\nimport {\n    HandleErrorFunction,\n    NumberCustomFormatChecker,\n    StringCustomFormatChecker,\n    CustomFormatChecker,\n} from './types/options';\nimport { MimeTypeRegistry } from './utils/mime';\n\nexport interface ExegesisCompiledOptions {\n    customFormats: CustomFormats;\n    controllers: Controllers;\n    authenticators: Authenticators;\n    bodyParsers: MimeTypeRegistry<BodyParser>;\n    parameterParsers: MimeTypeRegistry<StringParser>;\n    defaultMaxBodySize: number;\n    ignoreServers: boolean;\n    allowMissingControllers: boolean;\n    autoHandleHttpErrors: boolean | HandleErrorFunction;\n    onResponseValidationError: ResponseValidationCallback;\n    validateDefaultResponses: boolean;\n    allErrors: boolean;\n    treatReturnedJsonAsPure: boolean;\n    strictValidation: boolean;\n    lazyCompileValidationSchemas: boolean;\n}\n\n// See the OAS 3.0 specification for full details about supported formats:\n//      https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#data-types\nconst defaultValidators: CustomFormats = {\n    // TODO: Support async validators so we don't need all this casting.\n    int32: ajvFormats.get('int32') as NumberCustomFormatChecker,\n    int64: ajvFormats.get('int64') as NumberCustomFormatChecker,\n    double: ajvFormats.get('double') as NumberCustomFormatChecker,\n    float: ajvFormats.get('float') as NumberCustomFormatChecker,\n    // Nothing to do for 'password'; this is just a hint for docs.\n    password: () => true,\n    // Impossible to validate \"binary\".\n    binary: () => true,\n    byte: ajvFormats.get('byte') as RegExp,\n    // Not defined by OAS 3, but it's used throughout OAS 3.0.1, so we put it\n    // here as an alias for 'byte' just in case.\n    base64: ajvFormats.get('byte') as RegExp,\n    // Various formats we're supposed to support per the JSON Schema RFC.\n    // https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-7.3\n    date: ajvFormats.get('date') as CustomFormatChecker,\n    time: ajvFormats.get('time') as StringCustomFormatChecker,\n    'date-time': ajvFormats.get('date-time') as StringCustomFormatChecker,\n    duration: ajvFormats.get('duration') as RegExp,\n};\n\nexport function compileOptions(options: ExegesisOptions = {}): ExegesisCompiledOptions {\n    const maxBodySize = options.defaultMaxBodySize || 100000;\n\n    const mimeTypeParsers = Object.assign(\n        {\n            'text/*': new TextBodyParser(maxBodySize),\n            'application/json': new JsonBodyParser(maxBodySize),\n        },\n        options.mimeTypeParsers || {}\n    );\n\n    const wrappedBodyParsers = ld.mapValues(\n        mimeTypeParsers,\n        (p: StringParser | BodyParser | MimeTypeParser) => {\n            if ('parseReq' in p) {\n                return p;\n            } else if ('parseString' in p) {\n                return new BodyParserWrapper(p, maxBodySize);\n            } else {\n                return undefined;\n            }\n        }\n    );\n    const bodyParsers = new MimeTypeRegistry<BodyParser>(wrappedBodyParsers);\n\n    const parameterParsers = new MimeTypeRegistry<StringParser>(\n        ld.pickBy(mimeTypeParsers, (p: any) => !!p.parseString) as {\n            [mimeType: string]: StringParser;\n        }\n    );\n\n    const customFormats = Object.assign({}, defaultValidators, options.customFormats || {});\n\n    const contollersPattern = options.controllersPattern || '**/*.js';\n    const controllers =\n        typeof options.controllers === 'string'\n            ? loadControllersSync(options.controllers, contollersPattern)\n            : options.controllers || {};\n\n    const allowMissingControllers =\n        'allowMissingControllers' in options ? !!options.allowMissingControllers : true;\n\n    const authenticators: Authenticators = options.authenticators || {};\n\n    let autoHandleHttpErrors: boolean | HandleErrorFunction = true;\n    if (options.autoHandleHttpErrors !== undefined) {\n        if (options.autoHandleHttpErrors instanceof Function) {\n            autoHandleHttpErrors = options.autoHandleHttpErrors;\n        } else {\n            autoHandleHttpErrors = !!options.autoHandleHttpErrors;\n        }\n    }\n\n    const validateDefaultResponses =\n        'validateDefaultResponses' in options ? !!options.validateDefaultResponses : true;\n\n    return {\n        bodyParsers,\n        controllers,\n        authenticators,\n        customFormats,\n        parameterParsers,\n        defaultMaxBodySize: maxBodySize,\n        ignoreServers: options.ignoreServers || false,\n        allowMissingControllers,\n        autoHandleHttpErrors,\n        onResponseValidationError: options.onResponseValidationError || (() => void 0),\n        validateDefaultResponses,\n        allErrors: options.allErrors || false,\n        treatReturnedJsonAsPure: options.treatReturnedJsonAsPure || false,\n        strictValidation: options.strictValidation ?? false,\n        lazyCompileValidationSchemas: options.lazyCompileValidationSchemas ?? false,\n    };\n}\n"
  },
  {
    "path": "src/types/basicTypes.ts",
    "content": "import * as http from 'http';\n\nexport type Callback<T> = (err?: Error | null | undefined, value?: T) => void;\n\nexport type MiddlewareFunction = (\n    req: HttpIncomingMessage,\n    res: http.ServerResponse,\n    next: Callback<void>\n) => void;\n\nexport interface Dictionary<T> {\n    [key: string]: T;\n}\n\n/**\n * A dictionary of parameters, where keys are the source of the parameters.\n */\nexport interface ParametersByLocation<T> {\n    /** Parameters that came from the HTTP query string. */\n    query: T;\n\n    /** Parameters that came from an HTTP header. */\n    header: T;\n\n    /** Parameters that were parsed from the \"server\" in the OAS3 document. */\n    server: T;\n\n    /** Parameters that were parsed from the \"path. */\n    path: T;\n\n    /** Parameters that came from cookies. */\n    cookie: T;\n}\n\n/**\n * A collection of parameters.\n */\nexport interface ParametersMap<T> {\n    [key: string]: T;\n}\n\nexport interface HttpIncomingMessage extends http.IncomingMessage {\n    body?: any;\n}\n"
  },
  {
    "path": "src/types/bodyParser.ts",
    "content": "import * as http from 'http';\nimport { Callback, HttpIncomingMessage } from './basicTypes';\n\n// Stolen from @types/connect#NextHandleFunction.\nexport type ReqParserFunction = (\n    req: HttpIncomingMessage,\n    res: http.ServerResponse,\n    next: Callback<any>\n) => void;\n\nexport type StringParserFunction = (encoded: string) => any;\n\nexport interface StringParser {\n    /**\n     * Synchronous function which parses a string.  A BodyParser must implement\n     * this function to be used for parameter parsing.\n     *\n     * @param encoded - The encoded value to parse.\n     * @returns - The decoded value.\n     */\n    parseString: StringParserFunction;\n}\n\nexport interface BodyParser {\n    /**\n     * Async function which parses an incoming HTTP request.  This is essentially\n     * here so you can use express/connect body parsers.\n     *\n     * @param req - The request to read.  This function should add `req.body`\n     *   after parsing the body.  If `req.body` is already present, this\n     *   function can ignore the body and just call `next()`.\n     * @param res - The response object.  Well behaved body parsers should *not*\n     *   write anything to the response or modify it in any way.\n     * @param next - Callback to call when complete.  If no value is returned\n     *   via the callback then `req.body` will be used.\n     */\n    parseReq: ReqParserFunction;\n}\n\nexport interface MimeTypeParser extends StringParser, BodyParser {}\n"
  },
  {
    "path": "src/types/core.ts",
    "content": "import * as http from 'http';\nimport * as net from 'net';\nimport * as oas3 from 'openapi3-ts';\nimport { Readable } from 'stream';\nimport { ExegesisOptions, ParameterLocation, ParameterLocations } from '.';\nimport { Callback, HttpIncomingMessage, ParametersByLocation, ParametersMap } from './basicTypes';\nimport { BodyParser } from './bodyParser';\nimport { IValidationError, ResponseValidationResult, ValidatorFunction } from './validation';\n\nexport interface HttpHeaders {\n    [header: string]: number | string | string[];\n}\n\nexport interface ExegesisRoute {\n    path: string;\n}\n\nexport interface ExegesisResponse {\n    statusCode: number;\n    statusMessage: string | undefined;\n    headers: HttpHeaders;\n    body: Buffer | string | Readable | any;\n    connection: net.Socket;\n    socket: net.Socket;\n    ended: boolean;\n\n    setStatus(status: number): this;\n    status(status: number): this;\n    setBody(body: any): this;\n\n    /**\n     * Set the value of a header.\n     * @param header - the header to set.\n     * @param value - the value to set the header to.\n     */\n    header(header: string, value: number | string | string[] | undefined): this;\n    set(header: string, value: number | string | string[] | undefined): this;\n    /**\n     * Set the JSON content of the response.  Note that this will call `JSON.stringify()`\n     * immediately if response validation is enabled, because there may be `toJSON()`\n     * functions on the object or any nested values (e.g. if some values are Mongoose objects).\n     * This means we'll need to parse that string to do validation though.  If you\n     * know your object is a pure POJO, call `res.pureJson()` instead.\n     */\n    json(json: any): this;\n    /**\n     * Sets the JSON content of the response to the object provided.  Note that\n     * while `toJSON()` on the object or any child objects will be\n     * respsected when the object is serialized, it will be ignored for purposes\n     * of response validation.\n     */\n    pureJson(json: any): this;\n    end(): void;\n    redirect(status: number, url: string): this;\n    redirect(url: string): this;\n    setHeader(name: string, value: number | string | string[] | undefined): void;\n    getHeader(name: string): number | string | string[] | undefined;\n    getHeaderNames(): string[];\n    getHeaders(): HttpHeaders;\n    hasHeader(name: string): boolean;\n    removeHeader(name: string): void;\n    writeHead(statusCode: number, headers?: HttpHeaders): void;\n    writeHead(statusCode: number, statusMessage?: string, headers?: HttpHeaders): void;\n}\n\nexport interface ExegesisContextBase {\n    readonly req: HttpIncomingMessage;\n    readonly origRes: http.ServerResponse;\n    readonly res: ExegesisResponse;\n    api: any;\n    security?: { [scheme: string]: AuthenticationSuccess };\n    user?: any;\n    parameterLocations?: ParameterLocations;\n\n    makeError(statusCode: number, message: string): Error;\n    makeValidationError(message: string, parameterLocation: ParameterLocation): Error;\n\n    /**\n     * Returns true if the response has already been sent.\n     */\n    isResponseFinished(): boolean;\n}\n\nexport interface ExegesisContext extends ExegesisContextBase {\n    parameterLocations: ParameterLocations;\n    params: ParametersByLocation<ParametersMap<any>>;\n    requestBody: any;\n    options: ExegesisOptions;\n    route: ExegesisRoute;\n    baseUrl: string;\n}\n\nexport interface ExegesisPluginContext extends ExegesisContextBase {\n    getParams(): Promise<ParametersByLocation<ParametersMap<any>>>;\n    getParams(done: Callback<ParametersByLocation<ParametersMap<any>>>): void;\n    getRequestBody(): Promise<any>;\n    getRequestBody(done: Callback<any>): void;\n}\n\nexport interface OAS3ApiInfo {\n    openApiDoc: oas3.OpenAPIObject;\n    serverPtr: string | undefined;\n    serverObject: oas3.ServerObject | undefined;\n    pathItemPtr: string;\n    pathItemObject: oas3.PathItemObject;\n    operationPtr: string | undefined;\n    operationObject: oas3.OperationObject | undefined;\n    requestBodyMediaTypePtr: string | undefined;\n    requestBodyMediaTypeObject: oas3.MediaTypeObject | undefined;\n}\n\nexport type PromiseController = (context: ExegesisContext) => any;\nexport type CallbackController = (context: ExegesisContext, done: Callback<any>) => void;\nexport type Controller = PromiseController | CallbackController;\n\nexport interface ControllerModule {\n    [operationId: string]: Controller;\n}\n\nexport interface Controllers {\n    // controllerName can have \"/\"s in it.\n    [controllerName: string]: ControllerModule;\n}\n\nexport interface AuthenticationFailure {\n    type: 'invalid' | 'missing';\n    status?: number;\n    message?: string;\n    challenge?: string;\n}\n\nexport interface AuthenticationSuccess {\n    type: 'success';\n    user?: any;\n    roles?: string[] | undefined;\n    scopes?: string[] | undefined;\n    [name: string]: any;\n}\n\nexport type AuthenticationResult = AuthenticationSuccess | AuthenticationFailure;\n\nexport interface AuthenticatorInfo {\n    in?: 'query' | 'header' | 'cookie';\n    name?: string;\n    scheme?: string;\n}\n\nexport type PromiseAuthenticator = (\n    context: ExegesisPluginContext,\n    info: AuthenticatorInfo\n) => AuthenticationResult | undefined | Promise<AuthenticationResult | undefined>;\nexport type CallbackAuthenticator = (\n    context: ExegesisPluginContext,\n    info: AuthenticatorInfo,\n    done: Callback<AuthenticationResult | undefined>\n) => void;\nexport type Authenticator = PromiseAuthenticator | CallbackAuthenticator;\nexport interface Authenticators {\n    [scheme: string]: Authenticator;\n}\n\n/**\n * Result returned by the exegesisRunner.\n */\nexport interface HttpResult {\n    headers: HttpHeaders;\n    status: number;\n    body: NodeJS.ReadableStream | undefined;\n}\n\n/**\n * A function which takes in a request and response, and returns an HttpResult.\n *\n * @throws {ValidationError} - If a validation error occurs in the parameters or the body.\n * @throws {HttpError} - If a non-validation error occurs, and an HTTP error code is suggested.\n * @throws {Error} - If any other error occurs.\n */\nexport type ExegesisRunner = (\n    req: http.IncomingMessage,\n    res: http.ServerResponse\n) => Promise<HttpResult | undefined>;\n\nexport type ParsedParameterValidator = (\n    parameterValues: ParametersByLocation<ParametersMap<any>>\n) => IValidationError[] | null;\n\nexport interface ResolvedOperation {\n    parseParameters: () => ParametersByLocation<ParametersMap<any>>;\n    validateParameters: ParsedParameterValidator;\n    parameterLocations: ParameterLocations;\n    bodyParser: BodyParser | undefined;\n    validateBody: ValidatorFunction | undefined;\n    exegesisControllerName: string | undefined;\n    operationId: string | undefined;\n    controllerModule: ControllerModule | undefined;\n    controller: Controller | undefined;\n\n    validateResponse(\n        response: ExegesisResponse,\n        validateDefaultResponses: boolean\n    ): ResponseValidationResult;\n\n    // Returns the authentication data, or undefined if user could not be authenticated.\n    authenticate(\n        context: ExegesisContext\n    ): Promise<{ [scheme: string]: AuthenticationSuccess } | undefined>;\n}\n\nexport interface ResolvedPath<T> {\n    operation: ResolvedOperation | undefined;\n    api: T;\n    /** List of methods the client is allowed to send to this path.  e.g. `['get', 'post']`. */\n    allowedMethods: string[];\n    /** The path of the operation being accessed.  e.g. \"/users/1234\". */\n    path: string;\n    /**\n     * The \"base\" of the `path`.  `${baseUrl}${path}` represents the full\n     * URL being accessed.  For OAS3, for example you can set a URL like\n     * `https://myserver.com/v1` in the `Server` object, which would be reflected\n     * here.\n     */\n    baseUrl: string;\n}\n\n// ApiInterface provides an interface into the `oas3` subdirectory.  The idea here is,\n// when `oas4` comes along we can support it by writing a new `oas4` subdirectory\n// that implements this same interface, and then we'll be able to support oas4\n// wihtout changing anything.  (We'll see if this actually works.  :P)\nexport interface ApiInterface<T> {\n    /**\n     * Resolve an incoming request.\n     *\n     * @param method - The HTTP method used (e.g. 'GET').\n     * @param url - The URL used to retrieve this request.\n     * @param headers - Any headers sent along with the request.\n     * @throws {ValidationError} if some parameters cannot be parsed.\n     */\n    resolve(\n        method: string,\n        url: string,\n        headers: http.IncomingHttpHeaders\n    ): ResolvedPath<T> | undefined;\n}\n\nexport interface ExegesisPluginInstance {\n    /**\n     * Called exactly once, before Exegesis \"compiles\" the API document.\n     * Plugins must not modify apiDoc here.\n     *\n     * @param data.apiDoc - the API document.\n     */\n    preCompile?:\n        | ((data: { apiDoc: any }) => void | Promise<void>)\n        | ((data: { apiDoc: any }, done: Callback<void>) => void);\n\n    /**\n     * Called before routing.  Note that the context hasn't been created yet,\n     * so you just get a raw `req` and `res` object here.\n     */\n    preRouting?:\n        | ((data: { req: http.IncomingMessage; res: http.ServerResponse }) => void | Promise<void>)\n        | ((\n              data: { req: http.IncomingMessage; res: http.ServerResponse },\n              done: Callback<void>\n          ) => void);\n\n    /**\n     * Called immediately after the routing phase.  Note that this is\n     * called before Exegesis verifies routing was valid - the\n     * `pluginContext.api` object will have information about the\n     * matched route, but will this information may be incomplete.\n     * For example, for OAS3 we may have matched a route, but not\n     * matched an operation within the route. Or we may have matched\n     * an operation but that operation may have no controller defined.\n     * (If we failed to match a route at all, this will not be called.)\n     *\n     * If your API added a route to the API document, this function is a\n     * good place to write a reply.\n     *\n     * @param pluginContext - the plugin context.\n     */\n    postRouting?:\n        | ((pluginContext: ExegesisPluginContext) => void | Promise<void>)\n        | ((pluginContext: ExegesisPluginContext, done: Callback<void>) => void);\n\n    /**\n     * Called for each request, after security phase and before input\n     * is parsed and the controller is run.  This is a good place to\n     * do extra security checks.  The `exegesis-plugin-roles` plugin,\n     * for example, generates a 403 response here if the authenticated\n     * user has insufficient privliedges to access this path.\n     *\n     * Note that this function will not be called if a previous pluing\n     * has already written a response.\n     *\n     * @param pluginContext - the plugin context.\n     */\n    postSecurity?:\n        | ((pluginContext: ExegesisPluginContext) => void | Promise<void>)\n        | ((pluginContext: ExegesisPluginContext, done: Callback<void>) => void);\n\n    /**\n     * Called immediately after the controller has been run, but before\n     * any response validation.  This is a good place to do custom\n     * response validation.  If you have to deal with something weird\n     * like XML, this is where you'd handle it.\n     *\n     * This function can modify the contents of the response.\n     *\n     * @param context - The exegesis plugin context.\n     */\n    postController?:\n        | ((pluginContext: ExegesisContext) => void | Promise<void>)\n        | ((pluginContext: ExegesisContext, done: Callback<void>) => void);\n\n    /**\n     * Called after the response validation step.  This is the last step before\n     * the response is converted to JSON and written to the output.\n     */\n    postResponseValidation?:\n        | ((pluginContext: ExegesisContext) => void | Promise<void>)\n        | ((pluginContext: ExegesisContext, done: Callback<void>) => void);\n}\n\nexport interface ExegesisPlugin {\n    info: {\n        name: string;\n    };\n    makeExegesisPlugin(data: { apiDoc: any }): ExegesisPluginInstance;\n}\n"
  },
  {
    "path": "src/types/index.ts",
    "content": "import * as oas3 from 'openapi3-ts';\n\nexport { oas3 };\n\nexport * from './bodyParser';\nexport * from './basicTypes';\nexport * from './core';\nexport * from './options';\nexport * from './validation';\n"
  },
  {
    "path": "src/types/options.ts",
    "content": "import * as http from 'http';\nimport { BodyParser, StringParser } from './bodyParser';\nimport { Authenticators, Controllers, ExegesisPlugin } from './core';\nimport { ResponseValidationCallback } from './validation';\n\n/**\n * A function which validates custom formats.\n */\nexport type CustomFormatChecker = RegExp | ((value: string) => boolean);\n\nexport type HandleErrorFunction = (err: Error, context: { req: http.IncomingMessage }) => any;\n\nexport interface StringCustomFormatChecker {\n    type?: 'string' | undefined;\n    validate: CustomFormatChecker;\n}\n\nexport interface NumberCustomFormatChecker {\n    type: 'number';\n    validate: (value: number) => boolean;\n}\n\n/**\n * A hash where keys are format names.  Values can be one of:\n *\n *   * A RegExp for checking a string.\n *   * A `function(string) : boolean` for checking a string, which returns\n *     false the the string is invalid.\n *   * A `{validate, type}` object, where `type` is either \"string\" or \"number\",\n *     and validate is a `function(string) : boolean`.\n *   * Any `ajv` format.\n */\nexport interface CustomFormats {\n    [key: string]: CustomFormatChecker | StringCustomFormatChecker | NumberCustomFormatChecker;\n}\n\n/**\n * Options that control how an OpenAPI document is parsed and validated.\n */\nexport interface ExegesisOptions {\n    /**\n     * A hash where keys are either mime types or  mimetype wildcards\n     * (e.g. 'application/*'), and values are StringParsers, BodyParsers, or\n     * MimeTypeParsers.  In order to be used for parsing parameters, a\n     * parser must implement `parseString()`.\n     */\n    mimeTypeParsers?: { [mimeType: string]: StringParser | BodyParser };\n\n    /**\n     * A hash of authenticators.  See\n     * https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md\n     * for details.\n     */\n    authenticators?: Authenticators;\n\n    /**\n     * Either a folder which contains controller modules, or a hash where keys\n     * are controller names and values are modules.  If this is not\n     * provided, then Exegesis will never resolve a controller when calling\n     * `ApiInterface.resolve()`.\n     */\n    controllers?: string | Controllers;\n\n    /**\n     * If `controllers` is a folder name, then this is a glob pattern used to\n     * load controllers (e.g. `**\\/*.@(ts|js)` to allow both Typescript and\n     * Javascript files to be used as controllers.)  If `controllers` is\n     * not a folder name, this is ignored.\n     */\n    controllersPattern?: string;\n\n    /**\n     * A hash where keys are format names.  Values can be one of:\n     *\n     *   * A RegExp for checking a string.\n     *   * A `function(string) : boolean` for checking a string, which returns\n     *     false the the string is invalid.\n     *   * A `{validate, type}` object, where `type` is either \"string\" or \"number\",\n     *     and validate is a `function(string) : boolean`.\n     */\n    customFormats?: CustomFormats;\n\n    /**\n     * If true, when resolving a path Exegesis will\n     * ignore the \"servers\" section of the OpenAPI doc entirely.\n     */\n    ignoreServers?: boolean;\n\n    /**\n     * If a `MimeTypeParser` provided in `mimeTypeParsers` does not support\n     * `parseReq()`, this defines the maximum size of a body that will be parsed.\n     * Bodies longer than this will result in a \"413 - Payload Too Large\" error.\n     * Built in body parsers will also respect this option.\n     */\n    defaultMaxBodySize?: number;\n\n    /**\n     * If false, then if any operations do not define a controller,\n     * Exegesis will raise an error when the API is being compiled.  If\n     * true, then Exegesis will simply pretend any operations that don't\n     * have a controller do not exist, and will not handle them.\n     *\n     * Defaults to true.\n     */\n    allowMissingControllers?: boolean;\n\n    /**\n     * By default, ExegesisRunner will turn `exegesis.HttpError`s (such as errors\n     * generated from `context.makeError()` and `exegesis.ValidationError`s into JSON\n     * replies with appropriate error messages.  If you want to handle these errors\n     * yourself, set this value to false.  Defaults to true. TODO\n     */\n    autoHandleHttpErrors?: boolean | HandleErrorFunction;\n\n    /**\n     * If you provide this function, Exegesis will validate responses controllers\n     * generate before they are sent to the client.  If you throw an exception,\n     * a 500 error will be generated instead of sending the reply to the\n     * client.\n     *\n     * Note that when bodies are strings, buffers, or streams, Exegesis\n     * will not try to parse your body to see if it conforms to the\n     * response schema; only JSON objects are validated.\n     *\n     * @param result.errors - A list of error objects.\n     * @param result.isDefault - For OAS3, this is true if we validated against\n     *   the `default` status code in the Responses object.\n     */\n    onResponseValidationError?: ResponseValidationCallback;\n\n    /**\n     * Controls how Exegesis validates responses.  If this is set to false, then\n     * in OAS3 Exegesis will not do validation for responses unless the\n     * response status code matches an explicit status code in the\n     * responses object (not the \"default\" status code).  If this is set\n     * to true, then all responses will be validated.\n     *\n     * This option is ignored if `onResponseValidationError` is not set.  If\n     * `onResponseValidationError` is set, the default is true.\n     */\n    validateDefaultResponses?: boolean;\n\n    /**\n     * Array of plugins to run.\n     */\n    plugins?: ExegesisPlugin[];\n\n    /**\n     * If this is true, validation will find all errors in a document instead\n     * of stopping at the first error.  Enabling this is obviously better,\n     * because you get better errors, but it also means you spend more time\n     * processing invalid requests.\n     */\n    allErrors?: boolean;\n\n    /**\n     * If true, then when a controller returns a JSON object, exegesis will call\n     * `context.res.pureJson(val)` to set the body of the response.  If false, exegesis\n     * will call `context.res.json(val)`.\n     */\n    treatReturnedJsonAsPure?: boolean;\n\n    /**\n     * If true, then this will put ajv into \"strict mode\" (see https://ajv.js.org/strict-mode.html).\n     */\n    strictValidation?: boolean;\n\n    /**\n     * Response and request schemas are compiled by ajv to make validation faster. However compilation is slow\n     * and can cause compilation of API to take long time. Enabling this will cause validation schemas\n     * compilation to be executed when the validator is needed.\n     */\n    lazyCompileValidationSchemas?: boolean;\n}\n"
  },
  {
    "path": "src/types/validation.ts",
    "content": "import { ExegesisContext } from '.';\nimport * as ajv from 'ajv';\n\nexport type ParameterLocationIn =\n    | 'path'\n    | 'server'\n    | 'query'\n    | 'cookie'\n    | 'header'\n    | 'request'\n    | 'response';\n\n/**\n * The location of a parameter or property within a request.\n *\n * @property in - A description of where the error was located (e.g. 'path', 'query', 'request', etc...).\n * @property name - If this refers to a parameter, this is the name of the parameter.\n *   If `in` is 'request', this will be 'body'.\n * @property docPath - An array of strings which describes the path to the\n *   OpenAPI definition that is related to the parameter.\n * @property [path] - An array of strings which describes the path to the parameter.\n *   This may be omitted in some cases, for example if the error was in the body and\n *   the body is \"image/gif\", then this field doesn't really make sense.\n */\nexport interface ParameterLocation {\n    in: ParameterLocationIn;\n    name: string;\n    docPath: string;\n    path?: string;\n}\n\n/**\n * A dictionary of parameters, where keys are the source of the parameters.\n */\nexport interface ParameterLocations {\n    path: { [parameter: string]: ParameterLocation };\n    query: { [parameter: string]: ParameterLocation };\n    cookie: { [parameter: string]: ParameterLocation };\n    header: { [parameter: string]: ParameterLocation };\n    request?: { body: ParameterLocation };\n    response?: { body: ParameterLocation };\n}\n\n/**\n * A validation error.\n *\n * @property message - A short message about what was wrong.\n * @property location - The location of the parameter/property that caused the\n *   error.\n * @property ajvError - The raw Ajv error returned for the validator. Can be used to customise error messages.\n */\nexport interface IValidationError {\n    message: string;\n    location?: ParameterLocation;\n    ajvError?: ajv.ErrorObject;\n}\n\n/**\n * Validates a document.\n *\n * Note that this may modify the document in place.  For example, this may\n * convert strings into numbers, or convert a single element into an array,\n * in order to conform to the schema.\n *\n * @returns an `{errors, value}` object, wehre `errors` is a list of errors,\n *   or `null` if validation was successful, and `value` is the validated\n *   (possibly modified) value.\n */\nexport interface ValidatorFunction {\n    (doc: any): {\n        errors: IValidationError[] | null;\n        value: any;\n    };\n}\n\nexport interface ResponseValidationResult {\n    errors: IValidationError[] | null;\n    /**\n     * In OAS3, you can specify a response schema for different return codes.\n     * If the status code doesn't have an explicit schema, or if it falls into\n     * the \"default\" case, this flag will be true.\n     */\n    isDefault: boolean;\n}\n\nexport interface ResponseValidationCallback {\n    (result: { errors: IValidationError[]; isDefault: boolean; context: ExegesisContext }): void;\n}\n"
  },
  {
    "path": "src/utils/bufferToStream.ts",
    "content": "import { Readable } from 'stream';\n\nexport default function bufferToStream(buf: Buffer) {\n    return new Readable({\n        read() {\n            this.push(buf);\n            this.push(null);\n        },\n    });\n}\n"
  },
  {
    "path": "src/utils/httpUtils.ts",
    "content": "export function httpHasBody(headers: { [header: string]: any }): boolean {\n    const contentLength = headers['content-length'];\n    return (\n        !!headers['transfer-encoding'] ||\n        (contentLength && contentLength !== '0' && contentLength !== 0)\n    );\n}\n\n// `delete` might have a body. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE\nconst HTTP_METHODS_WITHOUT_BODY = ['get', 'head', 'trace', 'options'];\n\nexport function requestMayHaveBody(method: string) {\n    return HTTP_METHODS_WITHOUT_BODY.indexOf(method.toLowerCase()) === -1;\n}\n"
  },
  {
    "path": "src/utils/json-schema-infer-types.ts",
    "content": "import { resolveRef } from './json-schema-resolve-ref';\nimport { JSONSchema4, JSONSchema6 } from 'json-schema';\n\nconst VALID_SCHEMA_TYPES = ['null', 'boolean', 'object', 'array', 'number', 'string', 'integer'];\n\nconst ALL_ALLOWED_TYPES = new Set(VALID_SCHEMA_TYPES);\nconst NO_ALLOWED_TYPES = new Set<string>([]);\n\nfunction getType(val: any): string {\n    if (val === null || val === undefined) {\n        return 'null';\n    } else if (typeof val === 'string') {\n        return 'string';\n    } else if (val === true || val === false) {\n        return 'boolean';\n    } else if (Array.isArray(val)) {\n        return 'array';\n    } else if (Number.isInteger(val)) {\n        return 'integer';\n    } else if (typeof val === 'number' && !isNaN(val)) {\n        return 'number';\n    } else if (typeof val === 'object') {\n        return 'object';\n    } else {\n        throw new Error(`Can't work out JSON-Schema type of ${val}`);\n    }\n}\n\nfunction toArray(val: string | string[]) {\n    if (Array.isArray(val)) {\n        return val;\n    } else {\n        return [val];\n    }\n}\n\nfunction union<T>(a: Set<T>, b: Set<T>) {\n    return new Set<T>([...a, ...b]);\n}\n\nfunction intersection<T>(a: Set<T>, b: Set<T>) {\n    return new Set<T>([...a].filter((x) => b.has(x)));\n}\n\nfunction inferTypesOneOf(rootSchema: any, oneOf: any[], stack: any[]): Set<string> {\n    if (oneOf.length === 0) {\n        return ALL_ALLOWED_TYPES;\n    }\n\n    let allowedTypes = NO_ALLOWED_TYPES;\n    oneOf.forEach((childSchema: any) => {\n        childSchema = resolveRef(rootSchema, childSchema);\n        const types = inferTypesPriv(rootSchema, childSchema, stack);\n        allowedTypes = union(allowedTypes, types);\n    });\n\n    return allowedTypes;\n}\n\nfunction inferTypesPriv(\n    rootSchema: any,\n    schema: JSONSchema4 | JSONSchema6,\n    stack: any[]\n): Set<string> {\n    if (stack.includes(schema)) {\n        throw new Error('circular definition found');\n    } else {\n        stack = stack.concat(schema);\n    }\n\n    let allowedTypes = ALL_ALLOWED_TYPES;\n\n    allowedTypes = intersection(\n        allowedTypes,\n        inferTypesOneOf(rootSchema, schema.oneOf || [], stack)\n    );\n    allowedTypes = intersection(\n        allowedTypes,\n        inferTypesOneOf(rootSchema, schema.anyOf || [], stack)\n    );\n\n    if (schema.type) {\n        allowedTypes = intersection(allowedTypes, new Set(toArray(schema.type)));\n    }\n\n    if (schema.allOf) {\n        for (const childSchemaRef of schema.allOf) {\n            const childSchema = resolveRef(rootSchema, childSchemaRef);\n            const types = inferTypesPriv(rootSchema, childSchema, stack);\n            allowedTypes = intersection(allowedTypes, types);\n        }\n    }\n\n    // TODO: Dealing with \"not\" is hard.\n\n    if ('const' in schema) {\n        const schemaConst = (schema as JSONSchema6).const;\n        const constType = new Set<string>([getType(schemaConst)]);\n        allowedTypes = intersection(allowedTypes, constType);\n    }\n\n    if (schema.enum) {\n        const enumTypes = new Set<string>(schema.enum.map(getType));\n        allowedTypes = intersection(allowedTypes, enumTypes);\n    }\n\n    return allowedTypes;\n}\n\n/**\n * Given a JSON Schema, returns a list of types that an object which passes\n * schema validation would be allowed to have.\n *\n * @param schema - A JSON schema.  This is allowed to have `$ref`s, but they\n *   must be internal refs relative to the schema (or to the `rootDocument`\n *   if it is specified).\n * @param [options.rootDocument] - If your JSON schema is embedded in a larger\n *   JSON document, it can be provided here to resolve `$ref`s relative to that\n *   parent document.\n */\nexport default function inferTypes(\n    schema: JSONSchema4 | JSONSchema6,\n    options: {\n        rootDocument?: any;\n    } = {}\n): string[] {\n    let result = inferTypesPriv(options.rootDocument || schema, schema, []);\n\n    // Number includes integer, so if number is set, then integer needs to be as well.\n    if (result.has('number')) {\n        result = new Set(result);\n        result.add('integer');\n    }\n\n    return Array.from(result);\n}\n"
  },
  {
    "path": "src/utils/json-schema-resolve-ref.ts",
    "content": "import * as jsonPtr from 'json-ptr';\n\nfunction resolveRefPriv(document: any, ref: string): any {\n    if (!ref.startsWith('#/') && !ref.startsWith('/') && ref !== '') {\n        throw new Error(`Cannot resolve non-local ref ${ref}`);\n    }\n\n    const path = jsonPtr.JsonPointer.decode(ref).slice();\n    let currentDoc = document;\n    while (path.length > 0) {\n        const pathname = path.shift() as string;\n        currentDoc = currentDoc && currentDoc[pathname];\n        while (currentDoc && currentDoc.$ref) {\n            currentDoc = resolveRefPriv(document, currentDoc.$ref);\n        }\n    }\n\n    return currentDoc;\n}\n\nexport function resolveRef(document: any, ref: string | any): any | undefined {\n    if (ref instanceof String) {\n        return resolveRef(document, ref.toString());\n    } else if (typeof ref === 'string') {\n        return resolveRefPriv(document, ref);\n    } else if (ref.$ref) {\n        return resolveRefPriv(document, ref.$ref);\n    } else {\n        return ref;\n    }\n}\n"
  },
  {
    "path": "src/utils/jsonPaths.ts",
    "content": "import * as jsonPtr from 'json-ptr';\n\nfunction normalize(path: string): string {\n    return jsonPtr.encodePointer(jsonPtr.JsonPointer.decode(path));\n}\n\nexport function toUriFragment(path: string) {\n    return jsonPtr.encodeUriFragmentIdentifier(jsonPtr.JsonPointer.decode(path));\n}\n\nexport function jsonPointerStartsWith(path: string, prefix: string): boolean {\n    path = normalize(path);\n    prefix = normalize(prefix);\n    return path.startsWith(prefix);\n}\n\nexport function jsonPointerStripPrefix(path: string, prefix: string): string {\n    const isUriFragment = path.startsWith('#');\n    path = normalize(path);\n    prefix = normalize(prefix);\n    if (path.startsWith(prefix)) {\n        const answer = path.slice(prefix.length);\n        if (isUriFragment) {\n            return toUriFragment(answer);\n        } else {\n            return answer;\n        }\n    } else {\n        return path;\n    }\n}\n"
  },
  {
    "path": "src/utils/jsonSchema.ts",
    "content": "import ld from 'lodash';\nimport traveseSchema from 'json-schema-traverse';\nimport * as jsonPaths from './jsonPaths';\nimport * as jsonPtr from 'json-ptr';\nimport { JSONSchema6, JSONSchema4 } from 'json-schema';\nimport { resolveRef } from './json-schema-resolve-ref';\n\nfunction extractSchemaPriv(\n    subtreeRef: string,\n    refResolver: (ref: string) => any | undefined,\n    options: {\n        skipUnknownRefs?: boolean;\n    },\n    context?: {\n        result: any;\n        replaced: any;\n        replacements: string[];\n        schemaCount: number;\n        rootSubtreeRef: string;\n    }\n): JSONSchema4 | JSONSchema6 {\n    const subtreeObject = refResolver(subtreeRef);\n\n    if (!subtreeObject) {\n        throw new Error(`Could not find ref ${subtreeRef}`);\n    }\n\n    const result = ld.cloneDeep(subtreeObject);\n    const ctx = context || {\n        result: result,\n        replaced: {},\n        replacements: [],\n        schemaCount: 0,\n        rootSubtreeRef: subtreeRef,\n    };\n\n    traveseSchema(result, (schema: any) => {\n        if (schema.$ref && typeof schema.$ref === 'string') {\n            if (ctx.replaced[schema.$ref]) {\n                schema.$ref = ctx.replaced[schema.$ref];\n            } else if (jsonPaths.jsonPointerStartsWith(schema.$ref, ctx.rootSubtreeRef + '/')) {\n                ctx.replaced[schema.$ref] = jsonPaths.jsonPointerStripPrefix(\n                    schema.$ref,\n                    ctx.rootSubtreeRef\n                );\n                schema.$ref = ctx.replaced[schema.$ref];\n            } else if (!refResolver(schema.$ref)) {\n                // Don't know how to resolve this ref\n                if (!options.skipUnknownRefs) {\n                    throw new Error(`Can't find ref ${schema.$ref}`);\n                }\n            } else {\n                ctx.result.definitions = ctx.result.definitions || {};\n\n                // Find a name to store this under in 'definitions'.\n                //\n                // Because we try to pick a \"sensible\" name for the new definition,\n                // when we recurse into `extractSchemaPriv` below, if there's a child\n                // schema with the same name as the one we just picked, we could\n                // end up accidentally giving two different schemas the same name\n                // and clobbering one with the other.  To avoid this, we record\n                // all the `newRefSuffix`es we pick in `ctx.replacements`, and\n                // then we can make sure this doesn't happen.\n                const origRef = schema.$ref;\n                const jsonPath = jsonPtr.JsonPointer.decode(schema.$ref);\n                let newRefSuffix: string | undefined =\n                    jsonPath.length > 0 ? `${jsonPath[jsonPath.length - 1]}` : undefined;\n                while (\n                    !newRefSuffix ||\n                    ctx.result.definitions[newRefSuffix] ||\n                    ctx.replacements.includes(newRefSuffix)\n                ) {\n                    newRefSuffix = `schema${ctx.schemaCount++}`;\n                }\n                ctx.replacements.push(newRefSuffix);\n\n                // Do the replacement.\n                schema.$ref = ctx.replaced[schema.$ref] = `#/definitions/${newRefSuffix}`;\n                ctx.result.definitions[newRefSuffix] = extractSchemaPriv(\n                    origRef,\n                    refResolver,\n                    options,\n                    ctx\n                );\n            }\n        }\n    });\n\n    return result;\n}\n\n/**\n * Extracts a subtree from a JSON document, fixing any \"$ref\" JSON refs so they\n * now\n *\n * @param document - The document to extract a subtree from.\n * @param subtree - A JSON ref to the subtree to extract, or a child node of `document`.\n * @param [options.resolveRef] - A function which, given a JSON reference, resolves the node\n *   it refers to.\n * @param [options.skipUnknownRefs] - If true, skip any unknown refs instead of\n *   throwing an error.\n * @returns the extracted document.  The returned document is a copy, and shares\n *   no children with the original document.\n */\nexport function extractSchema(\n    document: any,\n    subtreeRef: string,\n    options: {\n        resolveRef?: (ref: string) => any | undefined;\n        skipUnknownRefs?: boolean;\n    } = {}\n): JSONSchema4 | JSONSchema6 {\n    const refResolver = options.resolveRef || resolveRef.bind(null, document);\n    return extractSchemaPriv(subtreeRef, refResolver, options, undefined);\n}\n"
  },
  {
    "path": "src/utils/mime.ts",
    "content": "// A mime-type, per RFC 7231 section 3.1.1.1\nconst TCHAR = \"[!#$%&'*+-.^_`|~A-Za-z0-9]\";\nconst TOKEN = `${TCHAR}+`;\nconst OWS = '[ \\t]*';\nconst MIME_TYPE_REGEX = new RegExp(`^(${TOKEN})/(${TOKEN})${OWS}(.*)$`);\n\nexport interface ParsedMimeType {\n    type: string;\n    subtype: string;\n}\n\n/**\n * Parses a mimeType into a `{type, subtype}` object.\n * Parameters provided with the mimeType are ignored.\n */\nexport function parseMimeType(mimeType: string): ParsedMimeType {\n    const match = MIME_TYPE_REGEX.exec(mimeType);\n    if (!match) {\n        throw new Error(`Invalid MIME type: \"${mimeType}\"`);\n    }\n    if (match[3] && match[3][0] !== ';') {\n        throw new Error(`Invalid MIME type: \"${mimeType}\"`);\n    }\n    return { type: match[1].toLowerCase(), subtype: match[2].toLowerCase() };\n}\n\nfunction isParsedMimeType(val: string | ParsedMimeType): val is ParsedMimeType {\n    return !!((val as ParsedMimeType).type && (val as ParsedMimeType).subtype);\n}\n\nexport class MimeTypeRegistry<T> {\n    // This is a registry of mime types with no wildcards.\n    private _staticMimeTypes: { [mimeType: string]: T } = Object.create(null);\n    // This is a registry of \"types\" for mime types where the subtype was wildcarded.\n    private _wildcardSubtypes: { [mimeType: string]: T } = Object.create(null);\n    // If someone registers \"*/*\", it goes here.\n    private _defaultMimeType: T | undefined;\n\n    constructor(map?: { [mimeType: string]: T | undefined } | undefined) {\n        if (map) {\n            for (const mimeType of Object.keys(map)) {\n                const t = map[mimeType];\n                if (t) {\n                    this.set(mimeType, t);\n                }\n            }\n        }\n    }\n\n    set(mimeType: string | ParsedMimeType, value: T) {\n        const { type, subtype } = isParsedMimeType(mimeType) ? mimeType : parseMimeType(mimeType);\n\n        if (type === '*' && subtype === '*') {\n            this._defaultMimeType = value;\n        } else if (subtype === '*') {\n            this._wildcardSubtypes[type] = value;\n        } else if (type === '*') {\n            throw new Error(\n                `Do not allow wildcarding mime \"type\" unless also wildcarding \"subtype\": ${mimeType}`\n            );\n        } else {\n            this._staticMimeTypes[`${type}/${subtype}`] = value;\n        }\n    }\n\n    get(mimeType: string | ParsedMimeType): T | undefined {\n        const { type, subtype } = isParsedMimeType(mimeType) ? mimeType : parseMimeType(mimeType);\n\n        return (\n            this._staticMimeTypes[`${type}/${subtype}`] ||\n            this._wildcardSubtypes[type] ||\n            this._defaultMimeType\n        );\n    }\n\n    getRegisteredTypes() {\n        const answer = Object.keys(this._staticMimeTypes).concat(\n            Object.keys(this._wildcardSubtypes).map((type) => `${type}/*`)\n        );\n\n        if (this._defaultMimeType) {\n            answer.push('*/*');\n        }\n\n        return answer;\n    }\n}\n"
  },
  {
    "path": "src/utils/stringToStream.ts",
    "content": "import { Readable } from 'stream';\n\nexport default function stringToStream(str: string, encoding: BufferEncoding = 'utf-8') {\n    return new Readable({\n        read() {\n            this.push(str, encoding);\n            this.push(null);\n        },\n    });\n}\n"
  },
  {
    "path": "src/utils/typeUtils.ts",
    "content": "import { Readable } from 'stream';\n\nexport function isReadable(obj: any): obj is Readable {\n    return obj && obj.pipe && typeof obj.pipe === 'function';\n}\n"
  },
  {
    "path": "test/controllers/fixtures/badControllers/a.md",
    "content": "# This is not javascript, it's a markdown file\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/a.js",
    "content": "exports.a = () => 'a';\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/b/c.js",
    "content": "exports.c = () => 'c';\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/d/index.js",
    "content": "exports.d = () => 'd';\n"
  },
  {
    "path": "test/controllers/fixtures/shadow/a/index.js",
    "content": "exports.a = () => 'index';\n"
  },
  {
    "path": "test/controllers/fixtures/shadow/a.js",
    "content": "exports.a = () => 'a';\n"
  },
  {
    "path": "test/controllers/loadControllersTest.ts",
    "content": "import path from 'path';\nimport { expect } from 'chai';\n\nimport * as load from '../../src/controllers/loadControllers';\nimport { invokeController } from '../../src/controllers/invoke';\nimport FakeExegesisContext from '../fixtures/FakeExegesisContext';\n\ndescribe('controllers - loadControllers', function () {\n    it('should load controllers from a folder', function () {\n        const controllers = load.loadControllersSync(\n            path.resolve(__dirname, './fixtures/controllers')\n        );\n        expect(Object.keys(controllers).sort()).to.eql([\n            'a',\n            'a.js',\n            'b/c',\n            'b/c.js',\n            'd',\n            'd/index',\n            'd/index.js',\n        ]);\n    });\n\n    it('should load controllers from a folder with a pattern', function () {\n        const controllers = load.loadControllersSync(\n            path.resolve(__dirname, './fixtures/controllers'),\n            'b/c.js'\n        );\n        expect(Object.keys(controllers).sort()).to.eql(['b/c', 'b/c.js']);\n    });\n\n    it('should skip directories', function () {\n        const controllers = load.loadControllersSync(\n            path.resolve(__dirname, './fixtures/controllers'),\n            '@(b|a.js)'\n        );\n        expect(Object.keys(controllers).sort()).to.eql(['a', 'a.js']);\n    });\n\n    it('should error if there are controllers that can not be loaded', function () {\n        expect(() =>\n            load.loadControllersSync(path.resolve(__dirname, './fixtures/badControllers'), '**')\n        ).to.throw(`Could not load controller`);\n    });\n\n    it('should correctly load index files that are shadowed by a file in the parent folder', async function () {\n        const controllers = load.loadControllersSync(path.resolve(__dirname, './fixtures/shadow'));\n        const context = new FakeExegesisContext();\n\n        expect(await invokeController(controllers['a'], controllers['a'].a, context)).to.equal('a');\n        expect(\n            await invokeController(controllers['a/index'], controllers['a/index'].a, context)\n        ).to.equal('index');\n    });\n});\n"
  },
  {
    "path": "test/core/ExegesisResponseImplTest.ts",
    "content": "import { expect } from 'chai';\nimport ExegesisResponseImpl from '../../lib/core/ExegesisResponseImpl';\n\ndescribe('ExegesisResponseImpl', () => {\n    describe('json', () => {\n        it('uses the object toJSON', () => {\n            class StringWrapper {\n                private readonly _content: string;\n\n                constructor(content: string) {\n                    this._content = content;\n                }\n\n                public toJSON() {\n                    return this._content;\n                }\n            }\n\n            const data = {\n                content: new StringWrapper('foo'),\n            };\n\n            const res = new ExegesisResponseImpl({ socket: {} } as any, true);\n            res.json(data);\n\n            expect(res.headers['content-type']).to.equal('application/json');\n            expect(res.body).to.eql(JSON.stringify({ content: 'foo' }));\n        });\n\n        it('skips toJSON if response validation is disabled', () => {\n            class StringWrapper {\n                private readonly _content: string;\n\n                constructor(content: string) {\n                    this._content = content;\n                }\n\n                public toJSON() {\n                    return this._content;\n                }\n            }\n\n            const data = {\n                content: new StringWrapper('foo'),\n            };\n\n            const res = new ExegesisResponseImpl({ socket: {} } as any, false);\n            res.json(data);\n\n            expect(res.headers['content-type']).to.equal('application/json');\n            expect(res.body).to.eql(data);\n        });\n    });\n\n    describe('pureJson', () => {\n        it('set the response body', () => {\n            const body = { content: 'foo' };\n\n            const res = new ExegesisResponseImpl({ socket: {} } as any, true);\n            res.pureJson(body);\n\n            expect(res.headers['content-type']).to.equal('application/json');\n            expect(res.body).to.eql(body);\n        });\n    });\n});\n"
  },
  {
    "path": "test/core/pluginTest.ts",
    "content": "import chai from 'chai';\nimport http from 'http';\nimport 'mocha';\nimport { ExegesisPlugin, ExegesisPluginInstance } from '../../src';\nimport generateExegesisRunner from '../../src/core/exegesisRunner';\nimport PluginsManager from '../../src/core/PluginsManager';\nimport { FakeApiInterface } from '../fixtures/FakeApiInterface';\n\nconst { expect } = chai;\n\ndescribe('Plugin Test', function () {\n    it('should run all the phases of a plugin on a request', async function () {\n        const callOrder: string[] = [];\n\n        const controller = () => {\n            callOrder.push('controller');\n        };\n\n        const api = new FakeApiInterface(controller);\n\n        const pluginInstance: ExegesisPluginInstance = {\n            preCompile() {\n                callOrder.push('preCompile');\n            },\n\n            preRouting() {\n                callOrder.push('preRouting');\n            },\n\n            postRouting() {\n                callOrder.push('postRouting');\n            },\n\n            postSecurity() {\n                callOrder.push('postSecurity');\n            },\n\n            postController() {\n                callOrder.push('postController');\n            },\n\n            postResponseValidation() {\n                callOrder.push('postResponseValidation');\n            },\n        };\n\n        const plugin: ExegesisPlugin = {\n            info: {\n                name: 'dummyPlugin',\n            },\n            makeExegesisPlugin() {\n                return pluginInstance;\n            },\n        };\n\n        const plugins = new PluginsManager({}, [plugin]);\n\n        const runner = await generateExegesisRunner(api, {\n            autoHandleHttpErrors: false,\n            plugins,\n            validateDefaultResponses: false,\n            originalOptions: {},\n        });\n\n        await runner({} as http.IncomingMessage, { socket: {} } as http.ServerResponse);\n\n        expect(callOrder).to.eql([\n            // Note: no preCompile here, because it would be called when compiling options.\n            'preRouting',\n            'postRouting',\n            'postSecurity',\n            'controller',\n            'postController',\n            'postResponseValidation',\n        ]);\n    });\n});\n"
  },
  {
    "path": "test/fixtures/FakeApiInterface.ts",
    "content": "import { ApiInterface, ResolvedPath } from '../../src';\nimport { Controller } from '../../lib/types';\n\nexport class FakeApiInterface implements ApiInterface<void> {\n    controller: Controller;\n    constructor(controller: Controller) {\n        this.controller = controller;\n    }\n\n    resolve(_method: string, url: string): ResolvedPath<void> | undefined {\n        const path: ResolvedPath<void> = {\n            operation: {\n                parseParameters: () => ({\n                    query: {},\n                    cookie: {},\n                    header: {},\n                    path: {},\n                    server: {},\n                }),\n                validateParameters: () => null,\n                parameterLocations: {\n                    query: {},\n                    cookie: {},\n                    header: {},\n                    path: {},\n                },\n                bodyParser: undefined,\n                validateBody: undefined,\n                exegesisControllerName: 'test',\n                operationId: 'operation',\n                controllerModule: undefined,\n                controller: this.controller,\n\n                validateResponse: () => ({\n                    errors: null,\n                    isDefault: false,\n                }),\n\n                // Returns the authentication data, or undefined if user could not be authenticated.\n                authenticate: () => Promise.resolve(undefined),\n            },\n            api: void 0,\n            allowedMethods: ['get'],\n            path: url,\n            baseUrl: '',\n        };\n\n        return path;\n    }\n}\n"
  },
  {
    "path": "test/fixtures/FakeExegesisContext.ts",
    "content": "import * as http from 'http';\nimport {\n    ExegesisContext,\n    ExegesisResponse,\n    AuthenticationSuccess,\n    ParametersByLocation,\n    ParametersMap,\n    ParameterLocation,\n    ParameterLocations,\n    ExegesisOptions,\n    ExegesisRoute,\n} from '../../src';\nimport { HttpError, ValidationError } from '../../src/errors';\nimport ExegesisResponseImpl from '../../src/core/ExegesisResponseImpl';\n\nexport default class FakeExegesisContext implements ExegesisContext {\n    readonly req: http.IncomingMessage;\n    readonly origRes: http.ServerResponse;\n    readonly res: ExegesisResponse;\n    api: any;\n    security?: { [scheme: string]: AuthenticationSuccess };\n    user?: any;\n    params: ParametersByLocation<ParametersMap<any>> = {\n        query: {},\n        header: {},\n        server: {},\n        path: {},\n        cookie: {},\n    };\n    requestBody: any = {};\n    parameterLocations: ParameterLocations = {\n        query: {},\n        header: {},\n        path: {},\n        cookie: {},\n    };\n    options: ExegesisOptions = {};\n    route: ExegesisRoute = {\n        path: '',\n    };\n    baseUrl: string = '';\n\n    constructor() {\n        this.req = {} as http.IncomingMessage;\n        this.origRes = { socket: {} } as http.ServerResponse;\n        this.res = new ExegesisResponseImpl(this.origRes, true);\n    }\n\n    makeError(statusCode: number, message: string): Error {\n        return new HttpError(statusCode, message);\n    }\n\n    makeValidationError(message: string, location: ParameterLocation) {\n        return new ValidationError([{ message, location }]);\n    }\n\n    /**\n     * Returns true if the response has already been sent.\n     */\n    isResponseFinished() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "test/fixtures/index.ts",
    "content": "import * as jsonPtr from 'json-ptr';\nimport * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from '../../src/oas3/Oas3CompileContext';\nimport * as options from '../../src/options';\n\nexport const defaultCompiledOptions: options.ExegesisCompiledOptions = options.compileOptions();\n\nexport function makeOpenApiDoc(): oas3.OpenAPIObject {\n    return {\n        openapi: '3.0.1',\n        info: {\n            title: 'foo',\n            version: '1.0.0',\n        },\n        paths: {},\n    };\n}\n\nexport function makeContext(\n    openApiDoc: oas3.OpenAPIObject,\n    jsonPointer: string,\n    options?: Partial<options.ExegesisCompiledOptions>\n) {\n    return new Oas3CompileContext(\n        openApiDoc,\n        jsonPtr.JsonPointer.decode(jsonPointer).map((x) => `${x}`),\n        Object.assign({}, defaultCompiledOptions, options || {})\n    );\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/echoController.ts",
    "content": "import * as exegesis from '../../../../src';\n\nexport function echo(context: exegesis.ExegesisContext) {\n    return context.req.body;\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/greetController.ts",
    "content": "import { ExegesisContext } from '../../../../src';\n\nexport function greetGet(context: ExegesisContext) {\n    const { name } = context.params.query;\n    return { greeting: `Hello, ${name}!` };\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/malformed.ts",
    "content": "export function getBadResponse() {\n    return { wat: 7 };\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/postWithDefault.ts",
    "content": "import { ExegesisContext } from '../../../../src';\n\nexport function postWithDefault(context: ExegesisContext) {\n    const { name } = context.requestBody;\n    return { greeting: `Hello, ${name}!` };\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/postWithOptionalBody.ts",
    "content": "import { ExegesisContext } from '../../../../src';\n\nexport function postWithOptionalBody(context: ExegesisContext) {\n    const hasBody = !!context.requestBody;\n    return { hasBody };\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/secureController.js",
    "content": "exports.secureGet = function secureGet(context) {\n    return {\n        security: context.security,\n        user: context.user,\n    };\n};\n"
  },
  {
    "path": "test/integration/integration/controllers/statusController.ts",
    "content": "import { ExegesisContext } from '../../../../src';\n\nexport function setStatus(context: ExegesisContext) {\n    context.res.setStatus(400);\n}\n\nexport function status(context: ExegesisContext) {\n    context.res.status(400);\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/streamController.ts",
    "content": "import { Readable } from 'stream';\nimport * as exegesis from '../../../../src';\n\nexport function replyWithStream(context: exegesis.ExegesisContext) {\n    context.res.setHeader('content-type', 'application/json');\n\n    const body = new Readable({\n        read() {\n            this.push('{\"message\": \"This was streamed\"}');\n            this.push(null);\n        },\n    });\n\n    return body;\n}\n"
  },
  {
    "path": "test/integration/integration/customErrorFormattingTest.ts",
    "content": "import * as http from 'http';\nimport * as path from 'path';\nimport { makeFetch } from 'supertest-fetch';\nimport * as exegesis from '../../../src';\nimport { handleError } from './customErrorHandler';\n\nasync function sessionAuthenticator(\n    context: exegesis.ExegesisPluginContext\n): Promise<exegesis.AuthenticationResult | undefined> {\n    const session = context.req.headers.session;\n    if (!session || typeof session !== 'string') {\n        return undefined;\n    }\n    if (session === 'lame') {\n        return {\n            type: 'success',\n            user: { name: 'Mr. Lame' },\n            roles: [],\n        };\n    } else if (session === 'secret') {\n        return {\n            type: 'success',\n            user: { name: 'jwalton' },\n            roles: ['readWrite', 'admin'],\n        };\n    } else {\n        throw context.makeError(403, 'Invalid session.');\n    }\n}\n\nasync function createServer() {\n    const options: exegesis.ExegesisOptions = {\n        controllers: path.resolve(__dirname, './controllers'),\n        authenticators: {\n            sessionKey: sessionAuthenticator,\n        },\n        controllersPattern: '**/*.@(ts|js)',\n        autoHandleHttpErrors: handleError,\n    };\n\n    const middleware = await exegesis.compileApi(\n        path.resolve(__dirname, './openapi.yaml'),\n        options\n    );\n\n    return http.createServer((req, res) =>\n        middleware!(req, res, (err) => {\n            if (err) {\n                console.error(err.stack);\n                res.writeHead(500);\n                res.end(`Internal error: ${err.message}`);\n            } else {\n                res.writeHead(404);\n                res.end();\n            }\n        })\n    );\n}\n\ndescribe('integration test', function () {\n    beforeEach(async function () {\n        this.server = await createServer();\n    });\n\n    afterEach(function () {\n        if (this.server) {\n            this.server.close();\n        }\n    });\n\n    describe('parameters', function () {\n        it('should return an error for missing parameters', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet`)\n                .expect(400)\n                .expect('content-type', 'application/json')\n                .expectBody({\n                    message: 'Validation errors',\n                    errors: [\n                        {\n                            message: 'Missing required query parameter \"name\"',\n                            location: {\n                                in: 'query',\n                                name: 'name',\n                                path: '',\n                            },\n                            keyword: 'missing',\n                        },\n                    ],\n                });\n        });\n\n        it('should return an error for invalid parameters', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet?name=A`)\n                .expect(400)\n                .expect('content-type', 'application/json')\n                .expectBody({\n                    message: 'Validation errors',\n                    errors: [\n                        {\n                            location: {\n                                in: 'query',\n                                name: 'name',\n                                path: '',\n                            },\n                            message: 'must NOT have fewer than 2 characters',\n                            keyword: 'minLength',\n                            params: {\n                                limit: 2,\n                            },\n                        },\n                    ],\n                });\n        });\n    });\n\n    describe('security', function () {\n        it('should require authentication from an authenticator', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/secure`).expect(401).expectBody({\n                message: 'Must authenticate using one of the following schemes: sessionKey.',\n            });\n        });\n\n        it('should return an error from an authenticator', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/secure`, {\n                headers: { session: 'wrong' },\n            })\n                .expect(403)\n                .expectBody({ message: 'Invalid session.' });\n        });\n    });\n\n    describe('post', function () {\n        it('return an error for invalid content-type', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, {\n                method: 'post',\n                headers: { 'content-type': 'application/xml' },\n                body: '<name>Joe</name>',\n            })\n                .expect(400)\n                .expectBody({ message: 'Invalid content-type: application/xml' });\n        });\n\n        it('return an error for no body if body is required', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, { method: 'post' })\n                .expect(400)\n                .expectBody({ message: 'Missing content-type. Expected one of: application/json' });\n        });\n\n        it('return an error for bad json', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, {\n                method: 'post',\n                headers: { 'content-type': 'application/json' },\n                body: '{',\n            }).expect(400);\n        });\n    });\n});\n"
  },
  {
    "path": "test/integration/integration/customErrorHandler.ts",
    "content": "import stringToStream from '../../../src/utils/stringToStream';\nimport { ValidationError } from '../../../src/errors';\n\nexport function handleError(err: Error) {\n    if (err instanceof ValidationError) {\n        const errors = err.errors.map((error) => {\n            let formattedError = {\n                message: error.message,\n            };\n\n            if (error.location) {\n                formattedError = Object.assign(formattedError, {\n                    location: {\n                        in: error.location.in,\n                        name: error.location.name,\n                        path: error.location.path,\n                    },\n                });\n            }\n\n            if (error.ajvError) {\n                formattedError = Object.assign(formattedError, {\n                    keyword: error.ajvError.keyword,\n                    params: error.ajvError.params,\n                });\n            } else if (error.message.startsWith('Missing')) {\n                formattedError = Object.assign(formattedError, {\n                    keyword: 'missing',\n                });\n            }\n\n            return formattedError;\n        });\n\n        return {\n            status: err.status,\n            headers: { 'content-type': 'application/json' },\n            body: stringToStream(\n                JSON.stringify({\n                    message: 'Validation errors',\n                    errors,\n                }),\n                'utf-8'\n            ),\n        };\n    } else if (Number.isInteger((err as any).status)) {\n        return {\n            status: (err as any).status,\n            headers: { 'content-type': 'application/json' },\n            body: stringToStream(JSON.stringify({ message: err.message }), 'utf-8'),\n        };\n    } else {\n        throw err;\n    }\n}\n"
  },
  {
    "path": "test/integration/integration/integrationTest.ts",
    "content": "import { expect } from 'chai';\nimport * as http from 'http';\nimport * as path from 'path';\nimport { makeFetch } from 'supertest-fetch';\nimport * as exegesis from '../../../src';\n\nasync function sessionAuthenticator(\n    context: exegesis.ExegesisPluginContext\n): Promise<exegesis.AuthenticationResult | undefined> {\n    const session = context.req.headers.session;\n    if (!session || typeof session !== 'string') {\n        return undefined;\n    }\n    if (session === 'lame') {\n        return {\n            type: 'success',\n            user: { name: 'Mr. Lame' },\n            roles: [],\n        };\n    } else if (session === 'secret') {\n        return {\n            type: 'success',\n            user: { name: 'jwalton' },\n            roles: ['readWrite', 'admin'],\n        };\n    } else {\n        throw context.makeError(403, 'Invalid session.');\n    }\n}\n\nasync function createServer(options: exegesis.ExegesisOptions) {\n    const middleware = await exegesis.compileApi(\n        path.resolve(__dirname, './openapi.yaml'),\n        options\n    );\n\n    const server = http.createServer((req, res) =>\n        middleware!(req, res, (err) => {\n            // if(err instanceof exegesis.ValidationError) {\n            //     res.writeHead(err.status);\n            //     res.end(JSON.stringify({message: err.message, errors: err.errors}));\n            // } else if(err instanceof exegesis.HttpError) {\n            //     res.writeHead(err.status);\n            //     res.end(JSON.stringify({message: err.message}));\n            // } else if(err) {\n            if (err) {\n                console.error(err.stack);\n                res.writeHead(500);\n                res.end(`Internal error: ${err.message}`);\n            } else {\n                res.writeHead(404);\n                res.end();\n            }\n        })\n    );\n\n    return server;\n}\n\ndescribe('integration test', function () {\n    beforeEach(async function () {\n        this.server = await createServer({\n            controllers: path.resolve(__dirname, './controllers'),\n            authenticators: {\n                sessionKey: sessionAuthenticator,\n            },\n            controllersPattern: '**/*.@(ts|js)',\n        });\n    });\n\n    afterEach(function () {\n        if (this.server) {\n            this.server.close();\n        }\n    });\n\n    describe('parameters', function () {\n        it('should succesfully call an API', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet?name=Jason`)\n                .expect(200)\n                .expect('content-type', 'application/json')\n                .expectBody({ greeting: 'Hello, Jason!' });\n        });\n\n        it('should return an error for missing parameters', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet`)\n                .expect(400)\n                .expect('content-type', 'application/json')\n                .expectBody({\n                    message: 'Validation errors',\n                    errors: [\n                        {\n                            message: 'Missing required query parameter \"name\"',\n                            location: {\n                                docPath: '/paths/~1greet/get/parameters/0',\n                                in: 'query',\n                                name: 'name',\n                                path: '',\n                            },\n                        },\n                    ],\n                });\n        });\n\n        it('should return an error for invalid parameters', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet?name=A`)\n                .expect(400)\n                .expect('content-type', 'application/json')\n                .expectBody({\n                    message: 'Validation errors',\n                    errors: [\n                        {\n                            location: {\n                                docPath: '/paths/~1greet/get/parameters/0/schema',\n                                in: 'query',\n                                name: 'name',\n                                path: '',\n                            },\n                            message: 'must NOT have fewer than 2 characters',\n                        },\n                    ],\n                });\n        });\n    });\n\n    describe('streaming reply', function () {\n        it('should stream a reply to the client', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/streamResponse`).expect(200).expectBody({\n                message: 'This was streamed',\n            });\n        });\n    });\n\n    describe('security', function () {\n        it('should require authentication from an authenticator', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/secure`).expect(401).expectBody({\n                message: 'Must authenticate using one of the following schemes: sessionKey.',\n            });\n        });\n\n        it('should return an error from an authenticator', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/secure`, {\n                headers: { session: 'wrong' },\n            })\n                .expect(403)\n                .expectBody({ message: 'Invalid session.' });\n        });\n\n        it('should authenticate successfully', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/secure`, {\n                headers: { session: 'secret' },\n            })\n                .expect(200)\n                .expectBody({\n                    security: {\n                        sessionKey: {\n                            type: 'success',\n                            user: { name: 'jwalton' },\n                            roles: ['readWrite', 'admin'],\n                        },\n                    },\n                    user: { name: 'jwalton' },\n                });\n        });\n    });\n\n    describe('set return status', function () {\n        it('should set status with `setStatus`', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/status/setStatus`).expect(400);\n        });\n\n        it('should set status with `status`', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/status/status`).expect(400);\n        });\n    });\n\n    describe('post', function () {\n        it('should post a body', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, {\n                method: 'post',\n                headers: { 'content-type': 'application/json' },\n                body: JSON.stringify({ name: 'Joe' }),\n            })\n                .expect(200)\n                .expectBody({ greeting: 'Hello, Joe!' });\n        });\n\n        it('return an error for invalid content-type', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, {\n                method: 'post',\n                headers: { 'content-type': 'application/xml' },\n                body: '<name>Joe</name>',\n            })\n                .expect(400)\n                .expectBody({ message: 'Invalid content-type: application/xml' });\n        });\n\n        it('return an error for no body if body is required', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithDefault`, { method: 'post' })\n                .expect(400)\n                .expectBody({ message: 'Missing content-type. Expected one of: application/json' });\n        });\n\n        it('should handle a missing optional body correctly', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithOptionalBody`, {\n                method: 'post',\n            })\n                .expect(200)\n                .expectBody({ hasBody: false });\n        });\n\n        it('should handle an optional body correctly', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/postWithOptionalBody`, {\n                method: 'post',\n                headers: { 'content-type': 'application/json' },\n                body: JSON.stringify({ name: 'joe' }),\n            })\n                .expect(200)\n                .expectBody({ hasBody: true });\n        });\n\n        it('should return 405 if post is not allowed.', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet`, {\n                method: 'post',\n                headers: { 'content-type': 'application/json' },\n                body: JSON.stringify({ name: 'joe' }),\n            })\n                .expect(405)\n                .expect('allow', 'GET')\n                .expect('content-type', 'application/json')\n                .expectBody({\n                    message: 'Method POST not allowed for /greet',\n                });\n        });\n    });\n\n    it('should correctly parse application/x-www-form-urlencoded', async function () {\n        const fetch = makeFetch(this.server);\n        await fetch(`/wwwFormUrlencoded`, {\n            method: 'post',\n            headers: { 'content-type': 'application/x-www-form-urlencoded' },\n            body: 'arr=a,b&other=foo',\n        }).expectBody({\n            arr: ['a', 'b'],\n            other: 'foo',\n        });\n    });\n\n    describe('response validation', function () {\n        let errors = 0;\n\n        beforeEach(async function () {\n            errors = 0;\n\n            this.server.close();\n            this.server = await createServer({\n                controllers: path.resolve(__dirname, './controllers'),\n                authenticators: {\n                    sessionKey: sessionAuthenticator,\n                },\n                controllersPattern: '**/*.@(ts|js)',\n                onResponseValidationError: () => {\n                    errors++;\n                },\n            });\n        });\n\n        it('should identify a bad response', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/malformedResponse`).expect(200);\n\n            expect(errors).to.equal(1);\n        });\n\n        it('should not complaint about an OK response', async function () {\n            const fetch = makeFetch(this.server);\n            await fetch(`/greet?name=Jason`).expect(200);\n\n            expect(errors).to.equal(0);\n        });\n    });\n});\n"
  },
  {
    "path": "test/integration/integration/openapi.yaml",
    "content": "openapi: '3.1.0'\ninfo:\n  version: 1.0.0\n  title: Exegesis Integration Test\n  license:\n    name: MIT\npaths:\n  /greet:\n    get:\n      summary: List all pets\n      x-exegesis-controller: greetController\n      operationId: greetGet\n      parameters:\n        - name: name\n          in: query\n          description: Name of user to greet\n          required: true\n          schema:\n            type: string\n            minLength: 2\n      responses:\n        '200':\n          description: Greet message.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - greeting\n                properties:\n                  greeting: { type: string }\n        default:\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Error'\n  /malformedResponse:\n    get:\n      x-exegesis-controller: malformed\n      operationId: getBadResponse\n      responses:\n        '200':\n          description: Greet message.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - greeting\n                properties:\n                  greeting: { type: string }\n  /secure:\n    get:\n      summary: A secure operation\n      x-exegesis-controller: secureController\n      operationId: secureGet\n      security:\n        - sessionKey: []\n      responses:\n        '200':\n          description: Greet message.\n          content:\n            application/json:\n              schema: { type: 'object' }\n  /status/setStatus:\n    get:\n      summary: Test `res.setStatus`\n      x-exegesis-controller: statusController\n      operationId: setStatus\n      responses:\n        '200':\n          description: OK\n          content:\n            application/json:\n              schema: { type: 'object' }\n  /status/status:\n    get:\n      summary: Test `res.status`\n      x-exegesis-controller: statusController\n      operationId: status\n      responses:\n        '200':\n          description: OK\n          content:\n            application/json:\n              schema: { type: 'object' }\n  /postWithDefault:\n    post:\n      summary: An operation with a default body\n      x-exegesis-controller: postWithDefault\n      operationId: postWithDefault\n      requestBody:\n        required: true\n        content:\n          'application/json':\n            schema:\n              type: object\n              properties:\n                name:\n                  type: string\n              default:\n                name: Tim\n      responses:\n        '200':\n          description: Greet message.\n          content:\n            application/json:\n              schema: { type: 'object' }\n  /postWithOptionalBody:\n    post:\n      summary: An operation with an optional body\n      x-exegesis-controller: postWithOptionalBody\n      operationId: postWithOptionalBody\n      requestBody:\n        required: false\n        content:\n          'application/json':\n            schema:\n              type: object\n              properties:\n                name:\n                  type: string\n      responses:\n        '200':\n          description: Result\n          content:\n            application/json:\n              schema: { type: 'object' }\n  /streamResponse:\n    get:\n      summary: Reply with a stream instead of a JSON object.\n      x-exegesis-controller: streamController\n      operationId: replyWithStream\n      responses:\n        '200':\n          description: Streamed reply.\n          content:\n            application/json:\n              schema:\n                type: object\n                required:\n                  - message\n                properties:\n                  message: { type: string }\n        default:\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Error'\n  /wwwFormUrlencoded:\n    post:\n      summary: Post with application/x-www-form-urlencoded\n      x-exegesis-controller: echoController.ts\n      x-exegesis-operationId: echo\n      requestBody:\n        required: true\n        content:\n          'application/x-www-form-urlencoded':\n            schema:\n              type: object\n              properties:\n                arr:\n                  type: array\n                  items:\n                    type: string\n                other:\n                  type: string\n            encoding:\n              arr:\n                style: form\n                explode: false\n      responses:\n        '200':\n          description: Respond\n          content:\n            'application/json':\n              schema: { type: 'object' }\n\ncomponents:\n  securitySchemes:\n    sessionKey:\n      type: 'apiKey'\n      name: 'session'\n      in: 'header'\n  schemas:\n    Error:\n      type: object\n      required:\n        - message\n      properties:\n        message: { type: string }\n"
  },
  {
    "path": "test/integration/issue-132/entry.yaml",
    "content": "# entry.yaml\npaths:\n  Entry:\n    get:\n      operationId: fetch\n      responses:\n        default:\n          description: Recursive entry.\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  entry:\n                    $ref: '#/components/schemas/Recurse'\ncomponents:\n  schemas:\n    Recurse:\n      type: object\n      properties:\n        recurse:\n          $ref: '#/components/schemas/Recurse'\n"
  },
  {
    "path": "test/integration/issue-132/issue132Test.ts",
    "content": "// import { expect } from 'chai';\nimport * as path from 'path';\nimport * as exegesis from '../../../src';\n\ndescribe('Issue 132 test', function () {\n    it('should load a yaml file with recursive definitions', async function () {\n        await exegesis.compileApi(path.resolve(__dirname, './openapi.yaml'), {});\n    });\n});\n"
  },
  {
    "path": "test/integration/issue-132/openapi.yaml",
    "content": "openapi: 3.0.1\ninfo:\n  title: Demo API\n  version: 1.0.0\npaths:\n  /entry:\n    $ref: 'entry.yaml#/paths/Entry'\n"
  },
  {
    "path": "test/oas3/OperationTest.ts",
    "content": "import 'mocha';\nimport chai from 'chai';\nimport chaiAsPromised from 'chai-as-promised';\nimport * as oas3 from 'openapi3-ts';\n\nimport Operation from '../../src/oas3/Operation';\nimport Oas3CompileContext from '../../src/oas3/Oas3CompileContext';\nimport { makeOpenApiDoc } from '../fixtures';\nimport { ExegesisOptions } from '../../src';\nimport { compileOptions } from '../../src/options';\nimport FakeExegesisContext from '../fixtures/FakeExegesisContext';\nimport { Readable, Transform } from 'stream';\nimport { ResponseObject } from 'openapi3-ts';\n\nchai.use(chaiAsPromised);\nconst { expect } = chai;\n\nconst BASIC_AUTH_SUCCESS_RESULT = { type: 'success', user: 'benbria' };\nconst DEFAULT_OAUTH_RESULT = { type: 'success', user: 'benbria', scopes: ['admin'] };\n\nfunction makeOperation(\n    method: string,\n    operation: oas3.OperationObject,\n    opts: {\n        openApiDoc?: any;\n        options?: ExegesisOptions;\n    } = {}\n) {\n    const openApiDoc = makeOpenApiDoc();\n\n    openApiDoc.components = openApiDoc.components || {};\n    openApiDoc.components.securitySchemes = {\n        basicAuth: { type: 'http', scheme: 'Basic' },\n        oauth: { type: 'oauth2', flows: {} },\n    };\n\n    if (opts.openApiDoc) {\n        Object.assign(openApiDoc, opts.openApiDoc);\n    }\n\n    openApiDoc.paths['/path'] = {\n        [method]: operation,\n    };\n\n    const options = opts.options || {\n        authenticators: {\n            basicAuth() {\n                return undefined;\n            },\n            oauth() {\n                return DEFAULT_OAUTH_RESULT;\n            },\n        },\n    };\n\n    const context = new Oas3CompileContext(\n        openApiDoc,\n        ['paths', '/path', method],\n        compileOptions(options)\n    );\n    return new Operation(context, operation, openApiDoc.paths['/path'], method, undefined, []);\n}\n\ndescribe('oas3 Operation', function () {\n    describe('security', function () {\n        beforeEach(function () {\n            this.operation = {\n                responses: { 200: { description: 'ok' } },\n                security: [{ oauth: ['admin'] }],\n            };\n\n            this.exegesisContext = new FakeExegesisContext();\n        });\n\n        it('should identify the security requirements for an operation', function () {\n            const operation: Operation = makeOperation('get', this.operation);\n\n            expect(operation.securityRequirements).to.eql([{ oauth: ['admin'] }]);\n        });\n\n        it('should identify the security requirements for an operation from root', function () {\n            delete this.operation.security;\n            const operation: Operation = makeOperation('get', this.operation, {\n                openApiDoc: {\n                    security: [{ basicAuth: [] }],\n                },\n            });\n\n            expect(operation.securityRequirements).to.eql([{ basicAuth: [] }]);\n        });\n\n        it('should override the security requirements for an operation', function () {\n            const operation: Operation = makeOperation('get', this.operation, {\n                openApiDoc: { security: [{ basicAuth: [] }] },\n            });\n\n            expect(operation.securityRequirements).to.eql([{ oauth: ['admin'] }]);\n        });\n\n        it('should error if an op requires a security scheme without a configured authenticator', function () {\n            this.operation.security = [{ foo: [] }];\n            expect(() => makeOperation('get', this.operation)).to.throw(\n                'Operation /paths/~1path/get references security scheme \"foo\" but no authenticator was provided'\n            );\n        });\n\n        it('should authenticate an incoming request', async function () {\n            const operation: Operation = makeOperation('get', this.operation);\n            const authenticated = await operation.authenticate(this.exegesisContext);\n\n            expect(authenticated).to.exist;\n            expect(authenticated).to.eql({\n                oauth: DEFAULT_OAUTH_RESULT,\n            });\n        });\n\n        it('should fail to authenticate an incoming request if no credentials are provided', async function () {\n            const options = {\n                authenticators: {\n                    oauth() {\n                        return undefined;\n                    },\n                },\n            };\n\n            const operation: Operation = makeOperation('get', this.operation, { options });\n            await operation.authenticate(this.exegesisContext);\n            expect(this.exegesisContext.res.statusCode).to.equal(401);\n            expect(this.exegesisContext.res.body).to.eql({\n                message: 'Must authenticate using one of the following schemes: oauth.',\n            });\n            expect(this.exegesisContext.res.headers['www-authenticate']).to.eql(['Bearer']);\n        });\n\n        it('should set response message to failed authenticator message if set', async function () {\n            const options = {\n                authenticators: {\n                    oauth() {\n                        return { type: 'invalid', message: 'Bearer token expired' };\n                    },\n                },\n            };\n\n            const operation: Operation = makeOperation('get', this.operation, { options });\n            await operation.authenticate(this.exegesisContext);\n            expect(this.exegesisContext.res.statusCode).to.equal(401);\n            expect(this.exegesisContext.res.body).to.eql({\n                message: 'Bearer token expired',\n            });\n            expect(this.exegesisContext.res.headers['www-authenticate']).to.eql(['Bearer']);\n        });\n\n        it('should fail to auth an incoming request if the user does not have the correct scopes', async function () {\n            this.operation.security = [{ oauth: ['scopeYouDontHave'] }];\n            const operation: Operation = makeOperation('get', this.operation);\n            await operation.authenticate(this.exegesisContext);\n            expect(this.exegesisContext.res.statusCode).to.equal(403);\n            expect(this.exegesisContext.res.body).to.eql({\n                message:\n                    \"Authenticated using 'oauth' but missing required scopes: scopeYouDontHave.\",\n            });\n        });\n\n        it('should always authenticate a request with no security requirements', async function () {\n            const options = {\n                authenticators: {\n                    oauth() {\n                        return undefined;\n                    },\n                },\n            };\n            this.operation.security = [];\n\n            const operation: Operation = makeOperation('get', this.operation, { options });\n            const authenticated = await operation.authenticate(this.exegesisContext);\n            expect(authenticated).to.eql({});\n        });\n\n        describe('Security schemes combined via OR', async function () {\n            it('should return result of first successful authenticator', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return BASIC_AUTH_SUCCESS_RESULT;\n                        },\n                        oauth() {\n                            return DEFAULT_OAUTH_RESULT;\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ basicAuth: [] }, { oauth: ['admin'] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.exist;\n                expect(authenticated).to.eql({\n                    basicAuth: BASIC_AUTH_SUCCESS_RESULT,\n                });\n            });\n\n            it('should authenticate a request if a missing result encountered with a success', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return { type: 'missing' };\n                        },\n                        oauth() {\n                            return DEFAULT_OAUTH_RESULT;\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ basicAuth: [] }, { oauth: ['admin'] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.exist;\n                expect(authenticated).to.eql({\n                    oauth: DEFAULT_OAUTH_RESULT,\n                });\n            });\n\n            it('should not authenticate a request if an invalid result encountered before a success', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return { type: 'invalid' };\n                        },\n                        oauth() {\n                            return DEFAULT_OAUTH_RESULT;\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ basicAuth: [] }, { oauth: ['admin'] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message:\n                        'Must authenticate using one of the following schemes: basicAuth, oauth.',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Basic',\n                    'Bearer',\n                ]);\n            });\n\n            it('should not authenticate a request if an invalid result encountered after a success', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return BASIC_AUTH_SUCCESS_RESULT;\n                        },\n                        oauth() {\n                            return { type: 'invalid' };\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ basicAuth: [] }, { oauth: ['admin'] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message:\n                        'Must authenticate using one of the following schemes: basicAuth, oauth.',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Basic',\n                    'Bearer',\n                ]);\n            });\n\n            it('should set message from first authenticator if it all results are invalid', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return { type: 'invalid', message: 'Invalid details' };\n                        },\n                        oauth() {\n                            return { type: 'invalid', message: 'Token expired' };\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ basicAuth: [] }, { oauth: ['admin'] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message: 'Invalid details',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Basic',\n                    'Bearer',\n                ]);\n            });\n        });\n\n        describe('Security schemes combined via AND', async function () {\n            it('should authenticate an incoming request if all authenticators succeed', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return BASIC_AUTH_SUCCESS_RESULT;\n                        },\n                        oauth() {\n                            return DEFAULT_OAUTH_RESULT;\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ oauth: [], basicAuth: [] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.exist;\n                expect(authenticated).to.eql({\n                    basicAuth: BASIC_AUTH_SUCCESS_RESULT,\n                    oauth: DEFAULT_OAUTH_RESULT,\n                });\n            });\n\n            it('should not authenticate an incoming request if one of the results is missing', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return { type: 'missing' };\n                        },\n                        oauth() {\n                            return DEFAULT_OAUTH_RESULT;\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ oauth: [], basicAuth: [] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message:\n                        'Must authenticate using one of the following schemes: (oauth + basicAuth).',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Bearer',\n                    'Basic',\n                ]);\n            });\n\n            it('should not authenticate an incoming request if one of the results is invalid', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return BASIC_AUTH_SUCCESS_RESULT;\n                        },\n                        oauth() {\n                            return { type: 'invalid' };\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ oauth: [], basicAuth: [] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message:\n                        'Must authenticate using one of the following schemes: (oauth + basicAuth).',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Bearer',\n                    'Basic',\n                ]);\n            });\n\n            it('should set message from first authenticator if it all results are invalid', async function () {\n                const options = {\n                    authenticators: {\n                        basicAuth() {\n                            return { type: 'invalid', message: 'Invalid details' };\n                        },\n                        oauth() {\n                            return { type: 'invalid', message: 'Token expired' };\n                        },\n                    },\n                };\n\n                const op: oas3.OperationObject = {\n                    responses: { 200: { description: 'ok' } },\n                    security: [{ oauth: [], basicAuth: [] }],\n                };\n\n                const operation: Operation = makeOperation('get', op, { options });\n                const authenticated = await operation.authenticate(this.exegesisContext);\n                expect(authenticated).to.not.exist;\n                expect(this.exegesisContext.res.statusCode).to.equal(401);\n                expect(this.exegesisContext.res.body).to.eql({\n                    message: 'Token expired',\n                });\n                expect(this.exegesisContext.res.headers['www-authenticate']).to.eql([\n                    'Bearer',\n                    'Basic',\n                ]);\n            });\n        });\n    });\n\n    describe('body', function () {\n        it('should generate a MediaType for each content type', function () {\n            const operation = makeOperation('post', {\n                responses: { 200: { description: 'ok' } },\n                requestBody: {\n                    content: {\n                        'application/json': {\n                            'x-name': 'json',\n                            schema: { type: 'object' },\n                        },\n                        'text/*': {\n                            'x-name': 'text',\n                            schema: { type: 'string' },\n                        },\n                    },\n                },\n            });\n\n            const jsonMediaType = operation.getRequestMediaType('application/json');\n            expect(jsonMediaType, 'application/json media type').to.exist;\n            expect(jsonMediaType!.oaMediaType['x-name']).to.equal('json');\n\n            const plainMediaType = operation.getRequestMediaType('text/plain');\n            expect(plainMediaType, 'text/plain media type').to.exist;\n            expect(plainMediaType!.oaMediaType['x-name']).to.equal('text');\n        });\n    });\n\n    describe('parameters', function () {\n        it('should generate a parameter parser for parameters', function () {\n            const operation = makeOperation('get', {\n                responses: { 200: { description: 'ok' } },\n                parameters: [\n                    {\n                        name: 'myparam',\n                        in: 'query',\n                        schema: { type: 'string' },\n                    },\n                ],\n            });\n\n            const result = operation.parseParameters({\n                headers: undefined,\n                rawPathParams: {},\n                serverParams: undefined,\n                queryString: 'myparam=7',\n            });\n\n            expect(result).to.eql({\n                query: { myparam: '7' },\n                header: {},\n                server: {},\n                path: {},\n                cookie: {},\n            });\n        });\n\n        it('should generate a validator for parameters', function () {\n            const operation = makeOperation('get', {\n                responses: { 200: { description: 'ok' } },\n                parameters: [\n                    {\n                        name: 'myparam',\n                        in: 'query',\n                        schema: { type: 'string' },\n                    },\n                ],\n            });\n\n            expect(\n                operation.validateParameters({\n                    query: { myparam: '7' },\n                    header: {},\n                    server: {},\n                    path: {},\n                    cookie: {},\n                })\n            ).to.equal(null);\n\n            const invalid = {\n                query: { myparam: { foo: 'bar' } },\n                header: {},\n                server: {},\n                path: {},\n                cookie: {},\n            };\n            const errors = operation.validateParameters(invalid);\n            expect(errors, 'error for bad myparam').to.exist;\n            expect(errors!).to.not.be.empty;\n        });\n\n        it('should include raw Ajv response in error', function () {\n            const operation = makeOperation('get', {\n                responses: { 200: { description: 'ok' } },\n                parameters: [\n                    {\n                        name: 'myparam',\n                        in: 'query',\n                        schema: { type: 'string' },\n                    },\n                ],\n            });\n\n            const invalid = {\n                query: { myparam: { foo: 'bar' } },\n                header: {},\n                server: {},\n                path: {},\n                cookie: {},\n            };\n            const errors = operation.validateParameters(invalid);\n            expect(errors).to.not.be.null;\n            if (errors) {\n                expect(errors[0]).to.have.property('ajvError');\n            }\n        });\n\n        it('should generate a map of parameter locations', function () {\n            const operation = makeOperation('get', {\n                responses: { 200: { description: 'ok' } },\n                parameters: [\n                    {\n                        name: 'myparam',\n                        in: 'query',\n                        schema: { type: 'string' },\n                    },\n                ],\n            });\n\n            expect(operation.parameterLocations).to.eql({\n                cookie: {},\n                header: {},\n                path: {},\n                query: {\n                    docPath: '/paths/~1path/get/parameters/0',\n                    in: 'query',\n                    name: 'myparam',\n                    path: '',\n                },\n            });\n        });\n    });\n\n    describe('validate response body', function () {\n        const DEFAULT_RESPONSE: ResponseObject = {\n            description: 'Unexpected error',\n            content: {\n                'application/json': {\n                    schema: {\n                        type: 'object',\n                        required: ['message'],\n                        properties: { message: { type: 'string' } },\n                    },\n                },\n            },\n        };\n\n        it('should correctly validate a response that does not expect a body', function () {\n            const operation = makeOperation('delete', {\n                responses: {\n                    200: { description: 'ok' },\n                    default: DEFAULT_RESPONSE,\n                },\n            });\n\n            const result = operation.validateResponse(\n                {\n                    statusCode: 200,\n                    headers: {},\n                    body: undefined,\n                } as any,\n                false\n            );\n\n            expect(result.errors).to.equal(null);\n        });\n\n        it('should correctly validate a response', function () {\n            const operation = makeOperation('delete', {\n                responses: {\n                    200: DEFAULT_RESPONSE,\n                    default: DEFAULT_RESPONSE,\n                },\n            });\n\n            const result = operation.validateResponse(\n                {\n                    statusCode: 200,\n                    headers: {\n                        'content-type': 'application/json',\n                    },\n                    body: {\n                        message: 'ok',\n                    },\n                } as any,\n                false\n            );\n\n            expect(result.errors).to.equal(null);\n        });\n\n        it('should error on an invalid response', function () {\n            const operation = makeOperation('delete', {\n                responses: {\n                    200: DEFAULT_RESPONSE,\n                    default: DEFAULT_RESPONSE,\n                },\n            });\n\n            const result = operation.validateResponse(\n                {\n                    statusCode: 200,\n                    headers: {\n                        'content-type': 'application/json',\n                    },\n                    body: {},\n                } as any,\n                false\n            );\n\n            expect(result.errors).to.not.be.empty;\n        });\n\n        it('should not try to validate a Readable body', function () {\n            const operation = makeOperation('delete', {\n                responses: {\n                    200: DEFAULT_RESPONSE,\n                    default: DEFAULT_RESPONSE,\n                },\n            });\n\n            const body = new Readable({\n                read() {\n                    this.push('foo');\n                    this.push(null);\n                },\n            });\n\n            const result = operation.validateResponse(\n                {\n                    statusCode: 200,\n                    headers: {\n                        'content-type': 'application/json',\n                    },\n                    body,\n                } as any,\n                false\n            );\n\n            expect(result.errors).to.equal(null);\n        });\n\n        it('should not try to validate a Transform body', function () {\n            const operation = makeOperation('delete', {\n                responses: {\n                    200: DEFAULT_RESPONSE,\n                    default: DEFAULT_RESPONSE,\n                },\n            });\n\n            const readable = new Readable({\n                read() {\n                    this.push('foo');\n                    this.push(null);\n                },\n            });\n\n            const body = new Transform({\n                transform(chunk, _encoding, callback) {\n                    this.push(chunk);\n                    callback();\n                },\n            });\n\n            readable.pipe(body);\n\n            const result = operation.validateResponse(\n                {\n                    statusCode: 200,\n                    headers: {\n                        'content-type': 'application/json',\n                    },\n                    body,\n                } as any,\n                false\n            );\n\n            expect(result.errors).to.equal(null);\n        });\n    });\n});\n"
  },
  {
    "path": "test/oas3/Paths/PathResolverTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport PathResolver from '../../../src/oas3/Paths/PathResolver';\n\ndescribe('oas3 PathResolver', function () {\n    it('empty path resolver should not resolve anything', function () {\n        const resolver = new PathResolver<string>();\n        expect(resolver.resolvePath('/foo/bar')).to.equal(undefined);\n    });\n\n    it('should resolve concrete paths', function () {\n        const resolver = new PathResolver<string>();\n        resolver.registerPath('/foo/bar', 'hello');\n        resolver.registerPath('/foo/baz', 'world');\n\n        expect(resolver.resolvePath('/foo/bar'), '/foo/bar').to.eql({\n            path: '/foo/bar',\n            value: 'hello',\n            rawPathParams: undefined,\n        });\n        expect(resolver.resolvePath('/foo/baz'), '/foo/baz').to.eql({\n            path: '/foo/baz',\n            value: 'world',\n            rawPathParams: undefined,\n        });\n        expect(resolver.resolvePath('/foo/qux'), '/foo/qux').to.equal(undefined);\n    });\n\n    it('should resolve templated paths, and parse raw parameter values', function () {\n        const resolver = new PathResolver<string>();\n        resolver.registerPath('/a/b/{var}', 'hello');\n        resolver.registerPath('/c/{otherVar}/d', 'world');\n\n        expect(resolver.resolvePath('/a/b/value')).to.eql({\n            path: '/a/b/{var}',\n            value: 'hello',\n            rawPathParams: { var: 'value' },\n        });\n        expect(resolver.resolvePath('/a/b/')).to.eql({\n            path: '/a/b/{var}',\n            value: 'hello',\n            rawPathParams: { var: '' },\n        });\n        expect(resolver.resolvePath('/a/b')).to.equal(undefined);\n\n        expect(resolver.resolvePath('/c/test/d')).to.eql({\n            path: '/c/{otherVar}/d',\n            value: 'world',\n            rawPathParams: { otherVar: 'test' },\n        });\n        expect(resolver.resolvePath('/c//d')).to.eql({\n            path: '/c/{otherVar}/d',\n            value: 'world',\n            rawPathParams: { otherVar: '' },\n        });\n        expect(resolver.resolvePath('/c/d')).to.equal(undefined);\n\n        expect(resolver.resolvePath('/foo/qux')).to.equal(undefined);\n    });\n\n    it('should resolve templated paths with multiple parameters', function () {\n        const resolver = new PathResolver<string>();\n        resolver.registerPath('/{a}/{b}/{c}', 'hello');\n        expect(resolver.resolvePath('/1/2/3')).to.eql({\n            path: '/{a}/{b}/{c}',\n            value: 'hello',\n            rawPathParams: { a: '1', b: '2', c: '3' },\n        });\n    });\n\n    it('should resolve paths with unusual templating', function () {\n        const resolver = new PathResolver<string>();\n        resolver.registerPath('/{a}x{b}x{c}', 'hello');\n        resolver.registerPath('/foo/{a}{b}{c}', 'hello');\n        expect(resolver.resolvePath('/1x2x3')).to.eql({\n            path: '/{a}x{b}x{c}',\n            value: 'hello',\n            rawPathParams: { a: '1', b: '2', c: '3' },\n        });\n        expect(resolver.resolvePath('/1x2x3x4')).to.eql({\n            path: '/{a}x{b}x{c}',\n            value: 'hello',\n            rawPathParams: { a: '1x2', b: '3', c: '4' },\n        });\n        expect(resolver.resolvePath('/foo/123')).to.eql({\n            path: '/foo/{a}{b}{c}',\n            value: 'hello',\n            rawPathParams: { a: '123', b: '', c: '' },\n        });\n    });\n\n    it('should resolve concrete paths before paths with templates', function () {\n        const resolver = new PathResolver<string>();\n        resolver.registerPath('/a/b/{var}', 'hello');\n        resolver.registerPath('/a/b/c', 'world');\n\n        expect(resolver.resolvePath('/a/b/value')).to.eql({\n            path: '/a/b/{var}',\n            value: 'hello',\n            rawPathParams: { var: 'value' },\n        });\n        expect(resolver.resolvePath('/a/b/c')).to.eql({\n            path: '/a/b/c',\n            value: 'world',\n            rawPathParams: undefined,\n        });\n\n        // Same results even if paths are registered in the opposite order.\n        const resolver2 = new PathResolver<string>();\n        resolver2.registerPath('/a/b/c', 'world');\n        resolver2.registerPath('/a/b/{var}', 'hello');\n\n        expect(resolver.resolvePath('/a/b/value')).to.eql({\n            path: '/a/b/{var}',\n            value: 'hello',\n            rawPathParams: { var: 'value' },\n        });\n        expect(resolver.resolvePath('/a/b/c')).to.eql({\n            path: '/a/b/c',\n            value: 'world',\n            rawPathParams: undefined,\n        });\n    });\n});\n"
  },
  {
    "path": "test/oas3/Paths/PathsTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport Paths from '../../../src/oas3/Paths';\nimport Oas3CompileContext from '../../../src/oas3/Oas3CompileContext';\nimport { defaultCompiledOptions, makeOpenApiDoc } from '../../fixtures';\n\nconst DUMMY_PATH_OBJECT = {\n    get: {\n        responses: {\n            200: {\n                description: 'OK!',\n            },\n        },\n    },\n};\n\ndescribe('oas3 Paths', function () {\n    it('should resolve paths', function () {\n        const openApiDoc = makeOpenApiDoc();\n        openApiDoc.paths['/foo'] = DUMMY_PATH_OBJECT;\n        const paths = new Paths(\n            new Oas3CompileContext(openApiDoc, ['paths'], defaultCompiledOptions),\n            undefined\n        );\n        const resolved = paths.resolvePath('/foo');\n        expect(resolved).to.exist;\n        expect(resolved!.path.oaPath).to.eql(DUMMY_PATH_OBJECT);\n        expect(resolved!.rawPathParams).to.eql(undefined);\n    });\n\n    it('should resolve a path with parameters', function () {\n        const openApiDoc = makeOpenApiDoc();\n        openApiDoc.paths['/{var}/foo'] = Object.assign(\n            {\n                parameters: [\n                    {\n                        name: 'var',\n                        in: 'path',\n                        required: true,\n                        schema: { type: 'string' },\n                    },\n                ],\n            },\n            DUMMY_PATH_OBJECT\n        );\n\n        const paths = new Paths(\n            new Oas3CompileContext(openApiDoc, ['paths'], defaultCompiledOptions),\n            undefined\n        );\n        const resolved = paths.resolvePath('/bar/foo');\n        expect(resolved).to.exist;\n        expect(resolved!.rawPathParams).to.eql({ var: 'bar' });\n    });\n\n    it('should not treat specitifcation extensions as paths', function () {\n        const openApiDoc = makeOpenApiDoc();\n        openApiDoc.paths['x-my-extension'] = DUMMY_PATH_OBJECT;\n        const paths = new Paths(\n            new Oas3CompileContext(openApiDoc, ['paths'], defaultCompiledOptions),\n            undefined\n        );\n        const resolved = paths.resolvePath('x-my-extension');\n        expect(resolved).to.not.exist;\n    });\n\n    it('error on paths that do not start with /', function () {\n        const openApiDoc = makeOpenApiDoc();\n        openApiDoc.paths['foo'] = DUMMY_PATH_OBJECT;\n        expect(\n            () =>\n                new Paths(\n                    new Oas3CompileContext(openApiDoc, ['paths'], defaultCompiledOptions),\n                    undefined\n                )\n        ).to.throw('Invalid path \"foo\"');\n    });\n\n    it('should allow empty paths object', function () {\n        const openApiDoc = makeOpenApiDoc();\n        expect(\n            () =>\n                new Paths(\n                    new Oas3CompileContext(openApiDoc, ['paths'], defaultCompiledOptions),\n                    undefined\n                )\n        ).to.not.throw;\n    });\n});\n"
  },
  {
    "path": "test/oas3/ResponsesTest.ts",
    "content": "import chai from 'chai';\nimport chaiAsPromised from 'chai-as-promised';\nimport 'mocha';\nimport * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from '../../src/oas3/Oas3CompileContext';\nimport Responses from '../../src/oas3/Responses';\nimport { compileOptions } from '../../src/options';\nimport { makeOpenApiDoc } from '../fixtures';\nimport stringToStream from '../../src/utils/stringToStream';\nimport { ResponsesObject } from 'openapi3-ts';\n\nchai.use(chaiAsPromised);\nconst { expect } = chai;\n\nconst DEFAULT_RESPONSE: ResponsesObject = {\n    200: {\n        description: '',\n        content: {\n            'application/json': {\n                schema: {\n                    type: 'object',\n                    required: ['foo'],\n                    properties: {\n                        foo: { type: 'string' },\n                    },\n                },\n            },\n        },\n    },\n    203: {\n        description: 'No content.',\n    },\n    default: {\n        description: 'Error',\n        content: {\n            'application/json': {\n                schema: {\n                    type: 'object',\n                    required: ['message'],\n                    properties: {\n                        message: { type: 'string' },\n                    },\n                },\n            },\n        },\n    },\n};\n\nconst RESPONSE_WITH_MULTIPLE_CONTENT_TYPES = {\n    200: {\n        description: '',\n        content: {\n            'application/json': {\n                schema: {\n                    type: 'object',\n                    required: ['foo'],\n                    properties: {\n                        foo: { type: 'string' },\n                    },\n                },\n            },\n            'application/xml': {\n                schema: {\n                    type: 'object',\n                    required: ['baz'],\n                    properties: {\n                        baz: { type: 'string' },\n                    },\n                },\n            },\n        },\n    },\n};\n\nconst RESPONSE_WITH_NO_SCHEMA = {\n    200: {\n        description: '',\n        content: {\n            'application/json': {},\n        },\n    },\n};\n\nconst RESPONSE_WITH_TEXT_PLAIN = {\n    200: {\n        description: '',\n        content: {\n            'text/plain': {},\n        },\n    },\n};\n\nfunction makeResponses(responses: oas3.ResponsesObject) {\n    const openApiDoc = makeOpenApiDoc();\n\n    openApiDoc.paths['/path'] = {\n        get: { responses },\n    };\n\n    const context = new Oas3CompileContext(\n        openApiDoc,\n        ['paths', '/path', 'get', 'responses'],\n        compileOptions()\n    );\n    return new Responses(context, responses);\n}\n\ndescribe('oas3 Responses', function () {\n    it('should validate a JSON response', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { foo: 'bar' },\n            true\n        );\n        expect(result).to.eql({\n            errors: null,\n            isDefault: false,\n        });\n    });\n\n    it('should fail a bad JSON response', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { what: 'bar' },\n            true\n        );\n\n        expect(result).to.eql({\n            isDefault: false,\n            errors: [\n                {\n                    location: {\n                        docPath: '/paths/~1path/get/responses/200/content/application~1json/schema',\n                        in: 'response',\n                        name: 'body',\n                        path: '',\n                    },\n                    message: \"must have required property 'foo'\",\n                    ajvError: {\n                        instancePath: '/value',\n                        keyword: 'required',\n                        message: \"must have required property 'foo'\",\n                        params: {\n                            missingProperty: 'foo',\n                        },\n                        schemaPath: '#/properties/value/required',\n                    },\n                },\n            ],\n        });\n    });\n\n    it('should fail a response with an unexpected content-type', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'text/plain' },\n            'hello',\n            true\n        );\n\n        expect(result).to.eql({\n            isDefault: false,\n            errors: [\n                {\n                    location: {\n                        docPath: '/paths/~1path/get/responses/200',\n                        in: 'response',\n                        name: 'body',\n                    },\n                    message: 'Unexpected content-type for 200 response: text/plain.',\n                },\n            ],\n        });\n    });\n\n    it('should be strict about JSON responses', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { foo: 7 }, // This should fail - no type coercion for responses.\n            true\n        );\n\n        expect(result.errors!.length).to.be.greaterThan(0);\n    });\n\n    it('should fail a bad JSON response, even if validateDefaultResponses', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { what: 'bar' },\n            false\n        );\n\n        expect(result.errors).to.exist;\n        expect(result.errors!.length).to.equal(1);\n    });\n\n    it('should validate a default response', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(\n            500,\n            { 'content-type': 'application/json' },\n            { message: 'Oh noes!' },\n            true\n        );\n        expect(result).to.eql({\n            isDefault: true,\n            errors: null,\n        });\n    });\n\n    it('should fail a default response', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(\n            500,\n            { 'content-type': 'application/json' },\n            {},\n            true\n        );\n\n        expect(result).to.eql({\n            isDefault: true,\n            errors: [\n                {\n                    location: {\n                        docPath:\n                            '/paths/~1path/get/responses/default/content/application~1json/schema',\n                        in: 'response',\n                        name: 'body',\n                        path: '',\n                    },\n                    message: \"must have required property 'message'\",\n                    ajvError: {\n                        instancePath: '/value',\n                        keyword: 'required',\n                        message: \"must have required property 'message'\",\n                        params: {\n                            missingProperty: 'message',\n                        },\n                        schemaPath: '#/properties/value/required',\n                    },\n                },\n            ],\n        });\n    });\n\n    it('should pass a result with no message body', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(203, {}, undefined, true);\n        expect(result).to.eql({\n            isDefault: false,\n            errors: null,\n        });\n    });\n\n    it('should fail a result with a message body which was not expecting a message body', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(203, {}, 'hello', true);\n        expect(result.errors).to.exist;\n        expect(result.errors!.length).to.be.greaterThan(0);\n    });\n\n    it('should not fail a default response if validateDefaultResponses is false', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n        const result = responses.validateResponse(\n            500,\n            { 'content-type': 'application/json' },\n            {},\n            false\n        );\n\n        expect(result).to.eql({\n            isDefault: true,\n            errors: null,\n        });\n    });\n\n    it('should validate JSON strings', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const body = '{\"what\": \"bar\"}';\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            body,\n            true\n        );\n\n        expect(result.errors).to.have.length(1);\n    });\n\n    it('should not validate non-JSON strings', async function () {\n        const responses = makeResponses(RESPONSE_WITH_TEXT_PLAIN);\n\n        const body = '{\"what\": \"bar\"}';\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'text/plain' },\n            body,\n            true\n        );\n\n        expect(result).to.eql({\n            isDefault: false,\n            errors: null,\n        });\n    });\n\n    it('should skip validation for buffers, strings, and streams', async function () {\n        const responses = makeResponses(DEFAULT_RESPONSE);\n\n        const cases = [\n            { type: 'Buffer', body: Buffer.from('{\"what\": \"bar\"}') },\n            { type: 'Readable', body: stringToStream('{\"what\": \"bar\"}') },\n        ];\n\n        for (const { type, body } of cases) {\n            const result = responses.validateResponse(\n                200,\n                { 'content-type': 'application/json' },\n                body,\n                true\n            );\n\n            expect(result, `result for ${type}`).to.eql({\n                isDefault: false,\n                errors: null,\n            });\n        }\n    });\n\n    it('should validate a JSON response for a result with multiple content-types', async function () {\n        const responses = makeResponses(RESPONSE_WITH_MULTIPLE_CONTENT_TYPES);\n        const jsonResult = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { foo: 'bar' },\n            true\n        );\n        expect(jsonResult, 'jsonResult').to.eql({\n            errors: null,\n            isDefault: false,\n        });\n\n        const erroredJsonResult = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { foo: 7 },\n            true\n        );\n        expect(erroredJsonResult.errors, 'erroredJsonResult').to.exist;\n        expect(erroredJsonResult.errors!.length, 'erroredJsonResult').to.be.greaterThan(0);\n\n        const xmlResult = responses.validateResponse(\n            200,\n            { 'content-type': 'application/xml' },\n            { baz: 'qux' },\n            true\n        );\n        expect(xmlResult, 'xmlResult').to.eql({\n            errors: null,\n            isDefault: false,\n        });\n\n        // This is highly unlikely - no one is going to ever do this, since\n        // we'd end up writing the XML out as JSON.  Maybe we should\n        // support some kind of \"responseWriter\" plugin to custom convert\n        // JSON objects to XML?\n        const erroredXmlResult = responses.validateResponse(\n            200,\n            { 'content-type': 'application/xml' },\n            { baz: 7 },\n            true\n        );\n        expect(erroredXmlResult.errors, 'erroredXmlResult').to.exist;\n        expect(erroredXmlResult.errors!.length, 'erroredXmlResult').to.be.greaterThan(0);\n    });\n\n    it('should validate a response with no schema', async function () {\n        const responses = makeResponses(RESPONSE_WITH_NO_SCHEMA);\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            { foo: 'bar' },\n            true\n        );\n        expect(result).to.eql({\n            errors: null,\n            isDefault: false,\n        });\n    });\n\n    it('should fail a message with a content-type and no body', async function () {\n        const responses = makeResponses(RESPONSE_WITH_NO_SCHEMA);\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            undefined,\n            true\n        );\n        expect(result.errors![0].message).to.equal('Missing response body for 200.');\n    });\n\n    it('should pass a message with a content-type and a zero-length body', async function () {\n        const responses = makeResponses(RESPONSE_WITH_NO_SCHEMA);\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': 'application/json' },\n            '',\n            true\n        );\n        expect(result).to.eql({\n            errors: null,\n            isDefault: false,\n        });\n    });\n\n    it('should fail a message with a more than one content-type header', async function () {\n        const responses = makeResponses(RESPONSE_WITH_NO_SCHEMA);\n        const result = responses.validateResponse(\n            200,\n            { 'content-type': ['application/json', 'text/plain'] },\n            '',\n            true\n        );\n        expect(result.errors![0].message).to.equal(\n            'Invalid content type for 200 response: application/json,text/plain'\n        );\n    });\n\n    it('should fail for an undefined status code if there is no default', async function () {\n        const responses = makeResponses(RESPONSE_WITH_NO_SCHEMA);\n        const result = responses.validateResponse(\n            404,\n            { 'content-type': 'text/plain' },\n            'Not found',\n            true\n        );\n        expect(result.errors![0].message).to.equal('No response defined for status code 404.');\n    });\n});\n"
  },
  {
    "path": "test/oas3/Schema/validatorsTest.ts",
    "content": "import { expect } from 'chai';\nimport * as oas3 from 'openapi3-ts';\nimport * as validators from '../../../src/oas3/Schema/validators';\nimport { ParameterLocation } from '../../../src/types';\nimport { makeContext, makeOpenApiDoc } from '../../fixtures';\n\nconst openApiDoc: oas3.OpenAPIObject = Object.assign(makeOpenApiDoc(), {\n    components: {\n        schemas: {\n            number: {\n                type: 'number',\n            },\n            float: {\n                type: 'number',\n                format: 'float',\n            },\n            int32: {\n                type: 'integer',\n                format: 'int32',\n            },\n            object: {\n                type: 'object',\n                required: ['a', 'b'],\n                properties: {\n                    a: {\n                        type: 'string',\n                        readOnly: true,\n                    },\n                    b: {\n                        type: 'string',\n                        writeOnly: true,\n                    },\n                },\n            },\n            object2: {\n                type: 'object',\n                properties: {\n                    a: { type: 'number' },\n                },\n            },\n            object3: {\n                type: 'object',\n                required: ['a', 'b'],\n                properties: {\n                    a: {\n                        type: 'string',\n                    },\n                    b: {\n                        type: 'string',\n                    },\n                },\n            },\n            noAdditional: {\n                type: 'object',\n                properties: {\n                    a: { type: 'number' },\n                },\n                additionalProperties: false,\n            },\n            aNullableObject: {\n                type: 'object',\n                required: ['a'],\n                nullable: true,\n                properties: {\n                    a: {\n                        type: 'string',\n                    },\n                },\n            },\n            withDefault: {\n                type: 'object',\n                properties: {\n                    a: { type: 'number', default: 6 },\n                },\n            },\n            numberWithDefault: {\n                type: 'number',\n                default: 7,\n            },\n            numberOrString: {\n                oneOf: [{ type: 'number' }, { type: 'string' }],\n            },\n        },\n    },\n});\n\nconst REQUEST_BODY_LOCATION: ParameterLocation = {\n    in: 'request',\n    name: 'body',\n    docPath: 'paths/~1foo/post/requestBody/content/application~1/json',\n};\n\nconst QUERY_PARAM_LOCATION: ParameterLocation = {\n    in: 'query',\n    name: 'foo',\n    docPath: '/components/parameters/foo',\n};\n\ndescribe('schema validators', function () {\n    it('should validate a schema', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/number');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n        expect(validator(7)).to.eql({ errors: null, value: 7 });\n\n        expect(validator('foo').errors).to.eql([\n            {\n                message: 'must be number',\n                location: {\n                    in: 'query',\n                    name: 'foo',\n                    docPath: '/components/schemas/number',\n                    path: '',\n                },\n                ajvError: {\n                    instancePath: '/value',\n                    keyword: 'type',\n                    message: 'must be number',\n                    params: {\n                        type: 'number',\n                    },\n                    schemaPath: '#/properties/value/type',\n                },\n            },\n        ]);\n    });\n\n    it('should not require properties marked readOnly in a request', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/object');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n        expect(validator({ b: 'hello' }).errors, 'should validate missing \"a\"').to.eql(null);\n        expect(validator({ a: 'hello', b: 'hello' }).errors, 'should allow \"a\"').to.eql(null);\n\n        expect(validator({}).errors, 'should still require \"b\"').to.eql([\n            {\n                message: \"must have required property 'b'\",\n                location: {\n                    in: 'request',\n                    name: 'body',\n                    docPath: '/components/schemas/object',\n                    path: '',\n                },\n                ajvError: {\n                    instancePath: '/value',\n                    keyword: 'required',\n                    message: \"must have required property 'b'\",\n                    params: {\n                        missingProperty: 'b',\n                    },\n                    schemaPath: '#/properties/value/required',\n                },\n            },\n        ]);\n    });\n\n    it('should not require properties marked writeOnly in a response', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/object');\n\n        const validator = validators.generateResponseValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false\n        );\n        expect(validator({ a: 'hello' }).errors, 'should validate missing \"b\"').to.eql(null);\n        expect(validator({ a: 'hello', b: 'hello' }).errors, 'should allow \"b\"').to.eql(null);\n\n        expect(validator({}).errors, 'should still require \"a\"').to.eql([\n            {\n                message: \"must have required property 'a'\",\n                location: {\n                    in: 'request',\n                    name: 'body',\n                    docPath: '/components/schemas/object',\n                    path: '',\n                },\n                ajvError: {\n                    instancePath: '/value',\n                    keyword: 'required',\n                    message: \"must have required property 'a'\",\n                    params: {\n                        missingProperty: 'a',\n                    },\n                    schemaPath: '#/properties/value/required',\n                },\n            },\n        ]);\n    });\n\n    it('should strip additional properties', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/noAdditional', {});\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n        const { errors, value } = validator({ a: 7, b: 'foo' });\n        expect(errors).to.equal(null);\n        expect(value).to.eql({ a: 7 });\n    });\n\n    it('should generate a path for the errored element', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/object2');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n\n        expect(validator({ a: 'hello' }).errors).to.eql([\n            {\n                message: 'must be number',\n                location: {\n                    in: 'request',\n                    name: 'body',\n                    docPath: '/components/schemas/object2',\n                    path: '/a',\n                },\n                ajvError: {\n                    instancePath: '/value/a',\n                    keyword: 'type',\n                    message: 'must be number',\n                    params: {\n                        type: 'number',\n                    },\n                    schemaPath: '#/properties/value/properties/a/type',\n                },\n            },\n        ]);\n    });\n\n    it('should validate an integer with a format', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/int32');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            false,\n            'application/json'\n        );\n\n        // A generally safe value to test\n        expect(validator(7).errors).to.eql(null);\n\n        // Min valid value\n        expect(validator(-1 * Math.pow(2, 31)).errors).to.eql(null);\n\n        // Max valid value\n        expect(validator(Math.pow(2, 31) - 1).errors).to.eql(null);\n\n        // Float values are not allowed by the 'integer' type validator, putting this here as insurance to make\n        // sure it correctly catches decimal values.\n        expect(validator(7.5).errors).to.eql([\n            {\n                message: 'must be integer',\n                location: {\n                    in: 'query',\n                    name: 'foo',\n                    docPath: '/components/schemas/int32',\n                    path: '',\n                },\n                ajvError: {\n                    instancePath: '/value',\n                    keyword: 'type',\n                    message: 'must be integer',\n                    params: {\n                        type: 'integer',\n                    },\n                    schemaPath: '#/properties/value/type',\n                },\n            },\n        ]);\n\n        // The expected failure result for an int32 format failure.\n        const int32FailValue = [\n            {\n                message: 'must match format \"int32\"',\n                location: {\n                    in: 'query',\n                    name: 'foo',\n                    docPath: '/components/schemas/int32',\n                    path: '',\n                },\n                ajvError: {\n                    instancePath: '/value',\n                    keyword: 'format',\n                    message: 'must match format \"int32\"',\n                    params: {\n                        format: 'int32',\n                    },\n                    schemaPath: '#/properties/value/format',\n                },\n            },\n        ];\n        // One less than the minimum value allowed\n        expect(validator(-1 * Math.pow(2, 31) - 1).errors).to.eql(int32FailValue);\n\n        // One more than the maximum value allowed\n        expect(validator(Math.pow(2, 31)).errors).to.eql(int32FailValue);\n    });\n\n    it('should validate a float', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/float');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n        expect(validator(7.5).errors).to.eql(null);\n    });\n\n    it('should error for a missing value if required', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/int32');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            true,\n            'application/x-www-form-urlencoded'\n        );\n\n        expect(validator(7).errors).to.eql(null);\n        expect(validator(undefined).errors).to.eql([\n            {\n                message: 'Missing required query parameter \"foo\"',\n                location: {\n                    in: 'query',\n                    name: 'foo',\n                    docPath: '/components/parameters/foo',\n                    path: '',\n                },\n            },\n        ]);\n    });\n\n    it('should not error for a missing value if not required', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/int32');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n\n        expect(validator(7).errors).to.eql(null);\n        expect(validator(undefined).errors).to.eql(null);\n    });\n\n    it('should not error for a missing object if nullable', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/aNullableObject');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            QUERY_PARAM_LOCATION,\n            false,\n            'application/json'\n        );\n\n        expect(validator(null).errors).to.eql(null);\n        expect(validator(undefined).errors).to.eql(null);\n    });\n\n    it('should not error for a null object in array', function () {\n        const openApiDoc: oas3.OpenAPIObject = Object.assign(makeOpenApiDoc(), {\n            components: {\n                schemas: {\n                    anArray: {\n                        type: 'array',\n                        items: {\n                            $ref: '#/components/schemas/aNullableObject',\n                        },\n                    },\n                    aNullableObject: {\n                        type: 'object',\n                        required: ['a'],\n                        nullable: true,\n                        properties: {\n                            a: {\n                                type: 'string',\n                            },\n                        },\n                    },\n                },\n            },\n        });\n\n        const context = makeContext(openApiDoc, '#/components/schemas/anArray');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/json'\n        );\n\n        expect(validator([{ a: 'foo' }, null]).errors).to.eql(null);\n        expect(validator([{ a: 'foo' }]).errors).to.eql(null);\n    });\n\n    it('should not error for a nullable array', function () {\n        const openApiDoc: oas3.OpenAPIObject = Object.assign(makeOpenApiDoc(), {\n            components: {\n                schemas: {\n                    anArray: {\n                        nullable: true,\n                        type: 'array',\n                        items: {\n                            type: 'string',\n                        },\n                    },\n                    other: {\n                        required: ['arr'],\n                        properties: {\n                            arr: {\n                                $ref: '#/components/schemas/anArray',\n                            },\n                        },\n                    },\n                },\n            },\n        });\n\n        const context = makeContext(openApiDoc, '#/components/schemas/other');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/json'\n        );\n\n        expect(validator({ arr: null }).errors).to.eql(null);\n    });\n\n    it('should fill in default values', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/withDefault');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/json'\n        );\n\n        const obj: any = {};\n        expect(validator(obj).errors).to.eql(null);\n        expect(obj.a).to.equal(6);\n    });\n\n    it('only return the first error if options.allErrors is false', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/object3');\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/json'\n        );\n\n        const obj: any = {};\n        expect(validator(obj)).to.eql({\n            errors: [\n                {\n                    ajvError: {\n                        instancePath: '/value',\n                        keyword: 'required',\n                        message: \"must have required property 'a'\",\n                        params: {\n                            missingProperty: 'a',\n                        },\n                        schemaPath: '#/properties/value/required',\n                    },\n                    location: {\n                        docPath: '/components/schemas/object3',\n                        in: 'request',\n                        name: 'body',\n                        path: '',\n                    },\n                    message: \"must have required property 'a'\",\n                },\n            ],\n            value: {},\n        });\n    });\n\n    it('return multiple errors if options.allErrors is true', function () {\n        const context = makeContext(openApiDoc, '#/components/schemas/object3', {\n            allErrors: true,\n        });\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/json'\n        );\n\n        const obj: any = {};\n        expect(validator(obj)).to.eql({\n            errors: [\n                {\n                    ajvError: {\n                        instancePath: '/value',\n                        keyword: 'required',\n                        message: \"must have required property 'a'\",\n                        params: {\n                            missingProperty: 'a',\n                        },\n                        schemaPath: '#/properties/value/required',\n                    },\n                    location: {\n                        docPath: '/components/schemas/object3',\n                        in: 'request',\n                        name: 'body',\n                        path: '',\n                    },\n                    message: \"must have required property 'a'\",\n                },\n                {\n                    ajvError: {\n                        instancePath: '/value',\n                        keyword: 'required',\n                        message: \"must have required property 'b'\",\n                        params: {\n                            missingProperty: 'b',\n                        },\n                        schemaPath: '#/properties/value/required',\n                    },\n                    location: {\n                        docPath: '/components/schemas/object3',\n                        in: 'request',\n                        name: 'body',\n                        path: '',\n                    },\n                    message: \"must have required property 'b'\",\n                },\n            ],\n            value: {},\n        });\n    });\n\n    describe('type coercion', function () {\n        it('type coerce root values', function () {\n            const context = makeContext(openApiDoc, '#/components/schemas/numberWithDefault');\n\n            const validator = validators.generateRequestValidator(\n                context,\n                REQUEST_BODY_LOCATION,\n                false,\n                'application/x-www-form-urlencoded'\n            );\n\n            const obj: any = '9';\n            expect(validator(obj)).to.eql({\n                errors: null,\n                value: 9,\n            });\n\n            expect(validator(undefined)).to.eql({\n                errors: null,\n                value: 7,\n            });\n        });\n\n        it('not type coerce request values for application/json', function () {\n            const context = makeContext(openApiDoc, '#/components/schemas/numberWithDefault');\n\n            const validator = validators.generateRequestValidator(\n                context,\n                REQUEST_BODY_LOCATION,\n                false,\n                'application/json'\n            );\n\n            const obj: any = '9';\n            expect(validator(obj)).to.eql({\n                errors: [\n                    {\n                        ajvError: {\n                            instancePath: '/value',\n                            keyword: 'type',\n                            message: 'must be number',\n                            params: {\n                                type: 'number',\n                            },\n                            schemaPath: '#/properties/value/type',\n                        },\n                        location: {\n                            docPath: '/components/schemas/numberWithDefault',\n                            in: 'request',\n                            name: 'body',\n                            path: '',\n                        },\n                        message: 'must be number',\n                    },\n                ],\n                value: '9',\n            });\n\n            expect(validator(undefined)).to.eql({\n                errors: null,\n                value: 7,\n            });\n        });\n\n        it('not type coerce values for a response', function () {\n            const context = makeContext(openApiDoc, '#/components/schemas/numberOrString');\n\n            const validator = validators.generateResponseValidator(\n                context,\n                REQUEST_BODY_LOCATION,\n                false\n            );\n\n            expect(validator('9')).to.eql({\n                errors: null,\n                value: '9',\n            });\n\n            expect(validator(9)).to.eql({\n                errors: null,\n                value: 9,\n            });\n        });\n    });\n\n    it('should validate minItems (#125)', function () {\n        const openApiDoc: oas3.OpenAPIObject = Object.assign(makeOpenApiDoc(), {\n            components: {\n                schemas: {\n                    body: {\n                        type: 'object',\n                        required: ['articleIds', 'kind'],\n                        properties: {\n                            articleIds: {\n                                type: 'array',\n                                uniqueItems: true,\n                                minItems: 1,\n                                items: {\n                                    type: 'string',\n                                },\n                            },\n                            details: {\n                                type: 'object',\n                                nullable: true,\n                            },\n                            kind: {\n                                type: 'string',\n                            },\n                        },\n                    },\n                },\n            },\n        });\n\n        const context = makeContext(openApiDoc, '#/components/schemas/body', {});\n\n        const validator = validators.generateRequestValidator(\n            context,\n            REQUEST_BODY_LOCATION,\n            false,\n            'application/x-www-form-urlencoded'\n        );\n\n        expect(\n            validator({\n                articleIds: [],\n                kind: 'test',\n            }).errors?.[0].message,\n            'should be invalid with 0 items'\n        ).to.equal('must NOT have fewer than 1 items');\n\n        expect(\n            validator({\n                articleIds: ['id-0'],\n                kind: 'test',\n            }).errors,\n            'should pass with one item'\n        ).to.equal(null);\n\n        expect(\n            validator({\n                articleIds: ['id-0', 'id-1'],\n                kind: 'test',\n            }).errors,\n            'should pass with two items'\n        ).to.equal(null);\n    });\n});\n"
  },
  {
    "path": "test/oas3/ServersTest.ts",
    "content": "import { expect } from 'chai';\n\nimport Servers from '../../src/oas3/Servers';\n\ndescribe('oas3 Servers', () => {\n    describe('generateServerParser', () => {\n        it('removes server absolute url from path', () => {\n            const oas3Server = {\n                url: '/api',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('host', '/api/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('/api');\n        });\n\n        it('removes server full url from path', () => {\n            const oas3Server = {\n                url: 'https://localhost:3030/api',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('localhost:3030', '/api/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('/api');\n        });\n\n        it('works with no path in absolute url', () => {\n            const oas3Server = {\n                url: '/',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('host', '/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('');\n        });\n\n        it('works with no path in absolute url (and no trailing /)', () => {\n            const oas3Server = {\n                url: '',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('host', '/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('');\n        });\n\n        it('works with no path in full url', () => {\n            const oas3Server = {\n                url: 'https://localhost:3030/',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('localhost:3030', '/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('');\n        });\n\n        it('works with no path in full url (and no trailing /)', () => {\n            const oas3Server = {\n                url: 'https://localhost:3030',\n            };\n            const servers = new Servers([oas3Server]);\n            const parser = servers['_servers'][0];\n            const result = parser('localhost:3030', '/v1/foo')!;\n            expect(result.pathnameRest).to.eql('/v1/foo');\n            expect(result.baseUrl).to.eql('');\n        });\n    });\n});\n"
  },
  {
    "path": "test/oas3/oas3ControllersTest.ts",
    "content": "import ld from 'lodash';\nimport { IncomingHttpHeaders } from 'http';\nimport oas3 from 'openapi3-ts';\nimport { expect } from 'chai';\nimport * as jsonPtr from 'json-ptr';\nimport OpenApi from '../../src/oas3/OpenApi';\nimport { compileOptions } from '../../src/options';\nimport { invokeController } from '../../src/controllers/invoke';\nimport { EXEGESIS_CONTROLLER, EXEGESIS_OPERATION_ID } from '../../src/oas3/extensions';\nimport FakeExegesisContext from '../fixtures/FakeExegesisContext';\n\n// \"Integration tests\" which check to veryify we can match a path and extract\n// various kinds of parameters correctly.\n\nfunction generateOpenApi(): oas3.OpenAPIObject {\n    return {\n        openapi: '3.0.1',\n        info: {\n            title: 'Test API',\n            version: '1.0.0',\n        },\n        paths: {\n            '/path': {\n                get: {\n                    responses: {\n                        default: { description: 'hello' },\n                    },\n                },\n                post: {\n                    responses: {\n                        default: { description: 'hello' },\n                    },\n                    requestBody: {\n                        required: true,\n                        content: {\n                            'application/json': {\n                                schema: { type: 'object' },\n                            },\n                        },\n                    },\n                },\n            },\n        },\n    };\n}\n\nconst controllers = {\n    myController: {\n        otherOp() {\n            return 7;\n        },\n        op() {\n            return this.otherOp();\n        },\n    },\n};\n\nconst options = compileOptions({\n    controllers,\n    allowMissingControllers: true,\n});\n\nasync function findControllerTest(\n    method: string,\n    controllerLocation: string,\n    operationLocation: string,\n    headers?: IncomingHttpHeaders\n) {\n    const context = new FakeExegesisContext();\n    const openApiDoc = generateOpenApi();\n    ld.set(openApiDoc, jsonPtr.JsonPointer.decode(controllerLocation), 'myController');\n    ld.set(openApiDoc, jsonPtr.JsonPointer.decode(operationLocation), 'op');\n\n    const openApi = new OpenApi(openApiDoc, options);\n\n    const resolved = openApi.resolve(\n        method,\n        '/path',\n        headers || (method === 'POST' ? { 'content-type': 'application/json' } : {})\n    );\n\n    expect(\n        {\n            controllerName: resolved!.operation!.exegesisControllerName,\n            operationId: resolved!.operation!.operationId,\n        },\n        `controller: ${controllerLocation}, operation: ${operationLocation}`\n    ).to.eql({\n        controllerName: 'myController',\n        operationId: 'op',\n    });\n    expect(\n        await invokeController(\n            resolved!.operation!.controllerModule!,\n            resolved!.operation!.controller!,\n            context\n        )\n    ).to.equal(7);\n}\n\ndescribe('oas3 integration controller extensions', function () {\n    it('should resolve controller and operationId with body', async function () {\n        const EXEGESIS_CONTROLLER_LOCATIONS = [\n            '/x-exegesis-controller',\n            '/paths/x-exegesis-controller',\n            '/paths/~1path/x-exegesis-controller',\n            '/paths/~1path/post/x-exegesis-controller',\n            '/paths/~1path/post/requestBody/content/application~1json/x-exegesis-controller',\n        ];\n\n        const EXEGESIS_OPERATION_LOCATIONS = [\n            '/paths/~1path/post/x-exegesis-operationId',\n            '/paths/~1path/post/operationId',\n            '/paths/~1path/post/requestBody/content/application~1json/x-exegesis-operationId',\n        ];\n\n        for (const controllerLocation of EXEGESIS_CONTROLLER_LOCATIONS) {\n            for (const operationLocation of EXEGESIS_OPERATION_LOCATIONS) {\n                await findControllerTest('POST', controllerLocation, operationLocation);\n            }\n        }\n    });\n\n    it('should resolve controller and operationId without body', async function () {\n        const EXEGESIS_CONTROLLER_LOCATIONS = [\n            '/x-exegesis-controller',\n            '/paths/x-exegesis-controller',\n            '/paths/~1path/x-exegesis-controller',\n            '/paths/~1path/get/x-exegesis-controller',\n        ];\n\n        const EXEGESIS_OPERATION_LOCATIONS = [\n            '/paths/~1path/get/x-exegesis-operationId',\n            '/paths/~1path/get/operationId',\n        ];\n\n        for (const controllerLocation of EXEGESIS_CONTROLLER_LOCATIONS) {\n            for (const operationLocation of EXEGESIS_OPERATION_LOCATIONS) {\n                await findControllerTest('get', controllerLocation, operationLocation);\n            }\n        }\n    });\n\n    it('should resolve controller and operationId without body and ignore the content-type header', async function () {\n        const EXEGESIS_CONTROLLER_LOCATIONS = [\n            '/x-exegesis-controller',\n            '/paths/x-exegesis-controller',\n            '/paths/~1path/x-exegesis-controller',\n            '/paths/~1path/get/x-exegesis-controller',\n        ];\n\n        const EXEGESIS_OPERATION_LOCATIONS = [\n            '/paths/~1path/get/x-exegesis-operationId',\n            '/paths/~1path/get/operationId',\n        ];\n\n        for (const controllerLocation of EXEGESIS_CONTROLLER_LOCATIONS) {\n            for (const operationLocation of EXEGESIS_OPERATION_LOCATIONS) {\n                await findControllerTest('get', controllerLocation, operationLocation, {\n                    'content-type': 'application/json',\n                });\n            }\n        }\n    });\n\n    it('should throw an error if there is no content-type header in a POST request', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n        expect(() => openApi.resolve('post', '/path', {})).to.throw(\n            'Missing content-type. Expected one of: application/json'\n        );\n    });\n\n    it('should throw an error in a post request with body but invalid content-type', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n        expect(() =>\n            openApi.resolve('post', '/path', {\n                'content-type': 'application/jsontypo',\n                'content-length': '442',\n            })\n        ).to.throw('Invalid content-type: application/jsontypo');\n    });\n\n    it('should throw an error in a post request without body but invalid content-type', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n        expect(() =>\n            openApi.resolve('post', '/path', {\n                'content-type': 'application/jsontypo',\n            })\n        ).to.throw('Invalid content-type: application/jsontypo');\n    });\n\n    it('should not an error in a get request with invalid content-type', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n        expect(\n            openApi.resolve('get', '/path', {\n                'content-type': 'application/jsontypo',\n            })\n        ).to.be.ok;\n    });\n\n    it('should throw an error in a post request without (optional) body but invalid content-type', function () {\n        const openApiDoc = generateOpenApi();\n        openApiDoc.paths['/path'].post.requestBody.required = false;\n        const openApi = new OpenApi(openApiDoc, options);\n        expect(() =>\n            openApi.resolve('post', '/path', {\n                'content-type': 'application/jsontypo',\n            })\n        ).to.throw('Invalid content-type: application/jsontypo');\n    });\n\n    it('should resolve even if there is no controller', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n\n        const resolved = openApi.resolve('POST', '/path', { 'content-type': 'application/json' });\n\n        expect({\n            controllerName: resolved!.operation!.exegesisControllerName,\n            operationId: resolved!.operation!.operationId,\n            controller: resolved!.operation!.controller,\n        }).to.eql({\n            controllerName: undefined,\n            operationId: undefined,\n            controller: undefined,\n        });\n    });\n\n    it('should throw an error if there is a controller defined, but it does not exist', function () {\n        const openApiDoc = generateOpenApi();\n        openApiDoc.paths['/path'].get[EXEGESIS_CONTROLLER] = 'idonotexist';\n        openApiDoc.paths['/path'].get.operationId = 'idonotexist';\n\n        expect(() => new OpenApi(openApiDoc, options)).to.throw(\n            'Could not find controller idonotexist defined in /paths/~1path/get'\n        );\n    });\n\n    it('should throw an error if there is an operationId defined, but it does not exist', function () {\n        const openApiDoc = generateOpenApi();\n        openApiDoc.paths['/path'].get[EXEGESIS_CONTROLLER] = 'myController';\n        openApiDoc.paths['/path'].get.operationId = 'idonotexist';\n\n        expect(() => new OpenApi(openApiDoc, options)).to.throw(\n            'Could not find operation myController#idonotexist defined in /paths/~1path/get'\n        );\n    });\n\n    it('should return undefined if there is no path to resolve', function () {\n        const openApiDoc = generateOpenApi();\n        const openApi = new OpenApi(openApiDoc, options);\n\n        const resolved = openApi.resolve('POST', 'badpath', { 'content-type': 'application/json' });\n        expect(resolved).to.be.undefined;\n    });\n\n    it('should throw an error if there is no openapi version field', function () {\n        const openApiDoc = generateOpenApi() as any;\n        delete openApiDoc['openapi'];\n        expect(() => new OpenApi(openApiDoc, options)).to.throw(\n            \"OpenAPI definition is missing 'openapi' field\"\n        );\n    });\n\n    it('should throw an error if the openapi version is invalid', function () {\n        const openApiDoc = generateOpenApi();\n        openApiDoc['openapi'] = '2.0.0';\n        expect(() => new OpenApi(openApiDoc, options)).to.throw(\n            'OpenAPI version 2.0.0 not supported'\n        );\n    });\n\n    it('should utilize the servers field', async function () {\n        const openApiDoc = generateOpenApi();\n        openApiDoc.servers = [{ url: '/basepath' }];\n        const openApi = new OpenApi(openApiDoc, options);\n        const resolved = openApi.resolve('get', '/basepath/path', {});\n        expect(resolved).to.be.ok;\n        const resolved2 = openApi.resolve('get', '/path', {});\n        expect(resolved2).to.be.not.ok;\n    });\n\n    describe('allowMissingControllers: false', function () {\n        const options2 = compileOptions({\n            controllers,\n            allowMissingControllers: false,\n        });\n\n        it('should error if an operation has no controller defined', function () {\n            const openApiDoc = generateOpenApi();\n            expect(() => new OpenApi(openApiDoc, options2)).to.throw(\n                `Missing ${EXEGESIS_CONTROLLER} for /paths/~1path/get`\n            );\n        });\n\n        it('should error if an operation has no operationId', function () {\n            const openApiDoc = generateOpenApi();\n            openApiDoc.paths['/path'].get[EXEGESIS_CONTROLLER] = 'myController';\n            expect(() => new OpenApi(openApiDoc, options2)).to.throw(\n                `Missing operationId or ${EXEGESIS_OPERATION_ID} for /paths/~1path/get`\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "test/oas3/oas3ParametersTest.ts",
    "content": "import oas3 from 'openapi3-ts';\nimport { expect } from 'chai';\nimport { defaultCompiledOptions } from '../fixtures';\nimport OpenApi from '../../src/oas3/OpenApi';\n\nconst pathStyles = ['simple', 'matrix' /*TODO: , 'label' */];\nconst queryStyles = ['form', 'pipeDelimited', 'spaceDelimited', 'deepObject'];\n\n// \"Integration tests\" which check to veryify we can match a path and extract\n// various kinds of parameters correctly.\n\nfunction generateOpenApi(paths: oas3.PathObject): OpenApi {\n    return new OpenApi(\n        {\n            openapi: '3.0.1',\n            info: {\n                title: 'Test API',\n                version: '1.0.0',\n            },\n            paths: paths,\n        },\n        defaultCompiledOptions\n    );\n}\n\nconst types: { [key: string]: any } = {\n    string: {\n        schema: { type: 'string' },\n        samples: [\n            {\n                value: 'foo',\n                simple: 'foo',\n                form: '?var=foo',\n                matrix: ';var=foo',\n                label: '.foo',\n            },\n            {\n                value: '',\n                simple: '',\n                form: '?var=',\n                matrix: ';var',\n                label: '.',\n            },\n        ],\n    },\n    array: {\n        schema: { type: 'array' },\n        samples: [\n            {\n                value: ['foo', 'bar,baz'],\n                simple: 'foo,bar%2Cbaz',\n                form: '?var=foo,bar%2Cbaz',\n                pipeDelimited: '?var=foo|bar%2Cbaz',\n                spaceDelimited: '?var=foo bar%2Cbaz',\n                matrix: ';var=foo,bar%2Cbaz',\n                label: '.foo.bar%2Cbaz',\n            },\n            {\n                value: ['foo'],\n                simple: 'foo',\n                form: '?var=foo',\n                pipeDelimited: '?var=foo',\n                spaceDelimited: '?var=foo',\n                matrix: ';var=foo',\n                label: '.foo',\n            },\n        ],\n    },\n    'exploded-array': {\n        schema: { type: 'array' },\n        explode: true,\n        samples: [\n            {\n                value: ['foo', 'bar,baz'],\n                simple: 'foo,bar%2Cbaz',\n                form: '?var=foo&var=bar%2Cbaz',\n                pipeDelimited: '?var=foo|bar%2Cbaz',\n                spaceDelimited: '?var=foo bar%2Cbaz',\n                matrix: ';var=foo;var=bar%2Cbaz',\n                label: '.foo.bar%2Cbaz',\n            },\n            {\n                value: ['foo'],\n                simple: 'foo',\n                form: '?var=foo',\n                matrix: ';var=foo',\n                label: '.foo',\n            },\n        ],\n    },\n    object: {\n        schema: { type: 'object' },\n        samples: [\n            {\n                value: { semi: ';', dot: '.', comma: ',' },\n                simple: 'semi,%3B,dot,.,comma,%2C',\n                form: '?var=semi,%3B,dot,.,comma,%2C',\n                matrix: ';var=semi,%3B,dot,.,comma,%2C',\n                label: '.semi,%3B,dot,.,comma,%2C',\n                deepObject: '?var[semi]=%3B&var[dot]=.&var[comma]=%2C',\n            },\n        ],\n    },\n    explodedObject: {\n        schema: { type: 'object' },\n        explode: true,\n        samples: [\n            {\n                value: { semi: ';', dot: '.', comma: ',' },\n                simple: 'semi=%3B,dot=.,comma=%2C',\n                form: '?semi=%3B&dot=.&comma=%2C',\n                matrix: ';semi=%3B;dot=.;comma=%2C',\n                // label: '.semi=%3B.dot=..comma=%2C', // This makes no sense.  How would you ever parse this?  O_o\n                deepObject: '?var[semi]=%3B&var[dot]=.&var[comma]=%2C',\n            },\n        ],\n    },\n    mixed: {\n        schema: {\n            anyOf: [{ type: 'string' }, { type: 'array' }],\n        },\n        samples: [\n            {\n                value: 'foo',\n                simple: 'foo',\n                form: '?var=foo',\n            },\n            {\n                value: ['foo', 'bar'],\n                simple: 'foo,bar',\n                form: '?var=foo,bar',\n                pipeDelimited: '?var=foo|bar',\n                spaceDelimited: '?var=foo bar',\n            },\n        ],\n    },\n};\n\ndescribe('oas3 integration parameter parsing', function () {\n    describe('path parameters', function () {\n        for (const typeName of Object.keys(types)) {\n            const typeDef = types[typeName];\n\n            for (const pathStyle of pathStyles) {\n                for (const sample of typeDef.samples) {\n                    if (!(pathStyle in sample)) {\n                        continue;\n                    }\n                    it(`should correctly parse ${typeName} with ${pathStyle} style: ${sample[pathStyle]}`, function () {\n                        const openApi = generateOpenApi({\n                            [`/path-${pathStyle}-${typeName}/{var}`]: {\n                                parameters: [\n                                    {\n                                        name: 'var',\n                                        in: 'path',\n                                        required: true,\n                                        style: pathStyle,\n                                        schema: typeDef.schema,\n                                        explode: typeDef.explode || false,\n                                    },\n                                ],\n                                get: {\n                                    responses: { default: { description: '' } },\n                                },\n                            },\n                        });\n\n                        const result = openApi.resolve(\n                            'GET',\n                            `/path-${pathStyle}-${typeName}/${sample[pathStyle]}`,\n                            {}\n                        );\n\n                        expect(result!.operation!.parseParameters!().path.var).to.eql(sample.value);\n                    });\n                }\n            }\n        }\n    });\n\n    describe('header parameters', function () {\n        for (const typeName of Object.keys(types)) {\n            const typeDef = types[typeName];\n\n            const generateHeaderDoc = () => {\n                const openApi = generateOpenApi({\n                    [`/header-${typeName}`]: {\n                        parameters: [\n                            {\n                                name: 'x-custom-header',\n                                in: 'header',\n                                required: false,\n                                style: 'simple',\n                                schema: typeDef.schema,\n                                explode: typeDef.explode || false,\n                            },\n                        ],\n                        get: {\n                            responses: { default: { description: '' } },\n                        },\n                    },\n                });\n                return openApi;\n            };\n\n            for (const sample of typeDef.samples) {\n                it(`should correctly parse ${typeName} with simple style: ${sample.simple}`, function () {\n                    const openApi = generateHeaderDoc();\n                    const result = openApi.resolve('GET', `/header-${typeName}`, {\n                        'x-custom-header': sample.simple,\n                    });\n\n                    expect(result!.operation!.parseParameters!().header['x-custom-header']).to.eql(\n                        sample.value\n                    );\n                });\n            }\n\n            it(`should correctly parse ${typeName} with simple style: undefined`, function () {\n                const openApi = generateHeaderDoc();\n                const result = openApi.resolve('GET', `/header-${typeName}`, {});\n\n                expect(result!.operation!.parseParameters!().header['x-custom-header']).to.eql(\n                    undefined\n                );\n            });\n        }\n    });\n\n    describe('query parameters', function () {\n        for (const typeName of Object.keys(types)) {\n            const typeDef = types[typeName];\n\n            for (const style of queryStyles) {\n                const explodeStr = typeDef.explode ? 'explode' : '';\n\n                const generateQueryOpenApi = () => {\n                    const openApi = generateOpenApi({\n                        [`/query-${typeName}-${explodeStr}`]: {\n                            parameters: [\n                                {\n                                    name: 'var',\n                                    in: 'query',\n                                    required: false,\n                                    style,\n                                    schema: typeDef.schema,\n                                    explode: typeDef.explode || false,\n                                },\n                            ],\n                            get: {\n                                responses: { default: { description: '' } },\n                            },\n                        },\n                    });\n                    return openApi;\n                };\n\n                for (const sample of typeDef.samples) {\n                    if (!(style in sample)) {\n                        continue;\n                    }\n                    it(`should correctly parse ${typeName} with ${style} style: ${sample.simple}`, function () {\n                        const openApi = generateQueryOpenApi();\n                        const result = openApi.resolve(\n                            'GET',\n                            `/query-${typeName}-${explodeStr}${sample[style]}`,\n                            {}\n                        );\n\n                        expect(result, 'matched a route').to.exist;\n                        expect(result!.operation!.parseParameters!().query.var).to.eql(\n                            sample.value\n                        );\n                    });\n                }\n\n                it(`should correctly parse ${typeName} with ${style} style: undefined`, function () {\n                    const openApi = generateQueryOpenApi();\n                    const result = openApi.resolve('GET', `/query-${typeName}-${explodeStr}`, {});\n\n                    const expected =\n                        typeName === 'explodedObject' && style === 'form' ? {} : undefined;\n                    expect(result!.operation!.parseParameters!().query.var).to.eql(expected);\n                });\n\n                // TODO: deepObject, pipe and space delimited.\n            }\n        }\n\n        it(`should correctly parse deepObject style`, function () {\n            const openApi = generateOpenApi({\n                ['/query']: {\n                    parameters: [\n                        {\n                            name: 'var',\n                            in: 'query',\n                            required: false,\n                            style: 'deepObject',\n                            schema: { type: 'object' },\n                        },\n                    ],\n                    get: {\n                        responses: { default: { description: '' } },\n                    },\n                },\n            });\n\n            let result = openApi.resolve('GET', `/query?var[a]=b&var[c]=d`, {});\n            expect(result!.operation!.parseParameters!().query.var).to.eql({ a: 'b', c: 'd' });\n\n            result = openApi.resolve('GET', `/query`, {});\n            expect(result!.operation!.parseParameters!().query.var).to.eql(undefined);\n        });\n\n        for (const style of [\n            { style: 'pipeDelimited', delimiter: '|' },\n            { style: 'spaceDelimited', delimiter: ' ' },\n        ]) {\n            it(`should correctly parse ${style.style} style`, function () {\n                const openApi = generateOpenApi({\n                    ['/query']: {\n                        parameters: [\n                            {\n                                name: 'var',\n                                in: 'query',\n                                required: false,\n                                style: style.style,\n                                schema: { type: 'array' },\n                            },\n                        ],\n                        get: {\n                            responses: { default: { description: '' } },\n                        },\n                    },\n                });\n\n                let result = openApi.resolve(\n                    'GET',\n                    `/query?var=a${style.delimiter}b${style.delimiter}c`,\n                    {}\n                );\n                expect(result!.operation!.parseParameters!().query.var).to.eql(['a', 'b', 'c']);\n\n                result = openApi.resolve('GET', `/query?var=a`, {});\n                expect(result!.operation!.parseParameters!().query.var).to.eql(['a']);\n\n                result = openApi.resolve('GET', `/query`, {});\n                expect(result!.operation!.parseParameters!().query.var).to.eql(undefined);\n            });\n        }\n    });\n});\n"
  },
  {
    "path": "test/oas3/parameterParsers/delimitedParserTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport {\n    pipeDelimitedParser,\n    spaceDelimitedParser,\n} from '../../../src/oas3/parameterParsers/delimitedParser';\nimport { ParameterLocation } from '../../../src/types';\n\ndescribe('oas3 parameter parsers - delimited parser', function () {\n    const parameterLocation: ParameterLocation = {\n        in: 'query',\n        name: 'myParam',\n        docPath: '/paths/~1foo/parameters/0',\n    };\n\n    it('should correctly parse a pipe delimited string', function () {\n        const result = pipeDelimitedParser(parameterLocation, { myParam: 'foo%7Cbar' });\n        expect(result).to.eql(['foo', 'bar']);\n    });\n\n    it('should correctly parse a space delimited string', function () {\n        const result = spaceDelimitedParser(parameterLocation, { myParam: 'foo%20bar' });\n        expect(result).to.eql(['foo', 'bar']);\n    });\n\n    it('decode pct-encoded characters', function () {\n        const result = pipeDelimitedParser(parameterLocation, { myParam: 'foo%7Cbar%2Cbaz' });\n        expect(result).to.eql(['foo', 'bar,baz']);\n    });\n\n    it('should correctly parse a pipe delimited string with one value', function () {\n        const result = pipeDelimitedParser(parameterLocation, { myParam: 'foo' });\n        expect(result).to.eql(['foo']);\n    });\n\n    it('should do something sensible for a pipe delimited parameter which shows up more than once', function () {\n        const result = pipeDelimitedParser(parameterLocation, { myParam: ['foo', 'bar%7Cbaz'] });\n        expect(result).to.eql(['foo', 'bar|baz']);\n    });\n});\n"
  },
  {
    "path": "test/oas3/parameterParsers/indexTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\nimport { ParameterLocation } from '../../../src/types';\nimport * as parameterParsers from '../../../src/oas3/parameterParsers';\n\ndescribe('oas3 parameter parsers', function () {\n    const queryParameterLocation: ParameterLocation = {\n        in: 'query',\n        name: 'myParam',\n        docPath: '/paths/~1foo/parameters/0',\n    };\n\n    it('should generate a pipe-delimited parser', function () {\n        const parser = parameterParsers.generateParser({\n            style: 'pipeDelimited',\n            explode: false,\n            schema: { type: 'array' },\n        });\n\n        const result = parameterParsers.parseQueryParameters(\n            [{ location: queryParameterLocation, parser }],\n            'myParam=foo%7Cbar'\n        );\n\n        expect(result).to.eql({ myParam: ['foo', 'bar'] });\n    });\n\n    it('should fill in default value if not provided', function () {\n        const parser = parameterParsers.generateParser({\n            style: 'simple',\n            explode: false,\n            schema: {\n                type: 'number',\n                default: 6,\n            },\n        });\n\n        const specified = parameterParsers.parseQueryParameters(\n            [{ location: queryParameterLocation, parser }],\n            'myParam=9'\n        );\n        expect(specified, 'specified').to.eql({ myParam: '9' });\n\n        const unspecified = parameterParsers.parseQueryParameters(\n            [{ location: queryParameterLocation, parser }],\n            ''\n        );\n        expect(unspecified, 'unspecified').to.eql({ myParam: 6 });\n    });\n\n    it('should fill in falsey default value if not provided', function () {\n        const parser = parameterParsers.generateParser({\n            style: 'simple',\n            explode: false,\n            schema: {\n                type: 'number',\n                default: 0,\n            },\n        });\n\n        const unspecified = parameterParsers.parseQueryParameters(\n            [{ location: queryParameterLocation, parser }],\n            ''\n        );\n        expect(unspecified).to.eql({ myParam: 0 });\n    });\n});\n"
  },
  {
    "path": "test/oas3/samplesTest.ts",
    "content": "import * as path from 'path';\nimport { defaultCompiledOptions } from '../fixtures';\nimport { compileRunner, compileApiInterface } from '../../src';\n\ndescribe('samples', function () {\n    it('should generate an exegesis runner for petstore without crashing', async function () {\n        await compileRunner(\n            path.resolve(__dirname, '../samples/petstore.yaml'),\n            defaultCompiledOptions\n        );\n    });\n\n    it('should compile the API interface for petstore', async function () {\n        await compileApiInterface(path.resolve(__dirname, '../samples/petstore.yaml'), {});\n    });\n});\n"
  },
  {
    "path": "test/samples/petstore.yaml",
    "content": "# From https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml\nopenapi: '3.0.0'\ninfo:\n  version: 1.0.0\n  title: Swagger Petstore\n  license:\n    name: MIT\nservers:\n  - url: http://petstore.swagger.io/v1\npaths:\n  /pets:\n    get:\n      summary: List all pets\n      operationId: listPets\n      tags:\n        - pets\n      parameters:\n        - name: limit\n          in: query\n          description: How many items to return at one time (max 100)\n          required: false\n          schema:\n            type: integer\n            format: int32\n      responses:\n        '200':\n          description: An paged array of pets\n          headers:\n            x-next:\n              description: A link to the next page of responses\n              schema:\n                type: string\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Pets'\n        default:\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Error'\n    post:\n      summary: Create a pet\n      operationId: createPets\n      tags:\n        - pets\n      responses:\n        '201':\n          description: Null response\n        default:\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Error'\n  /pets/{petId}:\n    get:\n      summary: Info for a specific pet\n      operationId: showPetById\n      tags:\n        - pets\n      parameters:\n        - name: petId\n          in: path\n          required: true\n          description: The id of the pet to retrieve\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Expected response to a valid request\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Pets'\n        default:\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/Error'\ncomponents:\n  schemas:\n    Pet:\n      required:\n        - id\n        - name\n      properties:\n        id:\n          type: integer\n          format: int64\n        name:\n          type: string\n        tag:\n          type: string\n    Pets:\n      type: array\n      items:\n        $ref: '#/components/schemas/Pet'\n    Error:\n      required:\n        - code\n        - message\n      properties:\n        code:\n          type: integer\n          format: int32\n        message:\n          type: string\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n    \"extends\": \"../tsconfig.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"typeRoots\": [\n            \"../node_modules/@types\",\n            \"../@types\"\n        ]\n    },\n    \"include\": [\n        \"./**/*\"\n    ],\n  }"
  },
  {
    "path": "test/utils/json-schema-infer-typesTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport inferTypes from '../../src/utils/json-schema-infer-types';\nimport { JSONSchema4 } from 'json-schema';\n\ndescribe('json-schema-infer-types', function () {\n    it('should get the type from a simple object', function () {\n        expect(inferTypes({ type: 'object' })).to.eql(['object']);\n        expect(inferTypes({ type: 'integer' })).to.eql(['integer']);\n    });\n\n    it('number includes integer', function () {\n        expect(new Set(inferTypes({ type: 'number' }))).to.have.all.keys(['number', 'integer']);\n    });\n\n    it('should get the type from a schema with multiple types', function () {\n        const schema: JSONSchema4 = {\n            type: ['object', 'integer'],\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys(['object', 'integer']);\n    });\n\n    it('should not infer anything from minProperties', function () {\n        // Since minProperties only applies if the value being validated is an\n        // object, then the presence of a 'minProperties' key doesn't imply\n        // the the value has to be an object.\n        const schema = {\n            minProperties: 1,\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys([\n            'null',\n            'boolean',\n            'object',\n            'array',\n            'number',\n            'string',\n            'integer',\n        ]);\n    });\n\n    it('should get the type from a schema with oneOf', function () {\n        const schema: JSONSchema4 = {\n            oneOf: [{ type: 'object' }, { type: 'integer' }],\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys(['object', 'integer']);\n    });\n\n    it('should get the type from a schema with anyOf', function () {\n        const schema: JSONSchema4 = {\n            anyOf: [{ type: 'object' }, { type: 'integer' }],\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys(['object', 'integer']);\n    });\n\n    it('should get the type from a schema with anyOf and oneOf', function () {\n        const schema: JSONSchema4 = {\n            anyOf: [{ type: 'object' }, { type: 'integer' }],\n            oneOf: [{ type: 'object' }, { type: 'array' }],\n        };\n\n        expect(inferTypes(schema)).to.eql(['object']);\n    });\n\n    it('should get the type from an object with allOf', function () {\n        const schema: JSONSchema4 = {\n            allOf: [{ type: 'object' }, { type: 'object' }],\n        };\n\n        expect(inferTypes(schema)).to.eql(['object']);\n    });\n\n    it('should get the type from an object with enum', function () {\n        const schema: JSONSchema4 = {\n            enum: ['foo', 'bar', 7],\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys(['string', 'integer']);\n    });\n\n    it('should get the type from an object with const', function () {\n        expect(inferTypes({ const: 'foo' })).to.eql(['string']);\n        expect(inferTypes({ const: { foo: 'bar' } })).to.eql(['object']);\n        expect(inferTypes({ const: ['foo'] })).to.eql(['array']);\n        expect(inferTypes({ const: 7 })).to.eql(['integer']);\n        expect(inferTypes({ const: 7.2 })).to.eql(['number', 'integer']);\n        expect(inferTypes({ const: null })).to.eql(['null']);\n        expect(inferTypes({ const: true })).to.eql(['boolean']);\n        expect(inferTypes({ const: false })).to.eql(['boolean']);\n    });\n\n    it('should follow $refs', function () {\n        const schema: JSONSchema4 = {\n            allOf: [{ $ref: '#/definitions/a' }, { $ref: '#/definitions/b' }],\n            definitions: {\n                a: { type: 'object' },\n                b: { type: 'object' },\n            },\n        };\n\n        expect(inferTypes(schema)).to.eql(['object']);\n    });\n\n    it('should get the type from an impossible object', function () {\n        const schema: JSONSchema4 = {\n            allOf: [{ type: 'object' }, { type: 'integer' }],\n        };\n\n        expect(inferTypes(schema)).to.eql([]);\n    });\n\n    it('should handle crazy complicated cases', function () {\n        const schema: JSONSchema4 = {\n            allOf: [{ type: ['object', 'integer'] }, { type: ['object', 'number', 'integer'] }],\n            oneOf: [{ type: 'integer' }, { type: 'array' }, { type: ['null', 'object'] }],\n            type: ['object', 'integer'],\n        };\n\n        expect(new Set(inferTypes(schema))).to.have.all.keys(['object', 'integer']);\n    });\n});\n"
  },
  {
    "path": "test/utils/json-schema-resolve-refTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport { resolveRef } from '../../src/utils/json-schema-resolve-ref';\n\ndescribe('resolveRef', function () {\n    const doc = {\n        a: 7,\n        b: { $ref: '#/a' },\n        c: { $ref: '#/b' },\n        file: { $ref: 'file://a' },\n        array: [6, { $ref: '#/a' }],\n        struct: { x: 7 },\n        struct2: { $ref: '#/struct' },\n        struct3: { $ref: '#/struct2/x' },\n    };\n\n    it('should resolve a JSON Reference', function () {\n        expect(resolveRef(doc, '#/a')).to.equal(7);\n    });\n\n    it('should resolve a JSON Reference to an array', function () {\n        expect(resolveRef(doc, '#/array/0')).to.equal(6);\n    });\n\n    it('should resolve root ref', function () {\n        expect(resolveRef(doc, '')).to.eql(doc);\n    });\n\n    it('should resolve a JSON Reference to a JSON Reference', function () {\n        expect(resolveRef(doc, '#/c')).to.equal(7);\n        expect(resolveRef(doc, '#/c')).to.equal(7);\n    });\n\n    it('should resolve nested references', function () {\n        expect(resolveRef(doc, '#/struct3')).to.equal(7);\n    });\n\n    it('should not resolve external references', function () {\n        expect(() => resolveRef(doc, '#/file')).to.throw('Cannot resolve non-local ref');\n    });\n\n    it('should resolve a ref with /s', function () {\n        expect(resolveRef({ 'a/b': 7 }, '#/a~1b')).to.eql(7);\n    });\n\n    it('should resolve a ref with URI encoded components', function () {\n        expect(resolveRef({ 'a b': 7 }, '#/a%20b')).to.eql(7);\n    });\n});\n"
  },
  {
    "path": "test/utils/jsonPathsTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\nimport * as jsonPaths from '../../src/utils/jsonPaths';\n\ndescribe('jsonPaths utils', function () {\n    it('should find prefix', function () {\n        expect(jsonPaths.jsonPointerStartsWith('/foo/bar', '/foo')).to.equal(true);\n        expect(jsonPaths.jsonPointerStartsWith('/foo/bar', '/baz')).to.equal(false);\n    });\n\n    it('should find prefix, even if prefix is a URI fragment', function () {\n        expect(jsonPaths.jsonPointerStartsWith('/foo/bar', '#/foo')).to.equal(true);\n        expect(jsonPaths.jsonPointerStartsWith('/foo/bar', '#/baz')).to.equal(false);\n    });\n\n    it('should strip prefix', function () {\n        expect(jsonPaths.jsonPointerStripPrefix('/foo/bar', '/foo')).to.equal('/bar');\n    });\n\n    it('should strip prefix. but preserve URI fragment format', function () {\n        expect(jsonPaths.jsonPointerStripPrefix('#/foo/bar', '/foo')).to.equal('#/bar');\n    });\n\n    it('should convert a JSON pointer in string representation to URI fragment representation', function () {\n        expect(jsonPaths.toUriFragment('/foo/bar')).to.equal('#/foo/bar');\n    });\n\n    it('should convert a JSON pointer in URI fragment representation to URI fragment representation', function () {\n        expect(jsonPaths.toUriFragment('#/foo/bar')).to.equal('#/foo/bar');\n    });\n});\n"
  },
  {
    "path": "test/utils/jsonSchemaTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport * as jsonSchema from '../../src/utils/jsonSchema';\n\ndescribe('jsonSchema utils', function () {\n    describe('extractSchema', function () {\n        it('should extract a schema from a parent document and resolve refs', function () {\n            const doc = {\n                myDoc: {\n                    someStuff: {\n                        schema: {\n                            type: 'object',\n                            properties: {\n                                users: { type: 'array', items: { $ref: '#/defs/User' } },\n                                menu: { $ref: '#/defs/Menu' },\n                            },\n                        },\n                    },\n                },\n                defs: {\n                    name: { type: 'string' },\n                    User: {\n                        type: 'object',\n                        properties: {\n                            name: { $ref: '#/defs/name' },\n                        },\n                    },\n                    Menu: {\n                        type: 'object',\n                        properties: {\n                            items: { type: 'array', items: { $ref: '#/defs/MenuItem' } },\n                        },\n                    },\n                    MenuItem: {\n                        oneOf: [\n                            // Circular ref\n                            { $ref: '#/defs/Menu' },\n                            { type: 'string' },\n                        ],\n                    },\n                },\n            };\n\n            const result = jsonSchema.extractSchema(doc, '#/myDoc/someStuff/schema');\n            expect(result).to.eql({\n                type: 'object',\n                properties: {\n                    users: { type: 'array', items: { $ref: '#/definitions/User' } },\n                    menu: {\n                        $ref: '#/definitions/Menu',\n                    },\n                },\n                definitions: {\n                    name: { type: 'string' },\n                    User: {\n                        type: 'object',\n                        properties: {\n                            name: { $ref: '#/definitions/name' },\n                        },\n                    },\n                    Menu: {\n                        type: 'object',\n                        properties: {\n                            items: { type: 'array', items: { $ref: '#/definitions/MenuItem' } },\n                        },\n                    },\n                    MenuItem: {\n                        oneOf: [\n                            // Circular ref\n                            { $ref: '#/definitions/Menu' },\n                            { type: 'string' },\n                        ],\n                    },\n                },\n            });\n        });\n\n        it('should extract a schema when one scheme is a prefix of the other', function () {\n            const doc = {\n                components: {\n                    schemas: {\n                        A: {\n                            description: 'thing',\n                            oneOf: [\n                                {\n                                    $ref: '#/components/schemas/AA',\n                                },\n                            ],\n                        },\n                        AA: {\n                            type: 'object',\n                        },\n                    },\n                },\n            };\n\n            const result = jsonSchema.extractSchema(doc, '#/components/schemas/A');\n            expect(result).to.eql({\n                description: 'thing',\n                oneOf: [\n                    {\n                        $ref: '#/definitions/AA',\n                    },\n                ],\n                definitions: {\n                    AA: { type: 'object' },\n                },\n            });\n        });\n\n        it('should correctly handle nested refs (#134)', function () {\n            const doc = {\n                paths: {\n                    '/test': {\n                        get: {\n                            responses: {\n                                '200': {\n                                    description: 'test.',\n                                    content: {\n                                        'application/json': {\n                                            schema: {\n                                                type: 'object',\n                                                properties: {\n                                                    test: {\n                                                        $ref:\n                                                            '#/definitions/LinkObject/properties/test/allOf/0',\n                                                    },\n                                                },\n                                            },\n                                        },\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n                definitions: {\n                    LinkObject: {\n                        type: 'object',\n                        properties: {\n                            test: {\n                                allOf: [\n                                    {\n                                        type: 'object',\n                                        properties: {\n                                            next: {\n                                                anyOf: [\n                                                    {\n                                                        $ref:\n                                                            '#/definitions/LinkObject/properties/test/allOf/0/additionalProperties/anyOf/0',\n                                                    },\n                                                    {\n                                                        $ref:\n                                                            '#/definitions/LinkObject/properties/test/allOf/0/additionalProperties/anyOf/1',\n                                                    },\n                                                ],\n                                            },\n                                        },\n                                        additionalProperties: {\n                                            anyOf: [\n                                                {\n                                                    type: 'array',\n                                                    items: { type: 'object' },\n                                                },\n                                                { type: 'object' },\n                                            ],\n                                        },\n                                    },\n                                ],\n                            },\n                        },\n                    },\n                },\n            };\n            const result = jsonSchema.extractSchema(\n                doc,\n                '/paths/~1test/get/responses/200/content/application~1json/schema'\n            );\n\n            expect(result).to.eql({\n                type: 'object',\n                properties: {\n                    test: {\n                        $ref: '#/definitions/0',\n                    },\n                },\n                definitions: {\n                    schema0: {\n                        type: 'array',\n                        items: { type: 'object' },\n                    },\n                    '1': { type: 'object' },\n                    '0': {\n                        type: 'object',\n                        properties: {\n                            next: {\n                                anyOf: [\n                                    { $ref: '#/definitions/schema0' },\n                                    { $ref: '#/definitions/1' },\n                                ],\n                            },\n                        },\n                        additionalProperties: {\n                            anyOf: [\n                                {\n                                    type: 'array',\n                                    items: { type: 'object' },\n                                },\n                                { type: 'object' },\n                            ],\n                        },\n                    },\n                },\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/utils/mimeTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport { MimeTypeRegistry, parseMimeType } from '../../src/utils/mime';\n\ndescribe('mime utils', function () {\n    describe('parseMimeType', function () {\n        it('should parse a mime type', function () {\n            expect(parseMimeType('application/json')).to.eql({\n                type: 'application',\n                subtype: 'json',\n            });\n        });\n\n        it('should always return lower case', function () {\n            expect(parseMimeType('APPLICATION/JSON')).to.eql({\n                type: 'application',\n                subtype: 'json',\n            });\n        });\n\n        it('should ignore parameters', function () {\n            expect(parseMimeType('text/html; foo=bar')).to.eql({ type: 'text', subtype: 'html' });\n        });\n    });\n\n    describe('MimeTypeRegistry', function () {\n        it('should register and retrieve mime types', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('application/json', 7);\n            registry.set('application/xml', 8);\n            registry.set('text/plain', 9);\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get('application/xml')).to.equal(8);\n            expect(registry.get('text/plain')).to.equal(9);\n            expect(registry.get('text/html')).to.equal(undefined);\n            expect(registry.get('image/gif')).to.equal(undefined);\n        });\n\n        it('should register and retrieve parsed mime types', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set({ type: 'application', subtype: 'json' }, 7);\n            registry.set('application/xml', 8);\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get({ type: 'application', subtype: 'xml' })).to.equal(8);\n        });\n\n        it('should allow initialization via a hash of mime types', function () {\n            const registry = new MimeTypeRegistry<number>({\n                'application/json': 7,\n                'application/xml': 8,\n                'text/plain': 9,\n            });\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get('application/xml')).to.equal(8);\n            expect(registry.get('text/plain')).to.equal(9);\n            expect(registry.get('text/html')).to.equal(undefined);\n            expect(registry.get('image/gif')).to.equal(undefined);\n        });\n\n        it('should resolve wildcards', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('application/*', 7);\n            registry.set('text/*', 9);\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get('application/xml')).to.equal(7);\n            expect(registry.get('text/plain')).to.equal(9);\n            expect(registry.get('image/gif')).to.equal(undefined);\n        });\n\n        it('should prefer static mimetypes over wildcards', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('application/*', 7);\n            registry.set('application/xml', 8);\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get('application/xml')).to.equal(8);\n        });\n\n        it('should prefer more specific wildcards over */*', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('*/*', 6);\n            registry.set('application/*', 7);\n            registry.set('image/*', 9);\n            registry.set('application/xml', 8);\n\n            expect(registry.get('application/json')).to.equal(7);\n            expect(registry.get('application/xml')).to.equal(8);\n            expect(registry.get('text/plain')).to.equal(6);\n        });\n\n        it('should list registered mime types', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('*/*', 6);\n            registry.set('application/*', 7);\n            registry.set('image/*', 9);\n            registry.set('application/xml', 8);\n            registry.set('TEXT/PLAIN', 8);\n\n            expect(registry.getRegisteredTypes().sort()).to.eql([\n                '*/*',\n                'application/*',\n                'application/xml',\n                'image/*',\n                'text/plain',\n            ]);\n        });\n\n        it('should not be case sentitive', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('application/json', 7);\n            registry.set('TEXT/PLAIN', 9);\n            registry.set('Image/*', 10);\n\n            expect(registry.get('application/json'), 'application/json').to.equal(7);\n            expect(registry.get('Application/JSON'), 'Application/JSON').to.equal(7);\n            expect(registry.get('text/plain'), 'text/plain').to.equal(9);\n            expect(registry.get('image/gif'), 'image/gif').to.equal(10);\n            expect(registry.get('IMAGE/GIF'), 'IMAGE/GIF').to.equal(10);\n        });\n\n        it('should query mime types with parameters', function () {\n            const registry = new MimeTypeRegistry<number>();\n            registry.set('application/json', 7);\n\n            expect(registry.get('application/json; foo=bar')).to.equal(7);\n            expect(registry.get('application/json;foo=bar')).to.equal(7);\n            expect(registry.get('application/json ; foo=bar')).to.equal(7);\n            expect(registry.get('application/json; foo=bar; baz=qux')).to.equal(7);\n        });\n\n        it('should complain about invalid mime types', function () {\n            const registry = new MimeTypeRegistry<number>();\n\n            expect(() => registry.set('application/json/foo', 7)).to.throw(\n                'Invalid MIME type: \"application/json/foo\"'\n            );\n\n            expect(() => registry.set('application', 7)).to.throw(\n                'Invalid MIME type: \"application\"'\n            );\n        });\n    });\n});\n"
  },
  {
    "path": "test/utils/stringToStreamTest.ts",
    "content": "import 'mocha';\nimport { expect } from 'chai';\n\nimport stringToStream from '../../src/utils/stringToStream';\n\ndescribe('stringToStream', function () {\n    it('should convert a string to a stream', function (done) {\n        const stream = stringToStream('hello');\n        let result = '';\n\n        stream.on('data', (chunk) => {\n            if (typeof chunk === 'string') {\n                result += chunk;\n            } else {\n                result += chunk.toString('utf-8');\n            }\n        });\n\n        stream.on('end', () => {\n            try {\n                expect(result).to.equal('hello');\n                done();\n            } catch (err) {\n                done(err);\n            }\n        });\n    });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        /* Basic Options */\n        \"target\": \"es2016\" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,\n        \"module\": \"commonjs\" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,\n        \"lib\": [\"es2016\"] /* Specify library files to be included in the compilation. */,\n        \"allowJs\": false /* Allow javascript files to be compiled. */,\n        // \"checkJs\": true,                       /* Report errors in .js files. */\n        // \"jsx\": \"preserve\",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */\n        \"declaration\": true /* Generates corresponding '.d.ts' file. */,\n        \"sourceMap\": true /* Generates corresponding '.map' file. */,\n        // \"outFile\": \"./\",                       /* Concatenate and emit output to single file. */\n        \"outDir\": \"./lib\" /* Redirect output structure to the directory. */,\n        // \"rootDir\": \"./\",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */\n        // \"removeComments\": true,                /* Do not emit comments to output. */\n        // \"noEmit\": true,                        /* Do not emit outputs. */\n        // \"importHelpers\": true,                 /* Import emit helpers from 'tslib'. */\n        // \"downlevelIteration\": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */\n        // \"isolatedModules\": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */\n        \"stripInternal\": true,\n\n        /* Strict Type-Checking Options */\n        \"strict\": true /* Enable all strict type-checking options. */,\n        // \"strictNullChecks\": true,              /* Enable strict null checks. */\n        // \"strictFunctionTypes\": true,           /* Enable strict checking of function types. */\n        // \"strictPropertyInitialization\": true,  /* Enable strict checking of property initialization in classes. */\n        \"noImplicitAny\": true /* Raise error on expressions and declarations with an implied 'any' type. */,\n        \"noImplicitThis\": true /* Raise error on 'this' expressions with an implied 'any' type. */,\n        \"alwaysStrict\": true /* Parse in strict mode and emit \"use strict\" for each source file. */,\n\n        /* Additional Checks */\n        \"forceConsistentCasingInFileNames\": true,\n        \"noUnusedLocals\": true /* Report errors on unused locals. */,\n        \"noUnusedParameters\": true /* Report errors on unused parameters. */,\n        \"noImplicitReturns\": true /* Report error when not all code paths in function return a value. */,\n        \"noFallthroughCasesInSwitch\": true /* Report errors for fallthrough cases in switch statement. */,\n\n        /* Module Resolution Options */\n        // \"moduleResolution\": \"node\",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */\n        // \"baseUrl\": \"./\",                       /* Base directory to resolve non-absolute module names. */\n        // \"paths\": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */\n        // \"rootDirs\": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */\n        \"typeRoots\": [\n            /* List of folders to include type definitions from. */ \"./node_modules/@types\",\n            \"./@types\"\n        ],\n        // \"typeRoots\": [],                       /* List of folders to include type definitions from. */\n        // \"types\": [],                           /* Type declaration files to be included in compilation. */\n        // \"allowSyntheticDefaultImports\": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */\n        \"esModuleInterop\": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,\n        // \"preserveSymlinks\": true,              /* Do not resolve the real path of symlinks. */\n\n        /* Source Map Options */\n        // \"sourceRoot\": \"./\",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */\n        // \"mapRoot\": \"./\",                       /* Specify the location where debugger should locate map files instead of generated locations. */\n        // \"inlineSourceMap\": true,               /* Emit a single file with source maps instead of having a separate file. */\n        // \"inlineSources\": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */\n\n        /* Experimental Options */\n        // \"experimentalDecorators\": true,        /* Enables experimental support for ES7 decorators. */\n        // \"emitDecoratorMetadata\": true,         /* Enables experimental support for emitting type metadata for decorators. */\n        \"skipLibCheck\": true\n    },\n    \"include\": [\"./src/**/*\"],\n    \"compileOnSave\": true\n}\n"
  }
]