Full Code of exegesis-js/exegesis for AI

master 0afac47f6dcc cached
136 files
413.7 KB
92.8k tokens
367 symbols
1 requests
Download .txt
Showing preview only (449K chars total). Download the full file or copy to clipboard to get everything.
Repository: exegesis-js/exegesis
Branch: master
Commit: 0afac47f6dcc
Files: 136
Total size: 413.7 KB

Directory structure:
gitextract_vv9qn1ho/

├── .eslintrc.js
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── github-ci.yaml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .markdownlint.json
├── .mocharc.js
├── .npmrc
├── .nycrc
├── .prettierrc.js
├── .releaserc.yml
├── .vscode/
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── Exegesis Controllers.md
│   ├── Exegesis Plugins.md
│   ├── OAS3 Parameter Parsing.md
│   ├── OAS3 Security.md
│   ├── OAS3 Specification Extensions.md
│   ├── Options.md
│   └── Tutorial.md
├── package.json
├── samples/
│   ├── javascript-example/
│   │   ├── controllers/
│   │   │   └── greetController.js
│   │   ├── index.js
│   │   ├── openapi.yaml
│   │   └── package.json
│   └── typescript-example/
│       ├── README.md
│       ├── controllers/
│       │   └── greetController.ts
│       ├── openapi.yaml
│       ├── package.json
│       ├── server.ts
│       └── tsconfig.json
├── src/
│   ├── bodyParsers/
│   │   ├── BodyParserWrapper.ts
│   │   ├── JsonBodyParser.ts
│   │   └── TextBodyParser.ts
│   ├── controllers/
│   │   ├── invoke.ts
│   │   └── loadControllers.ts
│   ├── core/
│   │   ├── ExegesisContextImpl.ts
│   │   ├── ExegesisResponseImpl.ts
│   │   ├── PluginsManager.ts
│   │   └── exegesisRunner.ts
│   ├── errors.ts
│   ├── index.ts
│   ├── oas3/
│   │   ├── Oas3CompileContext.ts
│   │   ├── OpenApi.ts
│   │   ├── Operation.ts
│   │   ├── Parameter.ts
│   │   ├── Path.ts
│   │   ├── Paths/
│   │   │   ├── PathResolver.ts
│   │   │   └── index.ts
│   │   ├── README.md
│   │   ├── RequestMediaType.ts
│   │   ├── Response.ts
│   │   ├── Responses.ts
│   │   ├── Schema/
│   │   │   └── validators.ts
│   │   ├── SecuritySchemes.ts
│   │   ├── Servers.ts
│   │   ├── extensions.ts
│   │   ├── index.ts
│   │   ├── oasUtils/
│   │   │   └── index.ts
│   │   ├── parameterParsers/
│   │   │   ├── README.md
│   │   │   ├── common.ts
│   │   │   ├── delimitedParser.ts
│   │   │   ├── index.ts
│   │   │   ├── pathStyleParser.ts
│   │   │   ├── simpleStringParser.ts
│   │   │   ├── structuredParser.ts
│   │   │   └── types.ts
│   │   └── urlEncodedBodyParser.ts
│   ├── options.ts
│   ├── types/
│   │   ├── basicTypes.ts
│   │   ├── bodyParser.ts
│   │   ├── core.ts
│   │   ├── index.ts
│   │   ├── options.ts
│   │   └── validation.ts
│   └── utils/
│       ├── bufferToStream.ts
│       ├── httpUtils.ts
│       ├── json-schema-infer-types.ts
│       ├── json-schema-resolve-ref.ts
│       ├── jsonPaths.ts
│       ├── jsonSchema.ts
│       ├── mime.ts
│       ├── stringToStream.ts
│       └── typeUtils.ts
├── test/
│   ├── controllers/
│   │   ├── fixtures/
│   │   │   ├── badControllers/
│   │   │   │   └── a.md
│   │   │   ├── controllers/
│   │   │   │   ├── a.js
│   │   │   │   ├── b/
│   │   │   │   │   └── c.js
│   │   │   │   └── d/
│   │   │   │       └── index.js
│   │   │   └── shadow/
│   │   │       ├── a/
│   │   │       │   └── index.js
│   │   │       └── a.js
│   │   └── loadControllersTest.ts
│   ├── core/
│   │   ├── ExegesisResponseImplTest.ts
│   │   └── pluginTest.ts
│   ├── fixtures/
│   │   ├── FakeApiInterface.ts
│   │   ├── FakeExegesisContext.ts
│   │   └── index.ts
│   ├── integration/
│   │   ├── integration/
│   │   │   ├── controllers/
│   │   │   │   ├── echoController.ts
│   │   │   │   ├── greetController.ts
│   │   │   │   ├── malformed.ts
│   │   │   │   ├── postWithDefault.ts
│   │   │   │   ├── postWithOptionalBody.ts
│   │   │   │   ├── secureController.js
│   │   │   │   ├── statusController.ts
│   │   │   │   └── streamController.ts
│   │   │   ├── customErrorFormattingTest.ts
│   │   │   ├── customErrorHandler.ts
│   │   │   ├── integrationTest.ts
│   │   │   └── openapi.yaml
│   │   └── issue-132/
│   │       ├── entry.yaml
│   │       ├── issue132Test.ts
│   │       └── openapi.yaml
│   ├── oas3/
│   │   ├── OperationTest.ts
│   │   ├── Paths/
│   │   │   ├── PathResolverTest.ts
│   │   │   └── PathsTest.ts
│   │   ├── ResponsesTest.ts
│   │   ├── Schema/
│   │   │   └── validatorsTest.ts
│   │   ├── ServersTest.ts
│   │   ├── oas3ControllersTest.ts
│   │   ├── oas3ParametersTest.ts
│   │   ├── parameterParsers/
│   │   │   ├── delimitedParserTest.ts
│   │   │   └── indexTest.ts
│   │   └── samplesTest.ts
│   ├── samples/
│   │   └── petstore.yaml
│   ├── tsconfig.json
│   └── utils/
│       ├── json-schema-infer-typesTest.ts
│       ├── json-schema-resolve-refTest.ts
│       ├── jsonPathsTest.ts
│       ├── jsonSchemaTest.ts
│       ├── mimeTest.ts
│       └── stringToStreamTest.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.js
================================================
module.exports = {
    parser: '@typescript-eslint/parser',
    parserOptions: {
        project: ['./tsconfig.json', './test/tsconfig.json'],
    },
    extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
    rules: {
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-inferrable-types': 'off',
    },
    overrides: [
        {
            files: ['test/**/*.ts', 'test/**/*.tsx'],
            rules: {
                '@typescript-eslint/no-non-null-assertion': 'off',
            },
        },
    ],
};


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
    - package-ecosystem: npm
      directory: '/'
      schedule:
          interval: daily
          time: '10:00'
      open-pull-requests-limit: 10


================================================
FILE: .github/workflows/github-ci.yaml
================================================
name: GitHub CI
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - name: test
        run: npm test
  release:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/master'
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - run: npm install
      - name: semantic-release
        run: npm run semantic-release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .gitignore
================================================
node_modules
/notes.md
/lib
/types
/.nyc_output
/coverage
/npm-debug.log
/yarn-error.log
/yarn.lock
/package.lock
.DS_Store
.idea

# Artifacts from IDEs, etc.:
/.vscode/snipsnap.code-snippets


================================================
FILE: .husky/.gitignore
================================================
_


================================================
FILE: .husky/pre-commit
================================================
npx --no-install pretty-quick --staged
npx --no-install lint-staged

================================================
FILE: .markdownlint.json
================================================
{
    "line-length": false,
    "no-inline-html": {
      "allowed_elements": [
        "a"
      ]
    },
    "no-trailing-punctuation": {
        "punctuation": ".,;:!"
    },
    "single-trailing-newline": false
}

================================================
FILE: .mocharc.js
================================================
module.exports = {
    extension: ['js', 'jsx', 'ts', 'tsx'],
    require: ['ts-node/register'],
    recursive: true,
    fullTrace: true,
    checkLeaks: true,
    reporter: 'spec',
};


================================================
FILE: .npmrc
================================================
package-lock=false


================================================
FILE: .nycrc
================================================
{
    "extension": [".ts"],
    "exclude": ["**/*.d.ts"],
    "include": ["src/**/*.ts"],
    "reporter": ["html", "text-summary", "lcov"],
    "all": true
}

================================================
FILE: .prettierrc.js
================================================
module.exports = {
    trailingComma: 'es5',
    printWidth: 100,
    tabWidth: 4,
    semi: true,
    singleQuote: true,
    overrides: [
        {
            files: '*.md',
            options: {
                tabWidth: 2,
            },
        },
        {
            files: '*.yaml',
            options: {
                tabWidth: 2,
            },
        },
    ],
};


================================================
FILE: .releaserc.yml
================================================
extends: '@jwalton/semantic-release-config'

================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Run Current Mocha Test",
            "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
            "protocol": "inspector",
            "smartStep": true,
            "sourceMaps": true,
            "outputCapture": "std",
            "args": ["--colors", "${relativeFile}"],
            "env": {
                "RUN_FROM_SRC": "true",
                "NODE_ENV": "test",
                "SETUP_DOM": "true"
            },
            "internalConsoleOptions": "openOnSessionStart"
        }
    ]
}


================================================
FILE: .vscode/settings.json
================================================
{
    "typescript.tsdk": "./node_modules/typescript/lib/"
}

================================================
FILE: .vscode/tasks.json
================================================
 {
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "type": "typescript",
            "tsconfig": "tsconfig.json",
            "problemMatcher": [
                "$tsc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "type": "npm",
            "script": "test",
            "problemMatcher": []
        }
    ]
}

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

This project uses [semantic-release](https://github.com/semantic-release/semantic-release)
so commit messages should follow [Angular commit message conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines).

## Supported Node Versions

Exegesis should run on node 10.x.x and up.

In a perfect world, no polyfills will be required.  Exegesis should *not* add
any polyfills on its own.  If there are required polyfills, we should document
what they are in README.md and let users of this library decide which polyfills
they want to include.


================================================
FILE: LICENSE
================================================
Copyright 2018 Jason Walton 

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Exegesis OpenAPI Engine

[![NPM version](https://badge.fury.io/js/exegesis.svg)](https://npmjs.org/package/exegesis)
![Build Status](https://github.com/exegesis-js/exegesis/workflows/GitHub%20CI/badge.svg)
[![Coverage Status](https://coveralls.io/repos/exegesis-js/exegesis/badge.svg)](https://coveralls.io/r/exegesis-js/exegesis)
[![Greenkeeper badge](https://badges.greenkeeper.io/exegesis-js/exegesis.svg)](https://greenkeeper.io/)
[![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)

> ## _exegesis_
>
> _n._ An explanation or critical interpretation of a text, especially an
> API definition document.
>
> -- No dictionary ever

This library implements a framework-agnostic server side implementation of
[OpenAPI 3.x](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#requestBodyObject).

## Description

Exegesis is a library for implementing server-side OpenAPI 3.x The library has been
written in such a way that hopefully it will also be used to implement future
versions of OpenAPI, or possibly even other API description standards altogether.

You probably don't want to be using this library directly. Have a look at:

- [exegesis-express](https://github.com/exegesis-js/exegesis-express) - Middleware
  for serving OpenAPI 3.x APIs from [express](https://expressjs.com/) or
  [connect](https://github.com/senchalabs/connect).
- [exegesis-koa](https://github.com/confuser/exegesis-koa) - Middleware
  for serving OpenAPI 3.x APIs from [koa](https://koajs.com/).

## Features

- 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).
- Built in support for "application/json" and "application/x-www-form-urlencoded" requests
- Can use express [body parser middlewares](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#mimetypeparsers)
- [Response validation](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#onresponsevalidationerror)
- [Authentication support](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md)
- [Plugins](https://github.com/exegesis-js/exegesis/tree/master/docs) allow easy extensibility
- Easy support for [validating custom formats](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#customformats)

## Tutorial

Check out the tutorial [here](https://github.com/exegesis-js/exegesis/blob/master/docs/Tutorial.md).

## API

### compileApi(openApiDoc, options[, done])

This function takes an API document and a set of
[options](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md),
and returns a connect-style middleware function which will execute the API.

`openApiDoc` is either a path to your openapi.yaml or openapi.json file,
or it can be a JSON object with the contents of your OpenAPI document. This
should have the [`x-exegesis-controller`](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Specification%20Extensions.md)
extension defined on any paths you want to be able to access.

`options` is described in detail [here](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md). At a
minimum, you'll probably want to provide `options.controllers`, a path to where
your [controller modules](https://github.com/exegesis-js/exegesis/blob/master/docs/Exegesis%20Controllers.md)
can be found. If you have any security requirements defined, you'll also
want to pass in some [authenticators](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md).
To enable response validation, you'll want to provide a validation callback
function via [`onResponseValidationError()`](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md#onresponsevalidationerror).
Exegesis's functionality can also be extended using [plugins](https://github.com/exegesis-js/exegesis/tree/master/docs),
which run on every request. Plugins let you add functionality like
[role base authorization](https://github.com/exegesis-js/exegesis-plugin-roles),
or CORS.

### compileRunner(openApiDoc, options[, done])

This function is similar to `compileApi`; it takes an API document and a set of
[options](https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md),
and returns a "runner". The runner is a `function runner(req, res)`, which takes
in a standard node HTTP request and response. It will not modify the response,
however. Instead it returns (either via callback or Promise) and `HttpResult`
object. This is a `{headers, status, body}` object, where `body` is a readable
stream, read to be piped to the response.

### writeHttpResult(httpResult, res[, done])

A convenience function for writing an `HttpResult` from a runner out to the
response.

## Example

```js
import * as path from 'path';
import * as http from 'http';
import * as exegesis from 'exegesis';

// See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
const options = {
  controllers: path.resolve(__dirname, './src/controllers'),
};

// `compileApi()` can either be used with a callback, or if none is provided,
// will return a Promise.
exegesis.compileApi(
  path.resolve(__dirname, './openapi/openapi.yaml'),
  options,
  (err, middleware) => {
    if (err) {
      console.error('Error creating middleware', err.stack);
      process.exit(1);
    }

    const server = http.createServer((req, res) =>
      middleware(req, res, (err) => {
        if (err) {
          res.writeHead(err.status || 500);
          res.end(`Internal error: ${err.message}`);
        } else {
          res.writeHead(404);
          res.end();
        }
      })
    );

    server.listen(3000);
  }
);
```

## Internal Workings

Internally, when you "compile" an API, Exegesis produces an
[ApiInterface](https://github.com/exegesis-js/exegesis/blob/f5266dfd27cdb40c5ebf8063303acbf483d78ed9/src/types/internal.ts#L50) object.
This is an object that, given a method, url, and headers, returns a
[`resolvedOperation`](https://github.com/exegesis-js/exegesis/blob/f5266dfd27cdb40c5ebf8063303acbf483d78ed9/src/types/internal.ts#L21) -
essentially a collection of functions that will parse and validate the body and
parameters, has the controller that executes the functionality, etc... The only
current implementation for an ApiInterface is the
[`oas3/OpenApi` class](https://github.com/exegesis-js/exegesis/blob/master/src/oas3/OpenApi.ts).
Essentially this class's job is to take in an OpenAPI 3.x.x document, and turn it
an ApiInterface that Exegesis can use. In theory, however, we could parse some
other API document format, produce an ApiInterface, and Exegsis would still be
able to run it.


================================================
FILE: docs/Exegesis Controllers.md
================================================
# Introduction to Controllers

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Introduction to Controllers](#introduction-to-controllers)
  - [Writing Controllers](#writing-controllers)
  - [Specifying a Controller to Run](#specifying-a-controller-to-run)
  - [What's in a Context?](#whats-in-a-context)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

## Writing Controllers

Exegesis controllers are functions that take in a context, and produce a result
to return to the client. This is one of the simplest controllers you can
write:

```js
export function myController(context) {
  const name = context.params.query.name;
  return { message: `Hello ${name}` };
}
```

This will return the object provided as a JSONdocs response. You can return a
JSON object, a string, a buffer, or a readable stream. You can also more
explicitly set the body by setting `res.body` or calling `res.setBody()`,
`res.json()`, or `res.pureJson()`:

```js
export function myController(context) {
  const name = context.params.query.name;
  context.res
      .status(200)
      .set('content-type', 'application/json');
      .setBody({message: `Hello ${name}`});
}
```

Controllers can be asynchronous, supporting either callbacks or Promises:

```js
export function myAsyncController(context, callback) {
  callback(null, { message: 'Hello World!' });
}

export function myPromiseController(context) {
  return Promise.resolve({ message: 'Hello World!' });
}
```

Controllers can, of course, also return non-JSON data:

```js
export function myController(context) {
  const name = context.params.query.name;
  context.res
    .status(200)
    .setHeader('content-type', 'text/xml')
    .setBody(`<message>Hello ${name}</message>`);
}
```

Note, however, that response validation will not be done if the body is not a
JSON object.

## Specifying a Controller to Run

Controllers are defined inside modules (.js files). In order to resolve a
controller, you specify both the name of the module and the name of the
function to call within the module. Here's a quick example:

```yaml
openapi: 3.0.3
info:
  title: Example
  version: 1.0.0
paths:
  "/users"
    x-exegesis-controller: userController
    get:
      operationId: getUsers
```

Here, we'd find a module named "userController.js", and then we'd call
`getUsers(context)` within that module.

If you have a path that takes input in multiple different formats, you can
also specify the `operationId` in the MediaType object, using
`x-exegesis-operationId`:

```yaml
openapi: 3.0.3
info:
  title: Example
  version: 1.0.0
paths:
  "/users"
    x-exegesis-controller: userController
    post:
      content:
        application/json:
          schema: {}
          x-exegesis-operationId: getUsersJson
        multipart/form-data:
          schema: {}
          x-exegesis-operationId: getUsersMultipart
```

You may specify `x-exegesis-controller` in any of the following:

- [OpenAPI Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oasObject)
- [Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject)
- [Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathItemObject)
- [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operationObject)
- [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]`.

Exegesis will start from the Media Type Object specified by a given request,
walk its way upwards to find the "closest" `x-exegesis-controller`, and then
do the same for `x-exegesis-operationId`. This will uniquely identify which
controller module and function to call.

## What's in a Context?

- `context.req` - The Node `http.IncomingMessage` from Node.js.
- `context.res` - A `Exegesis Response` object. This is very similar to a
  `http.ServerResponse`, and has many functions that will be familiar if you're
  used to express.
- `context.origRes` - This is the original `http.ServerResponse` from Node.js.
  In general you should not write directly to `context.origRes`. Exegesis will
  be unable to do response validation if you write directly to the origRes
  object.
- `context.params` - This is a `{query, header, path, server, cookie}` object.
  Each member is an object where the keys are parameter names, and values are
  the parsed parameters.
- `context.parameterLocations` - This is a mirror of the `context.params` object,
  but instead of parameter values, this has parameter locations. (`server`
  parameters are not preset in `parameterLocations` in the current release.)
- `context.requestBody` - The parsed message body, if one is present.
- `context.security` - This is a dictionary where keys are security schemes,
  and values are `{user, roles, scope}` objects (as returned by the associated
  [authenticator](./OAS3%20Security.md)). For OAS3, there will only be objects
  here for the first matched security requirement.
- `context.user` - This is a user, as returned by an authenticator. For OAS3
  this will only be present if the matched security requirement had exactly
  one security scheme.
- `context.api` - This is an object containing details about which parts of
  the OpenAPI document were resolved to service your request. For OAS3 this
  will be an object with the following fields:

  - `openApiDoc` - The OpenAPI document for your API. This is a "bundled" object,
    with all external $refs resolved, and only internal $refs remaining.
  - `serverObject` - The Server Object which was matched.
  - `serverPtr` - A JSON Pointer to the server object in `openApiDoc`.
  - `pathItemObject` - The Path Item Object which was matched.
  - `pathItemPath` - A JSON Pointer to the path item object.
  - `operationObject` - The matched Operation Object.
  - `operationPtr` - A JSON Pointer to the Operation Object.
  - `requestBodyMediaTypeObject` - The matched MediaType Object from the operation's
    `requestBody`, or null if none was matched or the Operation has no requestBody.
  - `requestBodyMediaTypePtr` - A JSON Pointer to the MediaType Object.

- `context.makeError(statusCode, message)` is a convenience function which
  will create a new Error object with a status. Note that this does not
  throw the error - your code has to do that.

- `context.makeValidationError(message, location)` is a convenience function which
  will creates a new validation error. `location` should be a parameter location
  from `context.parameterLocations`. Note that this does not throw the error - your
  code has to do that.

- `context.route.path` is a string with the path that was matched from the API document.
  This is similar to `req.route.path` in Express.

- `context.baseUrl` is a string with the baseUrl that was stripped off to match
  `context.route.path`. This is similar to `req.baseUrl` in Express.


================================================
FILE: docs/Exegesis Plugins.md
================================================
# Plugins

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Plugins](#plugins)
  - [Writing a Plugin](#writing-a-plugin)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

Plugins can be used to add functionality to Exegesis.

Today Exegesis only supports OpenAPI 3.x.x (OAS3), however the core of Exegesis
was designed to allow it to handle other API specifications. Exegesis
divides the execution of an API up into the following phases:

- **Routing** - Any REST base API will define a set of URLs or URIs which map to
  various functions and resources. Exegesis first examines the URL to work out
  which resource is being accessed. In OAS3, this means matching a Server
  Object and Path Object.
- **Security** - After routing is finished, but before we do any work parsing input,
  Exegesis will figure out if the request is authenticated and authorized to
  access the given route. In OAS3, this means checking the Security Object
  associated with the route, and verifying that at least one of the Security
  Requirements has been met.
- **Input Parsing and Validation** - Exegesis takes a "lazy" approach to parsing
  both parameters and the request body; the body and parameters are parsed
  and validated the first time they are accessed. This allows authenticators
  and plugins access to the body and parameters if required, but also means
  if no one asks for the parameters or the body, we won't bother to parse
  them. However, to make writing controllers easier, if they have not be parsed
  by this point Exegesis explicitly parses them and stores them in
  `context.params` and `context.requestBody`.
- **Controller** - Exegesis calls into a controller to run business logic
  associated with the resource being accessed.
- **Response Validation** - Exegesis optionally validates that the response
  has been correctly generated. For OAS3, this means checking the response
  against the response schema.

Finally, the response is written out back to the client.

Plugins allow you to add functionality before (almost) any of these phases,
and even to modify the API document before it is compiled.

## Writing a Plugin

Plugins are published on NPM with the prefix `exegesis-plugin-` to make it
easy to find plugins.

To write a plugin, you write a JavaScript module which exports a default function.
This function accepts a single parameter which is an object with the options to
configure your plugin. It returns a `{info, makeExegesisPlugin(data)}` object.
`makeExegesisPlugin` takes a single parameter, an `{apiDoc}` object. Right now
the API document will always be an OAS3 document, however this may not be the
case in the future, so plugins should take care to verify the document they are
being passed is in the format they expect.

The `makeExegesisPlugin` function should return an `ExegesisPluginInstance`
object. This has functions which will be called at various phases for each
request. See the example below for a list of functions that can be defined.
Each function can either take a callback function as the last parameter, or
return a Promise. See the example below.

Plugins are free to modify the `apiDoc` object in the `makeExegesisPlugin()`
function (although they may not replace it with an entirely new object). This
is the only time that plugins may modify the document.

In any phase, plugins can also generate a response by writing to
`context.res.body`, or can add headers to `context.res.headers`. Plugins must
_not_ write to `context.origRes`. Writing a response will cause subsequent
phases and plugins to be skipped, and if the controller has not yet been called,
the controller will be skipped. The exception to this is the `postController()`
function, which will always be called for a plugin, even if a previous phase has
written a response.

```js
import * as semver from 'semver';

function makeExegesisPlugin({apiDoc}) {
  // Verify the apiDoc is an OpenAPI 3.x.x document, because this plugin
  // doesn't know how to handle anything else.
  if (!apiDoc.openapi) {
    throw new Error("OpenAPI definition is missing 'openapi' field");
  }
  if (!semver.satisfies(apiDoc.openapi, '>=3.0.0 <4.0.0')) {
    throw new Error(`OpenAPI version ${apiDoc.openapi} not supported`);
  }

  // Can make modifications to apiDoc at this point, such as adding new
  // routes, or modifying documentation - whatever you want to do.  Just
  // keep in mind that other plugins might make changes, also, either before
  // or after this.  If you need the "final" apiDoc, see `preCompile`.

  // Return an ExegesisPluginInstance.
  return {
    // Called exactly once, before Exegesis "compiles" the API document.
    // Plugins must not modify apiDoc here.
    preCompile({apiDoc, options}) {
    }

    // Called before routing.  Note that the context hasn't been created yet,
    // so you just get a raw `req` and `res` object here.
    preRouting({req, res}) {
    }

    // Called immediately after the routing phase.  Note that this is
    // called before Exegesis verifies routing was valid - the
    // `pluginContext.api` object will have information about the
    // matched route, but will this information may be incomplete.
    // For example, for OAS3 we may have matched a route, but not
    // matched an operation within the route. Or we may have matched
    // an operation but that operation may have no controller defined.
    // (If we failed to match a route at all, this will not be called.)
    //
    // If your API added a route to the API document, this function is a
    // good place to write a reply.
    //
    // Note that calling `pluginContext.getParams()` or `pluginContext.getRequestBody()`
    // will throw here if routing was not successful.
    postRouting(pluginContext) {
    }

    // Called for each request, after security phase and before input
    // is parsed and the controller is run.  This is a good place to
    // do extra security checks.  The `exegesis-plugin-roles` plugin,
    // for example, generates a 403 response here if the authenticated
    // user has insufficient privliedges to access this path.
    //
    // Note that this function will not be called if a previous plugin
    // has already written a response.
    postSecurity(pluginContext) {
    }

    // Called immediately after the controller has been run, but before
    // any response validation.  This is a good place to do custom
    // response validation.  If you have to deal with something weird
    // like XML, this is where you'd handle it.
    //
    // This function can modify the contents of the response.
    postController(context) {
    }

    // Called after the response validation step.  This is the last step before
    // the response is converted to JSON and written to the output.
    postResponseValidation(context) {
    }
  };
}

export default function plugin(options) {
  return {
    info: {
      // This should match the name of your npm package.
      name: 'exegesis-plugin-example'
    },
    makeExegesisPlugin
  };
}
```


================================================
FILE: docs/OAS3 Parameter Parsing.md
================================================
# Parameter Parsing

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Parameter Parsing](#parameter-parsing)
  - [Parameter Parser Functions](#parameter-parser-functions)
  - [Cookie Parameters](#cookie-parameters)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

Parameters in OpenAPI 3 are serialized using URI Templates from RFC 6570.
The RFC explains exactly how to convert various values into strings
(a process the RFC calls "expansion") but says nothing about going in the
opposite direction (a process the RFC doesn't even give a name, but which
we will call "parsing" for this discussion).

Since parameter parsing happens on every incoming request, the goal is to
optimize as much of parameter parsing as possible ahead-of-time. We refer
to this process here as "compiling" the parser.

The Exegesis context is expecting a `parameters` object that consists of one
key for each of the "in"s (path, query, header, cookie), and each of those
objects has parameter names for keys, mapping to the associated values:

```js
parameters = {
  path: {
    id: '5acc0981eaad142f3a754c77',
  },
  query: {
    min: 6,
    users: ['tom', 'dick', 'harry'],
    deepObject: { a: 7, b: [2, 4] },
  },
  header: {},
  cookie: {},
};
```

The path resolver extracts all the values of the path parameters for us as raw
strings and stores them in an object. Node.js does the same for us for
headers. So, our goal is: given an object of path parameters, an object
of headers, and a query string, produce the above in as short a time as possible.

The first thing to note about URI template expansion is that, using "[form-style
query expansion](https://tools.ietf.org/html/rfc6570#section-3.2.8)", without
the "explode" modifier the array `['a', 'b', 'c', 'd']` would be expanded as
`?var=a,b,c,d`, and the object `{a: 'b', c: 'd'}` would be expanded as exactly
the same thing. So the first thing we need to know about a parameter, before
we can parse the value, is the target type we're trying to parse the parameter
as. Unfortunately, OpenAPI lets us use JSON-schema to define the type, and it's
easy with `oneOf` or `anyOf` to construct a schema that could validate both
an array and an object.

So, the general approach taken, when compiling a parser for an object, is:

- If the object can only be a single type, and that type is not an object,
  then we parse the result and produce either a string or an array of strings.
  We attempt to type-coerce the resulting object to the correct basic type. If
  this fails, we throw a validation error. We don't do full validation here -
  we leave that for the validation step. So if the type of the object is
  "array", then you'll get an array of strings here. We leave it to the
  validation step to do further type coercion as required.
- If the object can be multiple types, and none of those types is "object",
  then we'll parse the object into a string or an array and let validation
  handle type coercion.
- If the object can only be a single type, and that type is an object,
  then we attempt to parse the result into an object. If this fails (because
  the "array" of values has an odd length) then we throw a validation error.
  Again, we do no formal validation here - the object may not at all match
  the schema provided, but at least it will be an object.
- If the object can be multiple types and at least one of those types is
  "object", then in a future version we'll do something clever here, like try
  to parse it as an array and do full validation and if that fails move on to
  trying to parse it as an object. Right now though, we throw an exception
  when compiling the schema. See
  [discussion here](https://github.com/OAI/OpenAPI-Specification/issues/1535#issuecomment-380032898).

## Parameter Parser Functions

At run time, parameter parsers are functions that take in some input, and produce
a value. These functions are synchronous, because it makes the parameter
code easy to work with, and we don't have the overhead of creating Promises or
dealing with callbacks.

Parser functions take in an object from a specific "in", where keys are
parameter names, and values are either a string or an array of strings. A
"path parser" function will only receive values parsed out of the path, a
"query parser" will only receive values parsed out of the query string, etc.

Parsers also receive a second parameter, a `parameterContext`, which has
information about where the parameter came from, and in the case of query
parameters has access to the original query string.

Note that values passed to parameter parsers will not be passed through
`decodeURIComponent()` first; RFC 6570 requires that characters from the
"reserved set" be %-encoded. So, for a "simple" list, a value that contains
a "," will have that "," encoded. This means we need to split the raw
value on "," and then pass each resulting string through `decodeURIComponent()`
to correctly parse a list.

There are two special cases with query parameters. The first is where you
have a query parameter which represents an object, and the "explode" option is
set. For example:

```yaml
parameter:
  name: 'myParam'
  in: query
  style: form
  explode: true
  schema:
    type: object
    properties:
      a: { type: string }
      b: { type: string }
```

This will be expanded in the query string as '?a=foo&b=bar'. Note that the name
of our parameter, 'myParam', doesn't even appear in the query string.

As a result, query parameter parsers for exploded objects are passed the entire
set of extracted values as their 'value', and simply return it. We let validation
take care of working out if the are extra fields in the object that shouldn't be
there.

The second special case is for query parameters where the style is set to "deepObject".
In this case, we parse the entire query string with the `qs` library, find the
value for the parameter name, and return this as the parsed object. Here we
don't worry about %-encoding anything, we just let `qs` handle everything.

Note that the same query parsers are used to handle `application/x-www-form-urlencoded`
bodies.

## Cookie Parameters

The format for cookie parameters is ambiguous in OpenAPI 3.0. The specification
says one thing, but the documentation on swagger.io says something else.
Until [OpenAPI-Specification #1528](https://github.com/OAI/OpenAPI-Specification/issues/1528)
is resolved, Exegesis will probably not support cookie parameters. If you have a specific
use case, please raise an issue, and we'll see if we can help you out.


================================================
FILE: docs/OAS3 Security.md
================================================
# OAS3 Security

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [OAS3 Security](#oas3-security)
  - [Authenticators](#authenticators)
    - [Example: Basic Auth](#example-basic-auth)
    - [Example: Basic Auth with Passport](#example-basic-auth-with-passport)
  - [Example](#example)
    - [Using Multiple Authentication Types](#using-multiple-authentication-types)
      - [Scenarios](#scenarios)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

Each 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)
associated with it, in `security` (or inherited from the root document's `security`).
Each security requirement object has a list of security schemes; in order to
access an operation, a request must satisfy all security schemes for at least
one of the objects in the list.

When compiling your API, Exegesis will takes an `authenticators` option which
maps security schemes to authenticators. An `authenticator` is a
function which tries to authenticate the user using a given scheme.

## Authenticators

An authenticator is a function which authenticates a request.

```js
async function promiseAuthenticator(pluginContext, info) {...}
function callbackAuthenticator(pluginContext, info, done) {...}
```

For example:

```js
async function sessionAuthenticator(pluginContext, info) {
  const session = pluginContext.req.headers.session;
  if (!session) {
    return { type: 'missing', statusCode: 401, message: 'Session key required' };
  } else if (session === 'secret') {
    return { type: 'success', user: { name: 'jwalton', roles: ['read', 'write'] } };
  } else {
    // Session was supplied, but it's invalid.
    return { type: 'invalid', statusCode: 401, message: 'Invalid session key' };
  }
}

const options: exegesis.ExegesisOptions = {
  controllers: path.resolve(__dirname, './controllers'),
  authenticators: {
    sessionKey: sessionAuthenticator,
  },
};
```

Authenticators are very similar to controllers, except their role is to
return authentication information. Note that the `context` passed to an
authenticator is a "plugin context" - this differs from a regular context
in that `body` and `params` will be undefined as they have not been
parsed yet (although access to the body and parameters are available via
the async functions `getRequestBody()` and `getParams()`). Authenticators are also
passed an `info` object, which is either a `{in, name}` object describing
what field the authentication information should be stored in, or else a
`{scheme}` object describing the HTTP authentication scheme being used,
as described in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-5.1).

If the user is successfully authenticated, an authenticator should return a
`{type: "success", user, roles, scopes}` object. `user` is an arbitrary object
representing the authenticated user; it will be made available to the controller
via the context. `roles` is a list of roles which the user has (used by
[exegesis-plugin-roles](https://github.com/exegesis-js/exegesis-plugin-roles)),
and `scopes` is a list of OAuth scopes the user is authorized for. Authenticators
may also add additional data to this object (for example, when authenticating
via OAuth, you might set the `user` to the user the OAuth token is for, and also
set an `oauthClient` property to identify that this user was authenticated by
OAuth.)

If the user did not provide credentials, the authenticator should return a
`{type: 'missing', challenge, status, message}` object. If
`challenge` is specified it must be an
[RFC 7235 challenge](https://tools.ietf.org/html/rfc7235#section-2.1), suitable
for including in a WWW-Authenticate header.

If the user provided authentication credentials, but they are invalid,
the authenticator should return a `{type: 'invalid', challenge, status, message}`
object.

If a authenticator returns `undefined`, this is treated like 'missing'.

When Exegesis routes a request, it will run the relevant authenticators
and decide whether or not to allow the request. Note that if an operation has
no `security`, then no authenticators will be run.

If a request successfully matches a security requirement object then Exegesis
will create a `context.security` object with the details of the matched schemes.
This will be available to the controller which handles the operation.

Authenticators are run prior to body parsing, however the body is available via
the async function `context.getRequestBody()` if it is needed.

### Example: Basic Auth

```js
import basicAuth from 'basic-auth';
import bcrypt from 'bcrypt';

// Note that authenticators can either return a Promise, or take a callback.
async function basicAuthSecurity(pluginContext, info) {
  const credentials = basicAuth(pluginContext.req);
  if (!credentials) {
    // The request failed to provide a basic auth header.
    return { type: 'missing', challenge: info.scheme };
  }

  const { name, pass } = credentials;
  const user = await db.User.find({ name });
  if (!user) {
    return {
      type: 'invalid',
      challenge: info.scheme,
      message: `User ${name} not found`,
    };
  }
  if (!(await bcrypt.compare(pass, user.password))) {
    return {
      type: 'invalid',
      challenge: info.scheme,
      message: `Invalid password for ${name}`,
    };
  }

  return {
    type: 'success',
    user,
    roles: user.roles, // e.g. `['admin']`, or `[]` if this user has no roles.
    scopes: [], // Ignored in this case, but if `basicAuth` was an OAuth
    // security scheme, we'd fill this with `['readOnly', 'readWrite']`
    // or similar.
  };
}
```

### Example: Basic Auth with Passport

Here's the exact same example, but using [Passport](http://www.passportjs.org/):

```js
import exegesisPassport from 'exegesis-passport';
import passport from 'passport';
import { BasicStrategy } from 'passport-http';
import bcrypt from 'bcrypt';

// Note the name of the auth scheme here should match the name of the security
// role.
passport.use('basicAuth', new BasicStrategy(
  function(name, password, done) {
    db.User.find({name}, (err, user) => {
      if (err) {return done(err);}
      bcrypt.compare(password, user.password, (err, matched) => {
        if (err) {return done(err);}
        return done(null, matched ? user : false);
      }
    });
  }
));

const basicAuthAuthenticator = passportSecurity('basicAuth');
```

## Example

Here's an example of the securitySchemes section from an OpenAPI document:

```yaml
  securitySchemes:
    basicAuth: 
      description: A request with a username and password
      type: http
      scheme: basic
    oauth:
      description: A request with an oauth token.
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://api.exegesis.io/oauth/authorize
          tokenUrl: https://api.exegesis.io/oauth/token
          scopes:
            readOnly: "Read only scope."
            readWrite: "Read/write scope."
```

Operations have a list of security requirements:

```yaml
paths:
  '/kittens':
    get:
      description: Get a list of kittens
      security:
        - basicAuth: []
        - oauth: ['readOnly']
```

The "get" operation can only be executed if the request matches one of the two
listed security requirements.

If a user authenticated using `basicAuth`, then the controller would have
access to the object returned by the authenticator via `context.security.basicAuth`.
Similarly, if the request used OAuth then `context.security.oauth` would be
populated with the result of the OAuth authenticator.

### Using Multiple Authentication Types

Some REST APIs support several authentication types. The security section lets you combine the security requirements
using logical OR and AND to achieve the desired result.

While 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)
specifies that `only one of Security Requirement Objects in the list needs to be satisfied to authorize the request` this
library follows the principal of least privilege by **failing authorization if _any_ of the authenticators return an `invalid` result**.
One side affect of this decision is that all authenticators will run for every request to ensure that there are no invalid results.

#### Scenarios

```yaml
security: # A OR B
  - A
  - B
```

- The request will authenticate if **either** `A` or `B` return a `success` result and **none** return an `invalid` result.
- `A` will run first then `B`
- The authentication process will return the result of the first successful authenticator.
- If a authenticator returns an invalid result the authentication process will be halted and the invalid result will be returned.

```yaml
security: # A AND B
  - A
    B
```

- The request will authenticate only if **both** `A` and `B` return a `success` result and **none** return an `invalid` result.
- `A` will run first then `B`
- The authentication process will return the result of both of the successful authenticators.
- If an authenticator returns an invalid result the authentication process will be halted and the invalid result will be returned.

```yaml
security: # (A AND B) OR (C AND D)
  - A
    B
  - C
    D
```

- The request will authenticate only if (`A` and `B`) OR (`C` AND `D`) return a `success` result and **none** return an `invalid` result.


================================================
FILE: docs/OAS3 Specification Extensions.md
================================================
# Controllers

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Controllers](#controllers)
  - [x-exegesis-controller](#x-exegesis-controller)
  - [x-exegesis-operationId](#x-exegesis-operationid)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

## x-exegesis-controller

Controls which module defines the controller for an operation.
`x-exegesis-controller` may contains "/"s if controllers are organized in a
hierarchy.

Allowed in:

- [OpenAPI Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oasObject)
- [Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject)
- [Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathItemObject)
- [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operationObject)
- [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]`.

Definitions at lower levels override definitions at higher levels.

## x-exegesis-operationId

Controls which operation is called within a controller.

Allowed in:

- [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.
- [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]`.

Definitions at lower levels override definitions at higher levels. Definitions in Operation or Media Type Object also override `operationId` in the Operation Object.


================================================
FILE: docs/Options.md
================================================
# Exegesis Options

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Exegesis Options](#exegesis-options)
  - [controllers](#controllers)
  - [allowMissingControllers](#allowmissingcontrollers)
  - [authenticators](#authenticators)
  - [mimeTypeParsers](#mimetypeparsers)
  - [defaultMaxBodySize](#defaultmaxbodysize)
  - [customFormats](#customformats)
  - [ignoreServers](#ignoreservers)
  - [autoHandleHttpErrors](#autohandlehttperrors)
  - [onResponseValidationError](#onresponsevalidationerror)
  - [validateDefaultResponses](#validatedefaultresponses)
  - [treatReturnedJsonAsPure](#treatreturnedjsonaspure)
  - [allErrors](#allerrors)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

## controllers

Controllers are functions that Exegesis executes to handle incoming requests.
You can read about controllers
[here](https://github.com/exegesis-js/exegesis/blob/master/docs/Exegesis%20Controllers.md).

The `controllers` option tells Exegesis how to find controller functions to
call. This can either be the name of a folder containing controller modules,
or it can be an object where keys are controller names. If it is a folder,
you can additionally specify `controllersPattern`, which is a glob pattern
telling Exegesis which files to load.

For example: suppose you have a folder in your project named "src/controllers",
which contains "Pets.js" and "Users.ts". If you're loading your OpenAPI
document in "/src/index.js", you could specify `controllers` as:

```js
import * as path from 'path';

const options = {
  controllers: path.resolve(__dirname, 'controllers'),
  controllersPattern: '**/*.@(ts|js)',
};
```

Then you can use [`x-exegesis-controller: Pets`](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Specification%20Extensions.md)
in your OpenAPI document to reference the "Pets.js" module, and use either
`operationId` or `x-exegesis-operationId` to reference a function within the
module.

## allowMissingControllers

If false, then if any operations do not define a controller, Exegesis will raise
an error when the API is being compiled. If true, then Exegesis will simply
pretend any operations that don't have a controller do not exist, and will not
handle them.

Defaults to true.

## authenticators

An object specifying authenticators. Keys are security scheme names from your
OpenAPI document, values are authenticator functions. See [OAS3 Security](https://github.com/exegesis-js/exegesis/blob/master/docs/OAS3%20Security.md)
for details.

## mimeTypeParsers

An object where keys are either mime types or mime type wildcards (e.g. 'text/\*'),
and values are parsers.

This option is used to control how Exegesis [parses message bodies and certain
parameters](./OAS3%20Parameter%20Parsing.md). By default, parsers are provided for 'text/\*' and
'application/json'; however you can override either of these.

OpenAPI 3.x defines special handling for 'application/x-www-form-urlencoded',
and Exegesis will automatically generate an appropriate parser for this content,
however you can override the built-in implementation by supplying your own
parser for this media type.

A parser is either an object of the form:

```js
{
    /**
     * Synchronous function which parses a string.  A BodyParser must implement
     * this function to be used for parameter parsing.
     *
     * @param {string} encoded - The encoded value to parse.
     * @returns - The decoded value.
     */
    parseString(encoded) {...}
}
```

Or:

```js
{
    /**
     * Async function which parses an incoming HTTP request.  This is essentially
     * here so you can use express/connect body parsers.
     *
     * @param {http.IncomingMessage} req - The request to read.  This function
     *   should add `req.body` after parsing the body.  If `req.body` is already
     *   present, this function can ignore the body and just call `next()`.
     * @param {http.ServerResponse} res - The response object.  Well behaved
     *   body parsers should *not* write anything to the response or modify it
     *   in any way.
     * @param next - Callback to call when complete.  If no value is returned
     *   via the callback then `req.body` will be used as the body.
     */
    parseReq(req, res, next) {...}
}
```

In order to be used for parsing parameters, a parser must implement
`parseString()`. A parser that does not implement `parseReq()` can
still be used for parsing request bodies.

## defaultMaxBodySize

If a `MimeTypeParser` provided in `mimeTypeParsers` does not support
`parseReq()`, this defines the maximum size (in bytes) of a body that will be parsed.
Bodies longer than this will result in a "413 - Payload Too Large" error.
Built in body parsers will also respect this option.

## customFormats

If you use the "format" specifier in your OpenAPI document with custom defined
formats, you must provide validation functions for each format used.

`customFormats` is an object where keys are format names. Values can be one of:

- A RegExp for checking a string.
- A `function(string) : boolean` for checking a string, which returns
  false the the string is invalid.
- A `{validate, type}` object, where `type` is either "string" or "number",
  and validate is a `function(string) : boolean`.

## ignoreServers

OpenAPI 3.x lets you specify what servers your API is available on. For example:

```yaml
servers:
  - url: '/api/v2'
```

By default, Exegesis will take 'servers' into account when routing requests,
so if you have the above servers section, and a path in your API called
"/users", then exegesis will only match the route if the incoming requests has
the URL "/api/v2/users".

If you have path templates in your servers, the variables will be available to
your controllers via `context.params.server`.

If you specify the `ignoreServers` option, however, exegesis will ignore the
servers section, and route purely based on your paths.

## autoHandleHttpErrors

By default, ExegesisRunner will turn `exegesis.HttpError`s (such as errors
generated from `context.makeError()`, `exegesis.ValidationError`s, or any error
with a `.status`) into JSON replies with appropriate error messages. If you want
to handle these errors yourself, set this value to false. Alternatively, you can set the value
to a `function(err, { req })` function that handles the error returning a `HttpResult` object.
See [customErrorHandler](../test/integration/integration/customErrorHandler.ts) for an example.

Note that all `HttpError`s will have a `.status` property with a suggested
numeric HTTP response code.

## onResponseValidationError

This is a function to call when response validation fails. If you provide this
function, Exegesis will validate the responses that controllers generate before
they are sent to the client. If you throw an exception in this function, a
500 error will be generated and the reply will not be sent.

Note that when bodies are strings, buffers, or streams, Exegesis will not try
to parse your body to see if it conforms to the response schema; only JSON
objects are validated.

If provided, this should be a `function(result)` function, where:

- `result.errors` is a list of validation errors. Validation errors
  are `{type, message, location: {in: 'response', name: 'body', docPath}}` objects.
- (for OAS3) `result.isDefault` is true if we validated against a 'default' status code.
- `result.context` is the context passed to the controller.

A note about response validation and performance; if you call `context.res.json()`
or return an object from your controller, then the object or any nested objects
could have a `toJSON()` method on them. This would happen when, for example,
you return a Mongoose object, or use a Mongoose object as a child of your object.
In order to correctly validate the response for such an object, Exegesis must
first serialize the object to JSON, and then deserialize it again to get the
actual transmitted object. This is murderous for performance. Checking to see
if an object has any toJSON() functions is also not great for performance. In
order to get around this, you can call `context.res.pureJson()` to set the JSON
reply.

Note that in a future major release of Exegesis, returning a JSON object will
have the same behavior as calling `context.res.pureJson()` instead of having the
same behavior as calling `context.res.json()`.

## validateDefaultResponses

Controls how Exegesis validates responses. If this is set to false, then in
OAS3 Exegesis will not do validation for responses unless the response status
code matches an explicit status code in the responses object (not the "default"
status code). If this is set to true, then all responses will be validated.

This option is ignored if `onResponseValidationError` is not set. If
`onResponseValidationError` is set, the default is true.

## treatReturnedJsonAsPure

If true, then when a controller returns a JSON object, exegesis will call
`context.res.pureJson(val)` to set the body of the response. If false, Exegesis
will call `context.res.json(val)`. See `onResponseValidationError()` for
a discussion about the difference between these.

This defaults to false, but in a future release it will default to true.

## strictValidation

If true, then this will put ajv into ["strict mode"](https://ajv.js.org/strict-mode.html).

## allErrors

If set, then when encountering a validation error Exegesis will return
all errors found in the document instead of just the first error. This
causes Exegesis to spend more time on requests with errors in them, so
for performance reasons this is disabled by default.

## lazyCompileValidationSchemas

Response and request schemas are compiled by ajv to make validation faster. However compilation is slow
and can cause compilation of API to take long time. Enabling this will cause validation schemas
compilation to be executed when the validator is needed.


================================================
FILE: docs/Tutorial.md
================================================
# Exegesis Tutorial

<!-- markdownlint-disable MD007 -->
<!-- TOC depthFrom:2 -->

- [Exegesis Tutorial](#exegesis-tutorial)
  - [Project](#project)
  - [OpenAPI](#openapi)
  - [An Exegesis Server](#an-exegesis-server)
  - [The Controller](#the-controller)
  - [Giving It a Try](#giving-it-a-try)

<!-- /TOC -->
<!-- markdownlint-enable MD007 -->

This is a tutorial which will teach you how to create an OpenAPI 3.0.3 document,
and host it with Exegesis on node.js. You can find complete source for this
tutorial in the [samples](https://github.com/exegesis-js/exegesis/tree/master/samples)
directory, in both JavaScript and TypeScript.

OpenAPI 3.0.3 is the successor to Swagger - version 2.0 was known as the Swagger Specification.
While there are a few choices for implementing OpenAPI 2.0/Swagger on node.js,
Exegesis is the first complete server-framework for implementing version 3.0.X of the spec.

## Project

First, let's create the scaffold of our project:

```sh
mkdir exegesis-tutorial
cd exegesis-tutorial
mkdir controllers
npm init -y
npm install express exegesis-express
```

This creates a project folder named "exegesis-tutorial", a sub-folder
named "controllers" (where we'll put our controller implementations - the code
that gets run when someone accesses our API), creates a package.json file, and
installs the dependencies we'll need (express and exegesis-express).

## OpenAPI

The heart of any OpenAPI-based API is the OpenAPI document, which describes
all the paths and parameters your application accepts. We'll store this
in a file called "openapi.yaml". This is the simplest OpenAPI 3.0.3 document
you can write:

```yaml
openapi: 3.0.3
info:
  title: My API
  version: 1.0.0
paths:
```

The `openapi: 3.0.3` part tells us this is an OpenAPI document, and conforms
to [version 3.0.3](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md)
of the spec. There's a "paths" section where we can list all the paths our API
exposes.

This has all the required fields to be an OpenAPI document, and will pass
validation, but as API documents go, this one is pretty boring. It doesn't
actually do anything. Let's fix that by adding a path:

```yaml
openapi: 3.0.3
info:
  title: My API
  version: 1.0.0
paths:
  '/greet':
    get:
      summary: Greets the user
      operationId: getGreeting
      x-exegesis-controller: greetController
      parameters:
        - description: The name of the user to greet.
          name: name
          in: query
          required: true
          schema:
            type: string
      responses:
        200:
          description: A greeting for the user.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string
        default:
          description: Unexpected error.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string
```

That got big fast! Let's break this down into parts.

We've added a new "/greet" to our "paths" section, with a "get" operation:

```yaml
paths:
  '/greet':
    get:
      summary: Greets the user
      operationId: getGreeting
      x-exegesis-controller: greetController
```

This means clients can send an HTTP GET to "/greet" to run this operation. This
operation also has an `operationId` of "getGreeting". This is a string that
uniquely identifies this operation, across the entire document. No other
operation is allowed to have this operationId.

There's also one extra special part we've added here, the "x-exegesis-controller".
Anything that starts with an "x-" in an OpenAPI document is called a
"specification extension". In this case, we're using an
[Exegesis-specific extension](./OAS3%20Specification%20Extensions.md)
to tell Exegesis what JS module contains the code for this controller.

The get operation also has one parameter, the "name" parameter:

```yaml
parameters:
  - description: The name of the user to greet.
    name: name
    in: query
    required: true
    schema:
      type: string
```

This parameter must be present, and must be a string. Exegesis will generate
a validation error back to the client if this field isn't present, or is the
wrong type.

Finally, there's a responses section:

```yaml
200:
  description: A greeting for the user.
  content:
    application/json:
      schema:
        type: object
        required:
          - message
        properties:
          message:
            type: string
```

This says we can reply with a 200 response, and if we do, the response is going
to be a JSON object with a single property, "message". There's also a "default"
response, which is what the client can expect if our response is not a 200
response. You can list as many different response codes here as you wish.

## An Exegesis Server

Now that we have a simple OpenAPI document, we need to have a server which
implements it. Starting with the existing project, save the above OpenAPI
document as "openapi.yaml". Then create an index.js file. This file is mostly
boilerplate:

```js
const express = require('express');
const exegesisExpress = require('exegesis-express');
const http = require('http');
const path = require('path');

async function createServer() {
  // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
  const options = {
    controllers: path.resolve(__dirname, 'controllers'),
    allowMissingControllers: false,
  };

  // This creates an exegesis middleware, which can be used with express,
  // connect, or even just by itself.
  const exegesisMiddleware = await exegesisExpress.middleware(
    path.resolve(__dirname, './openapi.yaml'),
    options
  );

  const app = express();

  // If you have any body parsers, this should go before them.
  app.use(exegesisMiddleware);

  // Return a 404
  app.use((req, res) => {
    res.status(404).json({ message: `Not found` });
  });

  // Handle any unexpected errors
  app.use((err, req, res, next) => {
    res.status(500).json({ message: `Internal error: ${err.message}` });
  });

  const server = http.createServer(app);

  return server;
}

createServer()
  .then(server => {
    server.listen(3000);
    console.log('Listening on port 3000');
    console.log('Try visiting http://localhost:3000/greet?name=Jason');
  })
  .catch(err => {
    console.error(err.stack);
    process.exit(1);
  });
```

The interesting bit here is really the [options](./Options.md)
we pass to Exegesis:

```js
const options = {
  controllers: path.resolve(__dirname, 'controllers'),
  allowMissingControllers: false,
};
```

`controllers` gives the path to the "controllers" folder, where we store our
controller implementations. `allowMissingControllers: false` tells Exegesis
to throw an error at startup if any of our paths don't have a controller.
There are lots of other handy [options](./Options.md)
you can pass here. Note that if you want to enable response validation,
you must pass the [`onResponseValidationError`](./Options.md#onresponsevalidationerror)
option.

## The Controller

Now we have an OpenAPI document, and we have the boilerplate that starts
the server, but the exciting part is the "controller" - this is the code
that actually implements our OpenAPI document. You may recall in our
OpenAPI document we specified:

```yaml
operationId: getGreeting
x-exegesis-controller: greetController
```

So now were going to use the folder named "controllers" that was created when
you initially created the project. In that folder we're going to create a file
called "greetController.js":

```js
// This function has the same name as an operationId in the OpenAPI document.
exports.getGreeting = function getGreeting(context) {
  const name = context.params.query.name;
  return { message: `Hello ${name}` };
};
```

This controller is pretty simple - is just reads in the name parameter, and
returns a JSON object. Controllers can optionally return a Promise, take a
callback as a second parameter, or even just write a response directly
to `context.res`.

The `context` variable here is an ["Exegesis Context"](./Exegesis%20Controllers.md),
which contains lots of [helpful info](./Exegesis%20Controllers.md#whats-in-a-context)
for when you're writing a controller.

## Giving It a Try

Start the server with:

```sh
node index.js
```

Then, try pointing your browser at [http://localhost:3000/greet?name=Jason](http://localhost:3000/greet?name=Jason),
and you should see a greeting!


================================================
FILE: package.json
================================================
{
    "name": "exegesis",
    "version": "0.0.0-semantic-release",
    "description": "Parses OpenAPI documents",
    "main": "lib/index.js",
    "types": "lib/index.d.ts",
    "files": [
        "lib/**/*"
    ],
    "scripts": {
        "test": "npm run build && npm run lint && npm run test:unittest",
        "test:pre-commit": "pretty-quick --staged && npm run build && npm run lint && npm run test:unittest-pc",
        "build": "tsc",
        "clean": "rm -rf lib coverage",
        "test:unittest": "tsc -p test && nyc mocha 'test/**/*.@(ts|js)'",
        "test:unittest-pc": "tsc -p test && mocha --reporter progress 'test/**/*.@(ts|js)'",
        "lint": "npm run lint:source && npm run lint:tests",
        "lint:source": "eslint --ext .ts src",
        "lint:tests": "eslint --ext .ts test",
        "prepare": "husky install && npm run build",
        "prepublishOnly": "npm run build && npm test",
        "semantic-release": "semantic-release"
    },
    "lint-staged": {
        "(src/test)/**/*.(js|jsx|ts|tsx)": [
            "eslint"
        ]
    },
    "repository": {
        "type": "git",
        "url": "git+https://github.com/exegesis-js/exegesis.git"
    },
    "keywords": [
        "OpenAPI",
        "swagger",
        "OAS3"
    ],
    "author": "Jason Walton",
    "license": "MIT",
    "bugs": {
        "url": "https://github.com/exegesis-js/exegesis/issues"
    },
    "homepage": "https://github.com/exegesis-js/exegesis#readme",
    "devDependencies": {
        "@jwalton/semantic-release-config": "^1.0.0",
        "@types/body-parser": "^1.16.8",
        "@types/chai": "^4.1.7",
        "@types/chai-as-promised": "^7.1.0",
        "@types/content-type": "^1.1.3",
        "@types/deep-freeze": "^0.1.1",
        "@types/json-schema": "^7.0.3",
        "@types/lodash": "^4.14.132",
        "@types/mocha": "^10.0.0",
        "@types/node": "^18.7.23",
        "@types/qs": "^6.5.1",
        "@types/semver": "^7.1.0",
        "@typescript-eslint/eslint-plugin": "^7.3.1",
        "@typescript-eslint/parser": "^7.3.1",
        "chai": "^4.2.0",
        "chai-as-promised": "^7.1.1",
        "eslint": "^8.57.0",
        "husky": "^8.0.1",
        "lint-staged": "^13.0.3",
        "mocha": "^10.1.0",
        "nyc": "^15.1.0",
        "prettier": "^2.0.5",
        "pretty-quick": "^3.0.0",
        "semantic-release": "^21.0.1",
        "supertest-fetch": "^1.2.2",
        "ts-node": "^10.4.0",
        "typescript": "^5.0.3"
    },
    "dependencies": {
        "@apidevtools/json-schema-ref-parser": "^9.0.3",
        "ajv": "^8.3.0",
        "ajv-formats": "^2.1.0",
        "body-parser": "^1.18.3",
        "content-type": "^1.0.4",
        "deep-freeze": "0.0.1",
        "events-listener": "^1.1.0",
        "glob": "^10.3.10",
        "json-ptr": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "lodash": "^4.17.11",
        "openapi3-ts": "^3.1.1",
        "promise-breaker": "^6.0.0",
        "qs": "^6.6.0",
        "raw-body": "^2.3.3",
        "semver": "^7.0.0"
    },
    "engines": {
        "node": ">=10.0.0",
        "npm": ">5.0.0"
    }
}


================================================
FILE: samples/javascript-example/controllers/greetController.js
================================================
exports.getGreeting = function getGreeting(context) {
    const name = context.params.query.name;
    return {message: `Hello ${name}`};
}


================================================
FILE: samples/javascript-example/index.js
================================================
const express = require('express');
const exegesisExpress = require('exegesis-express');
const http = require('http');
const path = require('path');

async function createServer() {
    // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
    const options = {
        controllers: path.resolve(__dirname, './controllers'),
        allowMissingControllers: false
    };

    // This creates an exegesis middleware, which can be used with express,
    // connect, or even just by itself.
    const exegesisMiddleware = await exegesisExpress.middleware(
        path.resolve(__dirname, './openapi.yaml'),
        options
    );

    const app = express();

    // If you have any body parsers, this should go before them.
    app.use(exegesisMiddleware);

    // Return a 404
    app.use((req, res) => {
        res.status(404).json({message: `Not found`});
    });

    // Handle any unexpected errors
    app.use((err, req, res, next) => {
        res.status(500).json({message: `Internal error: ${err.message}`});
    });

    const server = http.createServer(app);

    return server;
}

createServer()
.then(server => {
    server.listen(3000);
    console.log("Listening on port 3000");
    console.log("Try visiting http://localhost:3000/greet?name=Jason");
})
.catch(err => {
    console.error(err.stack);
    process.exit(1);
});


================================================
FILE: samples/javascript-example/openapi.yaml
================================================
openapi: 3.0.1
info:
  title: My API
  version: 1.0.0
paths:
  '/greet':
    get:
      summary: Greets the user
      operationId: getGreeting
      x-exegesis-controller: greetController
      parameters:
        - description: The name of the user to greet.
          name: name
          in: query
          required: true
          schema:
            type: string
      responses:
        200:
          description: A greeting for the user.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string
        default:
          description: Unexpected error.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string


================================================
FILE: samples/javascript-example/package.json
================================================
{
  "name": "exegesis-tutorial",
  "version": "1.0.0",
  "description": "Exegesis tutorial sample",
  "main": "index.js",
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "exegesis-express": "^1.0.0",
    "express": "^4.16.3"
  }
}


================================================
FILE: samples/typescript-example/README.md
================================================
#Exegesis-Typescript-Example

A sample setup of a Typescript based API which uses Exegesis to work with the OpenAPI spec v3.

I made this because on the exegesis repo there is a sample setup however there is only a javascript version available. 

# Usage
Clone the repo and cd into the working directory :

```
git clone https://github.com/Ryan-Gordon/exegesis-typescript-example.git 

```

Then install the dependancies with `npm install`. Yarn is okay too!

Lastly run the API using :  

```
npm run dev
```

Please submit an issue if you have any problems, head over to the [Exegesis Repo](https://github.com/exegesis-js/exegesis) for other examples.

================================================
FILE: samples/typescript-example/controllers/greetController.ts
================================================
/**
 * Here a simple function is exported.
 * 
 * If you wanted a class with its own state and functions you will need something like:
 * 
 * class GreetController {
 * 
 * }
 * export default GreetController;
 */

//Export the getGreeting function to be used by the API.
//All controllers will be imported meaning this exported function will be put into scope.
exports.getGreeting = function getGreeting(context) {
    const name = context.params.query.name;
    return {message: `Hello ${name}`};
}

================================================
FILE: samples/typescript-example/openapi.yaml
================================================
openapi: 3.0.1
info:
  title: My API
  version: 1.0.0
paths:
  '/greet':
    get:
      summary: Greets the user
      operationId: getGreeting
      x-exegesis-controller: greetController
      parameters:
        - description: The name of the user to greet.
          name: name
          in: query
          required: true
          schema:
            type: string
      responses:
        200:
          description: A greeting for the user.
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string
    default:
      description: Unexpected error.
      content:
        application/json:
          schema:
            type: object
            required:
              - message
            properties:
              message:
                type: string

================================================
FILE: samples/typescript-example/package.json
================================================
{
  "name": "exegesis-typescript-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "tsc",
    "dev": "ts-node ./server.ts",
    "start": "nodemon ./dist/server.js",
    "prod": "npm run build && npm run start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/express": "^4.16.0",
    "exegesis-express": "^1.0.0",
    "express": "^4.16.3"
  }
}


================================================
FILE: samples/typescript-example/server.ts
================================================
//Import librarys used
import * as express from 'express';
import * as exegesisExpress from 'exegesis-express';
import * as path from 'path';

//You may choose HTTP or HTTPS, if HTTPS you need a SSL Cert
import * as http from 'http';
import * as https from 'https';

const PORT = 3000;



async function createServer() {
    // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
    const options = {
        controllers: path.resolve(__dirname, './controllers'),
        controllersPattern: "**/*.@(ts|js)"
    };

    // This creates an exegesis middleware, which can be used with express,
    // connect, or even just by itself.
    const exegesisMiddleware = await exegesisExpress.middleware(
        path.resolve(__dirname, './openapi.yaml'),
        options
    );

    const app = express();

    // If you have any body parsers, this should go before them.
    app.use(exegesisMiddleware);

    // Return a 404
    app.use((req, res) => {
        res.status(404).json({message: `Not found`});
    });

    // Handle any unexpected errors
    app.use((err, req, res, next) => {
        res.status(500).json({message: `Internal error: ${err.message}`});
    });
    //Used to create a HTTP Server
    const server = http.createServer(app);

    /**
     * If you want to run a HTTPS server instead you must:
     * + Get a SSL Cert and Key to use
     * + Change the server type from http to https as shown below
     *

    const httpsOptions = {
        key: fs.readFileSync('./config/key.pem'),
        cert: fs.readFileSync('./config/cert.pem')
    }
    const server = https.createServer(httpsOptions,app);

    */
    return server;
}
//Run our createServer function
createServer()
.then(server => {
    server.listen(PORT);
    console.log("Listening on port 3000");
    console.log("Try visiting http://localhost:3000/greet?name=Jason");
})
.catch(err => {
    console.error(err.stack);
    process.exit(1);
});


================================================
FILE: samples/typescript-example/tsconfig.json
================================================
// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "pretty": true,
        "sourceMap": true,
        "target": "es6",
        "outDir": "./dist",
        "baseUrl": "./"
    },
    "include": [
        "lib/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

================================================
FILE: src/bodyParsers/BodyParserWrapper.ts
================================================
import http from 'http';
import contentType from 'content-type';
import getRawBody from 'raw-body';

import { httpHasBody } from '../utils/httpUtils';
import { MimeTypeParser, StringParser, HttpIncomingMessage, Callback } from '../types';

export default class BodyParserWrapper implements MimeTypeParser {
    private _parser: StringParser;
    private _maxBodySize: number;

    constructor(parser: StringParser, maxBodySize: number) {
        this._parser = parser;
        this._maxBodySize = maxBodySize;
    }

    parseString(value: string) {
        return this._parser.parseString(value);
    }

    parseReq(req: HttpIncomingMessage, _res: http.ServerResponse, done: Callback<any>): void {
        if (req.body) {
            // Already parsed;
            return done();
        }

        // Make sure we have a body to parse
        if (!httpHasBody(req.headers)) {
            return done();
        }

        // Work out the encoding
        let encoding = 'utf-8';
        const parsedContentType = contentType.parse(req);
        if (parsedContentType && parsedContentType.parameters) {
            encoding = (parsedContentType.parameters.encoding || encoding).toLowerCase();
        }

        // Read the body
        getRawBody(req, { limit: this._maxBodySize, encoding }, (err, str) => {
            if (err) {
                return done(err);
            }

            req.body = this._parser.parseString(str);
            done(null, req.body);
        });
    }
}


================================================
FILE: src/bodyParsers/JsonBodyParser.ts
================================================
import http from 'http';
import expressBodyParser from 'body-parser';

import { MimeTypeParser, ReqParserFunction, Callback } from '../types';

export default class JsonBodyParser implements MimeTypeParser {
    private _bodyParserMiddlware: ReqParserFunction;

    constructor(maxBodySize: number) {
        // FIXME: https://github.com/expressjs/body-parser/issues/304
        this._bodyParserMiddlware = expressBodyParser.json({
            inflate: true,
            limit: maxBodySize,
            type: '*/*',
        }) as ReqParserFunction;
    }

    parseString(value: string) {
        return JSON.parse(value);
    }

    parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Callback<void>): void {
        this._bodyParserMiddlware(req, res, done);
    }
}


================================================
FILE: src/bodyParsers/TextBodyParser.ts
================================================
import http from 'http';
import expressBodyParser from 'body-parser';

import { MimeTypeParser, ReqParserFunction, Callback } from '../types';

export default class TextBodyParser implements MimeTypeParser {
    private _bodyParserMiddlware: ReqParserFunction;

    constructor(maxBodySize: number) {
        // FIXME: https://github.com/expressjs/body-parser/issues/304
        this._bodyParserMiddlware = expressBodyParser.text({
            inflate: true,
            limit: maxBodySize,
            type: '*/*',
        }) as ReqParserFunction;
    }

    parseString(value: string) {
        return value;
    }

    parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Callback<void>): void {
        this._bodyParserMiddlware(req, res, done);
    }
}


================================================
FILE: src/controllers/invoke.ts
================================================
import pb from 'promise-breaker';
import { Controller, ControllerModule, ExegesisContext } from '../types';
import { isReadable } from '../utils/typeUtils';

export function invokeController(
    controllerModule: ControllerModule | undefined,
    controller: Controller,
    context: ExegesisContext
): Promise<any> {
    return pb.apply(controller, controllerModule, [context]).then((result) => {
        if (!context.res.ended) {
            if (result === undefined || result === null) {
                context.res.end();
            } else if (
                typeof result === 'string' ||
                result instanceof Buffer ||
                isReadable(result)
            ) {
                context.res.setBody(result);
            } else if (context.options.treatReturnedJsonAsPure) {
                context.res.pureJson(result);
            } else {
                context.res.json(result);
            }
        }

        return result;
    });
}


================================================
FILE: src/controllers/loadControllers.ts
================================================
import fs from 'fs';
import * as glob from 'glob';
import path from 'path';

import { Controllers, ControllerModule } from '../types';

/**
 * Load a set of controllers.
 *
 * @param folder - The folder to load controllers from.
 * @param [pattern] - A glob pattern for controllers to load.  Defaults to only
 *   .js files.
 * @param [loader] - The function to call to load each controller.  Defaults to
 *   `require`.
 *
 * @example
 *   // Assuming controllers has files "foo.js" and "bar/bar.js", then `controllers`
 *   // will be a `{"foo", "foo.js", "bar/bar.js", "bar/bar"}` object.
 *   const controllers = loadControllersSync('controlers', '**\/*.js');
 */
export function loadControllersSync(
    folder: string,
    pattern: string = '**/*.js',
    loader: (path: string) => ControllerModule = require
): Controllers {
    const controllerNames = glob.sync(pattern, { cwd: folder });

    return controllerNames.reduce<Controllers>((result, controllerName) => {
        const fullPath = path.resolve(folder, controllerName);
        if (fs.statSync(fullPath).isDirectory()) {
            // Skip directories.
            return result;
        }
        try {
            // Add the file at the full path
            const mod = loader(fullPath);
            result[controllerName] = mod;

            // Add the file at the full path, minus the extension
            const ext = path.extname(controllerName);
            result[controllerName.slice(0, -ext.length)] = mod;

            // If the file is an "index" file, then add it at the folder
            // name (unless there's already something there.)
            const basename = path.basename(controllerName, ext);
            if (basename === 'index') {
                const indexFolder = controllerName.slice(0, -(ext.length + basename.length + 1));
                result[indexFolder] = result[indexFolder] || mod;
            }
        } catch (err) {
            throw new Error(`Could not load controller '${fullPath}': ${err}`);
        }
        return result;
    }, {});
}


================================================
FILE: src/core/ExegesisContextImpl.ts
================================================
import * as http from 'http';
import pb from 'promise-breaker';
import deepFreeze from 'deep-freeze';
import {
    ParametersByLocation,
    ParametersMap,
    ExegesisContext,
    AuthenticationSuccess,
    HttpIncomingMessage,
    ExegesisPluginContext,
    Callback,
    ParameterLocations,
    ParameterLocation,
    ExegesisOptions,
    ResolvedOperation,
    ExegesisRoute,
} from '../types';
import ExegesisResponseImpl from './ExegesisResponseImpl';
import { HttpError, ValidationError } from '../errors';

const EMPTY_PARAMS = deepFreeze({
    query: Object.create(null),
    header: Object.create(null),
    server: Object.create(null),
    path: Object.create(null),
    cookie: Object.create(null),
});

const EMPTY_PARAM_LOCATIONS: ParameterLocations = deepFreeze<ParameterLocations>({
    query: Object.create(null),
    header: Object.create(null),
    path: Object.create(null),
    cookie: Object.create(null),
});

const EMPTY_ROUTE = deepFreeze({
    path: '',
});

export default class ExegesisContextImpl<T> implements ExegesisContext, ExegesisPluginContext {
    readonly req: HttpIncomingMessage;
    readonly origRes: http.ServerResponse;
    readonly res: ExegesisResponseImpl;
    readonly options: ExegesisOptions;
    params: ParametersByLocation<ParametersMap<any>>;
    requestBody: any;
    security?: { [scheme: string]: AuthenticationSuccess };
    user: any | undefined;
    api: T;
    parameterLocations: ParameterLocations = EMPTY_PARAM_LOCATIONS;
    route: ExegesisRoute = EMPTY_ROUTE;
    baseUrl: string = '';

    private _operation: ResolvedOperation | undefined;
    private _paramsResolved: boolean = false;
    private _bodyResolved: boolean = false;

    constructor(
        req: http.IncomingMessage, // http2.Http2ServerRequest,
        res: http.ServerResponse, // http2.Http2ServerResponse,
        api: T,
        options: ExegesisOptions
    ) {
        const responseValidationEnabled = !!options.onResponseValidationError;
        this.req = req as HttpIncomingMessage;
        this.origRes = res;
        this.res = new ExegesisResponseImpl(res, responseValidationEnabled);
        this.api = api;
        this.options = options;

        // Temporarily set params to EMPTY_PARAMS.  While we're being a
        // 'plugin context', this will be empty, but it will be filled in
        // before we get to the controllers.
        this.params = EMPTY_PARAMS;
    }

    _setOperation(baseUrl: string, path: string, operation: ResolvedOperation) {
        this.baseUrl = baseUrl;
        this.route = { path };
        this._operation = operation;
        this.parameterLocations = operation.parameterLocations;

        // Set `req.baseUrl` and `req.path` to make this behave like Express.
        const req = this.req as any;
        if (req.baseUrl) {
            req.baseUrl = `${req.baseUrl}${baseUrl}`;
        } else {
            req.baseUrl = baseUrl;
        }
        req.route = { path };
    }

    makeError(statusCode: number, message: string): HttpError {
        return new HttpError(statusCode, message);
    }

    makeValidationError(message: string, parameterLocation: ParameterLocation) {
        return new ValidationError([{ message, location: parameterLocation }]);
    }

    /**
     * Returns true if the response has already been sent.
     */
    isResponseFinished() {
        return this.res.ended || this.origRes.headersSent;
    }

    getParams(): Promise<ParametersByLocation<ParametersMap<any>>>;
    getParams(done: Callback<ParametersByLocation<ParametersMap<any>>>): void;
    getParams(done?: Callback<any>): Promise<ParametersByLocation<ParametersMap<any>>> | void {
        return pb.addCallback(done, () => {
            if (!this._paramsResolved) {
                if (!this._operation) {
                    throw new Error('Cannot get parameters - no resolved operation.');
                }
                this.params = this._operation.parseParameters();
                const errors = this._operation.validateParameters(this.params);
                if (errors && errors.length > 0) {
                    const err = new ValidationError(errors);
                    throw err;
                }
                this._paramsResolved = true;
            }
            return this.params;
        });
    }

    getRequestBody(): Promise<any>;
    getRequestBody(done: Callback<any>): void;
    getRequestBody(done?: Callback<any>): Promise<any> | void {
        return pb.addCallback(done, async () => {
            if (!this._operation) {
                throw new Error('Cannot get parameters - no resolved operation.');
            }

            if (!this._bodyResolved) {
                let body: any;

                // Parse the body.
                if (this._operation.bodyParser) {
                    const bodyParser = this._operation.bodyParser;
                    body = await pb.call((done: Callback<void>) =>
                        bodyParser.parseReq(this.req, this.origRes, done)
                    );
                    body = body || this.req.body;
                }

                // Validate the body.  We need to validate the body even if we
                // didn't parse a body, since this is where we check if the
                // body is required.
                if (this._operation.validateBody) {
                    const validationResult = this._operation.validateBody(body);
                    if (validationResult.errors && validationResult.errors.length > 0) {
                        throw new ValidationError(validationResult.errors);
                    }

                    body = validationResult.value;
                }

                // Assign the body to the appropriate places
                this.requestBody = this.req.body = body;
                this._bodyResolved = true;
            }
            return this.requestBody;
        });
    }
}


================================================
FILE: src/core/ExegesisResponseImpl.ts
================================================
import * as http from 'http';
import * as net from 'net';
import * as types from '../types';
import { HttpHeaders } from '../types';

export default class ExegesisResponseImpl implements types.ExegesisResponse {
    private _body: any = undefined;
    _afterController: boolean = false;

    statusCode: number = 200;
    statusMessage: string | undefined = undefined;
    headers: types.HttpHeaders = Object.create(null);
    ended: boolean = false;
    connection: net.Socket;
    socket: net.Socket;
    headersSent: boolean = false;
    private _responseValidationEnabled: boolean;

    constructor(
        res: http.ServerResponse /* | http2.Http2ServerResponse */,
        responseValidationEnabled: boolean
    ) {
        if (!res.socket) {
            throw new Error('Response is already ended');
        }
        this.connection = this.socket = res.socket;
        this._responseValidationEnabled = responseValidationEnabled;
    }

    setStatus(status: number) {
        if (this.ended) {
            throw new Error('Trying to set status after response has been ended.');
        }
        this.statusCode = status;
        return this;
    }

    status(status: number) {
        return this.setStatus(status);
    }

    header(header: string, value: number | string | string[]) {
        this.setHeader(header, value);
        return this;
    }

    set(header: string, value: number | string | string[]) {
        this.setHeader(header, value);
        return this;
    }

    json(json: any) {
        this.set('content-type', 'application/json');
        if (this._responseValidationEnabled) {
            // Must stringify here, since the object or any of it's
            // nested values could have a toJSON().  Note this means
            // we'll have to parse it again when we do validation.
            this.setBody(JSON.stringify(json));
        } else {
            this.setBody(json);
        }
        return this;
    }

    pureJson(json: any) {
        this.set('content-type', 'application/json').setBody(json);
        return this;
    }

    setBody(body: any): this {
        if (this.ended && !this._afterController) {
            throw new Error('Trying to set body after response has been ended.');
        }
        this.body = body;
        return this;
    }

    set body(body: any) {
        this._body = body;
        this.end();
    }

    get body(): any {
        return this._body;
    }

    end() {
        this.headersSent = true;
        this.ended = true;
    }

    redirect(status: number, url: string): this;
    redirect(url: string): this;
    redirect(a: number | string, b?: string): this {
        if (typeof a === 'string' && !b) {
            this.writeHead(302, { Location: a });
        } else if (typeof a === 'number' && typeof b === 'string') {
            this.writeHead(a, { Location: b });
        } else {
            throw new Error('Invalid arguments to redirect');
        }
        this.end();

        return this;
    }

    setHeader(name: string, value: number | string | string[]) {
        if (this.ended && !this._afterController) {
            throw new Error('Trying to set header after response has been ended.');
        }
        this.headers[name.toLowerCase()] = value;
    }

    getHeader(name: string) {
        return this.headers[name];
    }

    getHeaderNames() {
        return Object.keys(this.headers);
    }

    getHeaders() {
        return Object.assign({}, this.headers);
    }

    hasHeader(name: string) {
        return !!this.headers[name];
    }

    removeHeader(name: string) {
        if (this.ended && !this._afterController) {
            throw new Error('Trying to remove header after response has been ended.');
        }
        delete this.headers[name];
    }

    writeHead(statusCode: number, statusMessage?: string | HttpHeaders, headers?: HttpHeaders) {
        if (statusMessage && typeof statusMessage !== 'string') {
            headers = statusMessage;
            statusMessage = undefined;
        }
        this.statusCode = statusCode;

        if (headers) {
            for (const headerName of Object.keys(headers)) {
                this.setHeader(headerName, headers[headerName]);
            }
        }
        this.headersSent = true;
    }
}


================================================
FILE: src/core/PluginsManager.ts
================================================
import * as exegesis from '../types';
import pb from 'promise-breaker';
import http from 'http';

function callFn(
    plugin: exegesis.ExegesisPluginInstance,
    fnName: keyof exegesis.ExegesisPluginInstance,
    param: any
) {
    const fnLength = (plugin as any)[fnName].length;
    if (fnLength < 2) {
        return (plugin as any)[fnName](param);
    } else {
        pb.call((done: exegesis.Callback<void>) => (plugin as any)[fnName](param, done));
    }
}

export default class PluginsManager {
    private readonly _plugins: exegesis.ExegesisPluginInstance[];
    private readonly _preRoutingPlugins: exegesis.ExegesisPluginInstance[];
    private readonly _postRoutingPlugins: exegesis.ExegesisPluginInstance[];
    private readonly _postSecurityPlugins: exegesis.ExegesisPluginInstance[];
    private readonly _postControllerPlugins: exegesis.ExegesisPluginInstance[];
    private readonly _postResponseValidation: exegesis.ExegesisPluginInstance[];

    constructor(apiDoc: any, plugins: exegesis.ExegesisPlugin[]) {
        this._plugins = plugins.map((plugin) => plugin.makeExegesisPlugin({ apiDoc }));

        this._preRoutingPlugins = this._plugins.filter((p) => !!p.preRouting);
        this._postRoutingPlugins = this._plugins.filter((p) => !!p.postRouting);
        this._postSecurityPlugins = this._plugins.filter((p) => !!p.postSecurity);
        this._postControllerPlugins = this._plugins.filter((p) => !!p.postController);
        this._postResponseValidation = this._plugins.filter((p) => !!p.postResponseValidation);
    }

    async preCompile(data: { apiDoc: any; options: exegesis.ExegesisOptions }) {
        for (const plugin of this._plugins) {
            if (plugin.preCompile) {
                await callFn(plugin, 'preCompile', data);
            }
        }
    }

    async preRouting(data: { req: http.IncomingMessage; res: http.ServerResponse }) {
        for (const plugin of this._preRoutingPlugins) {
            await callFn(plugin, 'preRouting', data);
        }
    }

    async postRouting(pluginContext: exegesis.ExegesisPluginContext) {
        for (const plugin of this._postRoutingPlugins) {
            await callFn(plugin, 'postRouting', pluginContext);
        }
    }

    async postSecurity(pluginContext: exegesis.ExegesisPluginContext) {
        for (const plugin of this._postSecurityPlugins) {
            await callFn(plugin, 'postSecurity', pluginContext);
        }
    }

    async postController(context: exegesis.ExegesisContext) {
        for (const plugin of this._postControllerPlugins) {
            await callFn(plugin, 'postController', context);
        }
    }

    async postResponseValidation(context: exegesis.ExegesisContext) {
        for (const plugin of this._postResponseValidation) {
            await callFn(plugin, 'postResponseValidation', context);
        }
    }
}


================================================
FILE: src/core/exegesisRunner.ts
================================================
import * as http from 'http';
import { Readable } from 'stream';
import { asError, HttpError } from '../errors';

import { invokeController } from '../controllers/invoke';
import stringToStream from '../utils/stringToStream';
import { ValidationError } from '../errors';
import bufferToStream from '../utils/bufferToStream';
import { isReadable } from '../utils/typeUtils';
import {
    ApiInterface,
    ExegesisRunner,
    HttpResult,
    ExegesisContext,
    ResponseValidationCallback,
    ResolvedOperation,
    ExegesisOptions,
    ExegesisResponse,
} from '../types';
import ExegesisContextImpl from './ExegesisContextImpl';
import PluginsManager from './PluginsManager';
import { IValidationError } from '../types/validation';
import { HandleErrorFunction } from '../types/options';

async function handleSecurity(operation: ResolvedOperation, context: ExegesisContext) {
    const authenticated = await operation.authenticate(context);
    context.security = authenticated;
    if (authenticated) {
        const matchedSchemes = Object.keys(authenticated);
        if (matchedSchemes.length === 1) {
            context.user = authenticated[matchedSchemes[0]].user;
        }
    }
}

function setDefaultContentType(res: ExegesisResponse) {
    const body = res.body;
    if (res.headers['content-type']) {
        // Nothing to do!
    } else if (body === undefined || body === null) {
        // Do nothing
    } else if (body instanceof Buffer) {
        res.headers['content-type'] = 'text/plain';
    } else if (typeof body === 'string') {
        res.headers['content-type'] = 'text/plain';
    } else if (isReadable(body)) {
        res.headers['content-type'] = 'text/plain';
    } else {
        res.headers['content-type'] = 'application/json';
    }
}

function resultToHttpResponse(context: ExegesisContext, result: any): HttpResult {
    let output: Readable | undefined;
    const headers = context.res.headers;

    if (result) {
        if (result instanceof Buffer) {
            output = bufferToStream(result);
        } else if (typeof result === 'string') {
            output = stringToStream(result);
        } else if (isReadable(result)) {
            output = result;
        } else {
            if (!headers['content-type']) {
                headers['content-type'] = 'application/json';
            }
            output = stringToStream(JSON.stringify(result), 'utf-8');
        }
    }

    return {
        status: context.res.statusCode,
        headers,
        body: output,
    };
}

function handleError(err: Error) {
    if (err instanceof ValidationError) {
        // TODO: Allow customization of validation error?  Or even
        // just throw the error instead of turning it into a message?
        const jsonError = {
            message: 'Validation errors',
            errors: err.errors.map((error: IValidationError) => {
                return {
                    message: error.message,
                    location: error.location,
                };
            }),
        };
        return {
            status: err.status,
            headers: { 'content-type': 'application/json' },
            body: stringToStream(JSON.stringify(jsonError), 'utf-8'),
        };
    } else if (Number.isInteger((err as any).status)) {
        return {
            status: (err as any).status,
            headers: (err as any).headers || { 'content-type': 'application/json' },
            body: stringToStream(JSON.stringify({ message: err.message }), 'utf-8'),
        };
    } else {
        throw err;
    }
}

/**
 * Returns a `(req, res) => Promise<boolean>` function, which handles incoming
 * HTTP requests.  The returned function will return true if the request was
 * handled, and false otherwise.
 *
 * @returns runner function.
 */
export default async function generateExegesisRunner<T>(
    api: ApiInterface<T>,
    options: {
        autoHandleHttpErrors: boolean | HandleErrorFunction;
        plugins: PluginsManager;
        onResponseValidationError?: ResponseValidationCallback;
        validateDefaultResponses: boolean;
        originalOptions: ExegesisOptions;
    }
): Promise<ExegesisRunner> {
    const plugins = options.plugins;

    return async function exegesisRunner(
        req: http.IncomingMessage,
        res: http.ServerResponse
    ): Promise<HttpResult | undefined> {
        const method = req.method || 'get';
        const url = req.url || '/';

        let result: HttpResult | undefined;

        try {
            await plugins.preRouting({ req, res });

            const resolved = api.resolve(method, url, req.headers);
            if (!resolved) {
                return result;
            }

            if (!resolved.operation) {
                const error: any = new Error(`Method ${method} not allowed for ${url}`);
                error.status = 405;
                error.headers = {
                    allow: resolved.allowedMethods.join(',').toUpperCase(),
                    'content-type': 'application/json',
                };

                return handleError(error);
            }

            const context = new ExegesisContextImpl<T>(
                req,
                res,
                resolved.api,
                options.originalOptions
            );

            if (!context.isResponseFinished()) {
                await plugins.postRouting(context);
            }

            const { operation } = resolved;

            context._setOperation(resolved.baseUrl, resolved.path, operation);

            if (!operation.controller) {
                throw new Error(`No controller found for ${method} ${url}`);
            }

            await handleSecurity(operation, context);

            if (!context.isResponseFinished()) {
                await plugins.postSecurity(context);
            }

            if (!context.isResponseFinished()) {
                // Fill in context.params and context.requestBody.
                await context.getParams();
                await context.getRequestBody();
            }

            if (!context.isResponseFinished()) {
                await invokeController(operation.controllerModule, operation.controller, context);
            }

            if (!context.origRes.headersSent) {
                // Set _afterController to allow postController() plugins to
                // modify the response.
                context.res._afterController = true;
                await plugins.postController(context);
            }

            if (!context.origRes.headersSent) {
                // Before response validation, if there is a body and no
                // content-type has been set, set a reasonable default.
                setDefaultContentType(context.res);

                if (options.onResponseValidationError) {
                    const responseValidationResult = resolved.operation.validateResponse(
                        context.res,
                        options.validateDefaultResponses
                    );
                    try {
                        if (
                            responseValidationResult.errors &&
                            responseValidationResult.errors.length
                        ) {
                            options.onResponseValidationError({
                                errors: responseValidationResult.errors,
                                isDefault: responseValidationResult.isDefault,
                                context,
                            });
                        }
                    } catch (e) {
                        const err = asError(e) as HttpError;
                        (err as any).status = err.status || 500;
                        throw err;
                    }
                }
                await plugins.postResponseValidation(context);
            }

            if (!context.origRes.headersSent) {
                result = resultToHttpResponse(context, context.res.body);
            }

            return result;
        } catch (e) {
            const err = asError(e);

            if (options.autoHandleHttpErrors) {
                if (options.autoHandleHttpErrors instanceof Function) {
                    return options.autoHandleHttpErrors(err, { req });
                }
                return handleError(err);
            } else {
                throw err;
            }
        }
    };
}


================================================
FILE: src/errors.ts
================================================
import { IValidationError } from './types';

export class ExtendableError extends Error {
    constructor(message: string) {
        super(message);
        this.name = this.constructor.name;
        if (typeof Error.captureStackTrace === 'function') {
            Error.captureStackTrace(this, this.constructor);
        } else {
            this.stack = new Error(message).stack;
        }
    }
}

export class HttpError extends ExtendableError {
    readonly status: number;

    constructor(status: number, message: string) {
        super(message);
        this.status = status;
    }
}

export class HttpBadRequestError extends HttpError {
    constructor(message: string) {
        super(400, message);
    }
}

export class ValidationError extends HttpBadRequestError {
    errors: IValidationError[];

    constructor(errors: IValidationError[] | IValidationError) {
        if (!Array.isArray(errors)) {
            errors = [errors];
        }
        super(errors.length === 1 ? errors[0].message : 'Multiple validation errors');
        this.errors = errors;
    }
}

export class HttpNotFoundError extends HttpError {
    constructor(message: string) {
        super(404, message);
    }
}

export class HttpPayloadTooLargeError extends HttpError {
    constructor(message: string) {
        super(413, message);
    }
}

/**
 * Ensures the passed in `err` is of type Error.
 */
export function asError(err: any): Error {
    if (err instanceof Error) {
        return err;
    } else {
        const newErr = new Error(err);
        if (err.status) {
            (newErr as any).status = err.status;
        }
        return newErr;
    }
}


================================================
FILE: src/index.ts
================================================
import * as http from 'http';
import * as oas3 from 'openapi3-ts';
import pb from 'promise-breaker';
import { pipeline } from 'stream';
import $RefParser from '@apidevtools/json-schema-ref-parser';

import { compileOptions } from './options';
import { compile as compileOpenApi } from './oas3';
import generateExegesisRunner from './core/exegesisRunner';
import {
    ApiInterface,
    ExegesisOptions,
    Callback,
    ExegesisRunner,
    HttpResult,
    HttpIncomingMessage,
    MiddlewareFunction,
    OAS3ApiInfo,
} from './types';
export { HttpError, ValidationError } from './errors';
import { OpenAPIObject } from 'openapi3-ts';
import PluginsManager from './core/PluginsManager';

// Export all our public types.
export * from './types';

/**
 * Reads a JSON or YAML file and bundles all $refs, resulting in a single
 * document with only internal refs.
 *
 * @param openApiDocFile - The file containing the document, or a JSON object.
 * @returns - Returns the bundled document
 */
function bundle(openApiDocFile: string | unknown): Promise<any> {
    const refParser = new $RefParser();

    return refParser.bundle(openApiDocFile as any, { dereference: { circular: false } });
}

async function compileDependencies(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions
) {
    const compiledOptions = compileOptions(options);
    const bundledDoc = await bundle(openApiDoc);

    const plugins = new PluginsManager(bundledDoc, (options || {}).plugins || []);

    await plugins.preCompile({ apiDoc: bundledDoc, options });

    const apiInterface = await compileOpenApi(bundledDoc as OpenAPIObject, compiledOptions);

    return { compiledOptions, apiInterface, plugins };
}

/**
 * Compiles an API interface for the given openApiDoc using the options.
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param options - Options.  See docs/options.md
 * @returns - a Promise which returns the compiled API interface
 */
export function compileApiInterface(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions
): Promise<ApiInterface<OAS3ApiInfo>>;

/**
 * Compiles an API interface for the given openApiDoc using the options.
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param options - Options.  See docs/options.md
 * @param done Callback which returns the compiled API interface
 */
export function compileApiInterface(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions,
    done: Callback<ApiInterface<OAS3ApiInfo>>
): void;

export function compileApiInterface(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions,
    done?: Callback<ApiInterface<OAS3ApiInfo>>
): Promise<ApiInterface<OAS3ApiInfo>> {
    return pb.addCallback(done, async () => {
        return (await compileDependencies(openApiDoc, options)).apiInterface;
    });
}

/**
 * Returns a "runner" function - call `runner(req, res)` to get back a
 * `HttpResult` object.
 *
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param [options] - Options.  See docs/options.md
 * @returns - a Promise<ExegesisRunner>.  ExegesisRunner is a
 *   `function(req, res)` which will handle an API call, and return an
 *   `HttpResult`, or `undefined` if the request could not be handled.
 */
export function compileRunner(
    openApiDoc: string | oas3.OpenAPIObject,
    options?: ExegesisOptions
): Promise<ExegesisRunner>;

/**
 * Returns a "runner" function - call `runner(req, res)` to get back a
 * `HttpResult` object.
 *
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param options - Options.  See docs/options.md
 * @param done - Callback which retunrs an ExegesisRunner.  ExegesisRunner is a
 *   `function(req, res)` which will handle an API call, and return an
 *   `HttpResult`, or `undefined` if the request could not be handled.
 */
export function compileRunner(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions | undefined,
    done: Callback<ExegesisRunner>
): void;

export function compileRunner(
    openApiDoc: string | oas3.OpenAPIObject,
    options?: ExegesisOptions,
    done?: Callback<ExegesisRunner>
): Promise<ExegesisRunner> {
    return pb.addCallback(done, async () => {
        options = options || {};
        const { compiledOptions, apiInterface, plugins } = await compileDependencies(
            openApiDoc,
            options
        );
        return generateExegesisRunner(apiInterface, {
            autoHandleHttpErrors: compiledOptions.autoHandleHttpErrors,
            plugins,
            onResponseValidationError: compiledOptions.onResponseValidationError,
            validateDefaultResponses: compiledOptions.validateDefaultResponses,
            originalOptions: options,
        });
    });
}

/**
 * Convenience function which writes an `HttpResult` obtained from an
 * ExegesisRunner out to an HTTP response.
 *
 * @param httpResult - Result to write.
 * @param res - The response to write to.
 * @returns - a Promise which resolves on completion.
 */
export function writeHttpResult(httpResult: HttpResult, res: http.ServerResponse): Promise<void>;

/**
 * Convenience function which writes an `HttpResult` obtained from an
 * ExegesisRunner out to an HTTP response.
 *
 * @param httpResult - Result to write.
 * @param res - The response to write to.
 * @param callback - Callback to call on completetion.
 */
export function writeHttpResult(
    httpResult: HttpResult,
    res: http.ServerResponse,
    done: Callback<void>
): void;

export function writeHttpResult(
    httpResult: HttpResult,
    res: http.ServerResponse,
    done?: Callback<void>
): Promise<void> {
    return pb.addCallback(done, async () => {
        Object.keys(httpResult.headers).forEach((header) =>
            res.setHeader(header, httpResult.headers[header])
        );
        res.statusCode = httpResult.status;

        if (httpResult.body) {
            const body = httpResult.body;
            await pb.call((done2: (err: NodeJS.ErrnoException | null) => void) =>
                pipeline(body, res, done2)
            );
        } else {
            res.end();
        }
    });
}

/**
 * Returns a connect/express middleware function which implements the API.
 *
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param [options] - Options.  See docs/options.md
 * @returns - a Promise<MiddlewareFunction>.
 */
export function compileApi(
    openApiDoc: string | oas3.OpenAPIObject,
    options?: ExegesisOptions
): Promise<MiddlewareFunction>;

/**
 * Returns a connect/express middleware function which implements the API.
 *
 * @param openApiDoc - A string, representing a path to the OpenAPI document,
 *   or a JSON object.
 * @param options - Options.  See docs/options.md
 * @param done - callback which returns the MiddlewareFunction.
 */
export function compileApi(
    openApiDoc: string | oas3.OpenAPIObject,
    options: ExegesisOptions | undefined,
    done: Callback<MiddlewareFunction>
): void;

export function compileApi(
    openApiDoc: string | oas3.OpenAPIObject,
    options?: ExegesisOptions | undefined,
    done?: Callback<MiddlewareFunction> | undefined
): Promise<MiddlewareFunction> {
    return pb.addCallback(done, async () => {
        const runner = await compileRunner(openApiDoc, options);

        return function exegesisMiddleware(
            req: HttpIncomingMessage,
            res: http.ServerResponse,
            next: Callback<void>
        ) {
            runner(req, res)
                .then((result) => {
                    let answer: Promise<void> | undefined;

                    if (!result) {
                        if (next) {
                            next();
                        }
                    } else if (res.headersSent) {
                        // Someone else has already written a response.  :(
                    } else if (result) {
                        answer = writeHttpResult(result, res);
                    } else {
                        if (next) {
                            next();
                        }
                    }
                    return answer;
                })
                .catch((err) => {
                    if (next) {
                        next(err);
                    } else {
                        res.statusCode = err.status || 500;
                        res.end('error');
                    }
                });
        };
    });
}


================================================
FILE: src/oas3/Oas3CompileContext.ts
================================================
import * as ld from 'lodash';

import * as oas3 from 'openapi3-ts';
import * as jsonPtr from 'json-ptr';
import { resolveRef } from '../utils/json-schema-resolve-ref';

import { ExegesisCompiledOptions } from '../options';

/**
 * A path to an object within a JSON document.
 */
export type JsonPath = string[];
export type ReadOnlyJsonPath = readonly string[];

/**
 * This has common stuff that we want to pass all the way down through the OAS
 * heirarchy.  This also keeps track of the `path` that a given object was
 * generated from.
 */
export default class Oas3CompileContext {
    readonly path: JsonPath;
    readonly jsonPointer: string;
    readonly openApiDoc: oas3.OpenAPIObject;
    readonly options: ExegesisCompiledOptions;

    /**
     * Create a new Oas3CompileContext.
     *
     * @param openApiDoc - A fully resolved OpenAPI document, with no $refs.
     * @param path - The path to the object represented by this context.
     * @param options - Options.
     */
    constructor(
        openApiDoc: oas3.OpenAPIObject,
        path: ReadOnlyJsonPath,
        options: ExegesisCompiledOptions
    );
    constructor(parent: Oas3CompileContext, relativePath: ReadOnlyJsonPath);
    constructor(a: any, path: ReadOnlyJsonPath, options?: ExegesisCompiledOptions) {
        if (a instanceof Oas3CompileContext) {
            // TODO: Could make this WAY more efficient with Object.create().
            const parent = a;
            this.path = parent.path.concat(path);
            this.openApiDoc = parent.openApiDoc;
            this.options = parent.options;
        } else if (options) {
            this.path = path.slice();
            this.openApiDoc = a;
            this.options = options;
        } else {
            throw new Error('Invalid parameters to Oas3CompileContext constructor');
        }
        this.jsonPointer = jsonPtr.encodePointer(this.path);
    }

    childContext(relativePath: JsonPath | string) {
        if (ld.isArray(relativePath)) {
            return new Oas3CompileContext(this, relativePath);
        } else {
            return new Oas3CompileContext(this, [relativePath]);
        }
    }

    resolveRef(ref: string | any) {
        return resolveRef(this.openApiDoc, ref);
    }
}


================================================
FILE: src/oas3/OpenApi.ts
================================================
import { parse as parseUrl } from 'url';
import * as semver from 'semver';
import * as http from 'http';
import * as oas3 from 'openapi3-ts';

import { ExegesisCompiledOptions } from '../options';
import {
    ApiInterface,
    ResolvedPath,
    ParsedParameterValidator,
    ResolvedOperation,
    ParametersMap,
    OAS3ApiInfo,
    ExegesisContext,
    AuthenticationSuccess,
    ExegesisResponse,
} from '../types';
import Paths from './Paths';
import Servers from './Servers';
import Oas3CompileContext from './Oas3CompileContext';
import { EXEGESIS_CONTROLLER, EXEGESIS_OPERATION_ID } from './extensions';
import RequestMediaType from './RequestMediaType';
import { HttpBadRequestError } from '../errors';
import { httpHasBody, requestMayHaveBody } from '../utils/httpUtils';
import { HTTP_METHODS } from './Path';

export default class OpenApi implements ApiInterface<OAS3ApiInfo> {
    readonly openApiDoc: oas3.OpenAPIObject;
    private readonly _options: ExegesisCompiledOptions;
    private _servers?: Servers;
    private _paths: Paths;

    /**
     * Creates a new OpenApi object.
     *
     * @param openApiDoc - The complete JSON definition of the API.
     *   The passed in definition should be a complete JSON object with no $refs.
     */
    constructor(openApiDoc: oas3.OpenAPIObject, options: ExegesisCompiledOptions) {
        if (!openApiDoc.openapi) {
            throw new Error("OpenAPI definition is missing 'openapi' field");
        }
        if (!semver.satisfies(openApiDoc.openapi, '>=3.0.0 <4.0.0')) {
            throw new Error(`OpenAPI version ${openApiDoc.openapi} not supported`);
        }

        this.openApiDoc = openApiDoc;
        this._options = options;

        // TODO: Optimize this case when no `servers` were present in openApi doc,
        // or where we don't need to match servers (only server is {url: '/'})?
        if (!options.ignoreServers && openApiDoc.servers) {
            this._servers = new Servers(openApiDoc.servers);
        }

        const exegesisController = openApiDoc[EXEGESIS_CONTROLLER];

        this._paths = new Paths(
            new Oas3CompileContext(openApiDoc, ['paths'], options),
            exegesisController
        );
    }

    resolve(
        method: string,
        url: string,
        headers: http.IncomingHttpHeaders
    ): ResolvedPath<OAS3ApiInfo> | undefined {
        const parsedUrl = parseUrl(url);
        const pathname = parsedUrl.pathname || '';
        const host = parsedUrl.hostname || headers['host'] || '';
        const contentType = headers['content-type'];

        let pathToResolve: string | undefined;
        let oaServer: oas3.ServerObject | undefined;
        let serverParams: ParametersMap<string | string[]> | undefined;
        let baseUrl = '';

        if (!this._servers) {
            pathToResolve = pathname;
        } else {
            const serverData = this._servers.resolveServer(host, pathname);
            if (serverData) {
                oaServer = serverData.oaServer;
                pathToResolve = serverData.pathnameRest;
                serverParams = serverData.serverParams;
                baseUrl = serverData.baseUrl;
            }
        }

        if (pathToResolve) {
            const resolvedPath = this._paths.resolvePath(pathToResolve);
            if (resolvedPath) {
                const { path, rawPathParams } = resolvedPath;
                const operation = path.getOperation(method);
                let mediaType: RequestMediaType | undefined;

                if (operation && contentType) {
                    mediaType = operation.getRequestMediaType(contentType);
                    if (!mediaType && (httpHasBody(headers) || requestMayHaveBody(method))) {
                        throw new HttpBadRequestError(`Invalid content-type: ${contentType}`);
                    }
                } else if (
                    operation &&
                    operation.bodyRequired &&
                    operation.validRequestContentTypes
                ) {
                    throw new HttpBadRequestError(
                        `Missing content-type. ` +
                            `Expected one of: ${operation.validRequestContentTypes}`
                    );
                }

                let resolvedOperation: ResolvedOperation | undefined;
                if (operation) {
                    const parseParameters = function () {
                        return operation.parseParameters({
                            headers,
                            rawPathParams,
                            serverParams,
                            queryString: parsedUrl.query || undefined,
                        });
                    };

                    const validateParameters: ParsedParameterValidator = (parameterValues) =>
                        operation.validateParameters(parameterValues);

                    const bodyParser = mediaType && mediaType.parser;
                    const validateBody = mediaType && mediaType.validator;

                    const validateResponse = (
                        response: ExegesisResponse,
                        validateDefaultResponses: boolean
                    ) => operation.validateResponse(response, validateDefaultResponses);

                    const exegesisControllerName =
                        (mediaType && mediaType.oaMediaType[EXEGESIS_CONTROLLER]) ||
                        operation.exegesisController;

                    const operationId =
                        (mediaType && mediaType.oaMediaType[EXEGESIS_OPERATION_ID]) ||
                        operation.operationId;

                    const controllerModule =
                        exegesisControllerName && this._options.controllers[exegesisControllerName];

                    const controller =
                        operationId && controllerModule && controllerModule[operationId];

                    const authenticate = (
                        context: ExegesisContext
                    ): Promise<{ [scheme: string]: AuthenticationSuccess } | undefined> => {
                        return operation.authenticate(context);
                    };

                    resolvedOperation = {
                        parseParameters,
                        validateParameters,
                        parameterLocations: operation.parameterLocations,
                        bodyParser,
                        validateBody,
                        validateResponse,
                        exegesisControllerName,
                        operationId,
                        controllerModule,
                        controller,
                        authenticate,
                    };
                }

                const allowedMethods = HTTP_METHODS.filter((method) => path.getOperation(method));

                return {
                    operation: resolvedOperation,
                    api: {
                        openApiDoc: this.openApiDoc,
                        serverPtr: undefined, // FIXME
                        serverObject: oaServer,
                        pathItemPtr: path.context.jsonPointer,
                        pathItemObject: path.oaPath,
                        operationPtr: operation && operation.context.jsonPointer,
                        operationObject: operation && operation.oaOperation,
                        requestBodyMediaTypePtr: mediaType && mediaType.context.jsonPointer,
                        requestBodyMediaTypeObject: mediaType && mediaType.oaMediaType,
                    },
                    allowedMethods,
                    path: resolvedPath.pathKey,
                    baseUrl,
                };
            }
        }

        return undefined;
    }
}


================================================
FILE: src/oas3/Operation.ts
================================================
import deepFreeze from 'deep-freeze';
import ld from 'lodash';
import pb from 'promise-breaker';
import * as oas3 from 'openapi3-ts';

import { MimeTypeRegistry } from '../utils/mime';
import { contentToRequestMediaTypeRegistry } from './oasUtils';
import RequestMediaType from './RequestMediaType';
import Oas3CompileContext from './Oas3CompileContext';
import Parameter from './Parameter';
import { RawValues, parseParameterGroup, parseQueryParameters } from './parameterParsers';
import {
    ParametersMap,
    ParametersByLocation,
    IValidationError,
    ExegesisContext,
    AuthenticationSuccess,
    Dictionary,
    AuthenticationFailure,
    AuthenticationResult,
    ExegesisResponse,
    ResponseValidationResult,
    ParameterLocations,
} from '../types';
import { EXEGESIS_CONTROLLER, EXEGESIS_OPERATION_ID } from './extensions';
import Responses from './Responses';
import SecuritySchemes from './SecuritySchemes';

// `delete` might have a body. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE
const METHODS_WITH_BODY = ['post', 'put', 'patch', 'delete'];

function isAuthenticationFailure(result: any): result is AuthenticationFailure {
    return !!(result.type === 'invalid' || result.type === 'missing');
}

function getMissing(required: string[], have: string[] | undefined) {
    if (!have || have.length === 0) {
        return required;
    } else {
        return required.filter((r) => !have.includes(r));
    }
}

function validateController(
    context: Oas3CompileContext,
    controller: string | undefined,
    operationId: string | undefined
) {
    if (!controller && !context.options.allowMissingControllers) {
        throw new Error(`Missing ${EXEGESIS_CONTROLLER} for ${context.jsonPointer}`);
    }
    if (!operationId && !context.options.allowMissingControllers) {
        throw new Error(
            `Missing operationId or ${EXEGESIS_OPERATION_ID} for ${context.jsonPointer}`
        );
    }
    if (controller && operationId) {
        if (!context.options.controllers[controller]) {
            throw new Error(
                `Could not find controller ${controller} defined in ${context.jsonPointer}`
            );
        } else if (!context.options.controllers[controller][operationId]) {
            throw new Error(
                `Could not find operation ${controller}#${operationId} defined in ${context.jsonPointer}`
            );
        }
    }
}

/*
 * Validate that all operations/request bodies have a controller and
 * operationId defined.
 */
function validateControllers(
    context: Oas3CompileContext,
    requestBody: oas3.RequestBodyObject | undefined,
    opController: string | undefined,
    operationId: string | undefined
) {
    if (requestBody) {
        for (const mediaType of Object.keys(requestBody.content)) {
            const mediaContext = context.childContext(['requestBody', 'content', mediaType]);
            const mediaTypeObject = requestBody.content[mediaType];
            const mediaController = mediaTypeObject[EXEGESIS_CONTROLLER] || opController;
            const mediaOperationId = mediaTypeObject[EXEGESIS_OPERATION_ID] || operationId;
            validateController(mediaContext, mediaController, mediaOperationId);
        }
    } else {
        validateController(context, opController, operationId);
    }
}

export default class Operation {
    readonly context: Oas3CompileContext;
    readonly oaOperation: oas3.OperationObject;
    readonly oaPath: oas3.PathItemObject;
    readonly exegesisController: string | undefined;
    readonly operationId: string | undefined;
    readonly securityRequirements: oas3.SecurityRequirementObject[];
    readonly parameterLocations: ParameterLocations;

    /**
     * If this operation has a `requestBody`, this is a list of content-types
     * the operation understands.  If this operation does not expect a request
     * body, then this is undefined.  Note this list may contain wildcards.
     */
    readonly validRequestContentTypes: string[] | undefined;

    readonly bodyRequired: boolean;

    private readonly _requestBodyContentTypes: MimeTypeRegistry<RequestMediaType>;
    private readonly _parameters: ParametersByLocation<Parameter[]>;
    private readonly _responses: Responses;
    private readonly _securitySchemes: SecuritySchemes;

    constructor(
        context: Oas3CompileContext,
        oaOperation: oas3.OperationObject,
        oaPath: oas3.PathItemObject,
        method: string,
        exegesisController: string | undefined,
        parentParameters: Parameter[]
    ) {
        this.context = context;
        this.oaOperation = oaOperation;
        this.oaPath = oaPath;
        this.exegesisController = oaOperation[EXEGESIS_CONTROLLER] || exegesisController;
        this.operationId = oaOperation[EXEGESIS_OPERATION_ID] || oaOperation.operationId;

        this.securityRequirements = oaOperation.security || context.openApiDoc.security || [];

        this._securitySchemes = new SecuritySchemes(context.openApiDoc);

        this._responses = new Responses(context.childContext('responses'), oaOperation.responses);

        for (const securityRequirement of this.securityRequirements) {
            for (const schemeName of Object.keys(securityRequirement)) {
                if (!context.options.authenticators[schemeName]) {
                    throw new Error(
                        `Operation ${context.jsonPointer} references security scheme "${schemeName}" ` +
                            `but no authenticator was provided.`
                    );
                }
            }
        }

        const requestBody =
            oaOperation.requestBody && METHODS_WITH_BODY.includes(method)
                ? (context.resolveRef(oaOperation.requestBody) as oas3.RequestBodyObject)
                : undefined;

        validateControllers(context, requestBody, this.exegesisController, this.operationId);

        if (requestBody) {
            this.validRequestContentTypes = Object.keys(requestBody.content);
            this.bodyRequired = requestBody.required || false;

            const contentContext = context.childContext(['requestBody', 'content']);
            this._requestBodyContentTypes = contentToRequestMediaTypeRegistry(
                contentContext,
                { in: 'request', name: 'body', docPath: contentContext.jsonPointer },
                requestBody.required || false,
                requestBody.content
            );
        } else {
            this._requestBodyContentTypes = new MimeTypeRegistry<RequestMediaType>();
            this.bodyRequired = false;
        }

        const localParameters = (this.oaOperation.parameters || []).map(
            (parameter, index) =>
                new Parameter(context.childContext(['parameters', '' + index]), parameter)
        );
        const allParameters = parentParameters.concat(localParameters);

        this._parameters = allParameters.reduce(
            (result: ParametersByLocation<Parameter[]>, parameter: Parameter) => {
                (result as any)[parameter.oaParameter.in].push(parameter);
                return result;
            },
            { query: [], header: [], path: [], server: [], cookie: [] }
        );

        this.parameterLocations = deepFreeze(
            allParameters.reduce(
                (result: ParameterLocations, parameter: Parameter) => {
                    (result as any)[parameter.oaParameter.in] = parameter.location;
                    return result;
                },
                { query: {}, header: {}, path: {}, cookie: {} }
            )
        );
    }

    /**
     * Given a 'content-type' from a request, return a `MediaType` object that
     * matches, or `undefined` if no objects match.
     *
     * @param contentType - The content type from the 'content-type' header on
     *   a request.
     * @returns - The MediaType object to handle this request, or undefined if
     *   no MediaType is set for the given contentType.
     */
    getRequestMediaType(contentType: string): RequestMediaType | undefined {
        return this._requestBodyContentTypes.get(contentType);
    }

    /**
     * Parse parameters for this operation.
     * @param params - Raw headers, raw path params and server params from
     *   `PathResolver`, and the raw queryString.
     * @returns parsed parameters.
     */
    parseParameters(params: {
        headers: RawValues | undefined;
        rawPathParams: RawValues | undefined;
        serverParams: RawValues | undefined;
        queryString: string | undefined;
    }): ParametersByLocation<ParametersMap<any>> {
        const { headers, rawPathParams, queryString } = params;

        return {
            query: parseQueryParameters(this._parameters.query, queryString),
            header: parseParameterGroup(this._parameters.header, headers || {}),
            server: params.serverParams || {},
            path: rawPathParams ? parseParameterGroup(this._parameters.path, rawPathParams) : {},
            cookie: {},
        };
    }

    validateParameters(
        parameterValues: ParametersByLocation<ParametersMap<any>>
    ): IValidationError[] | null {
        // TODO: We could probably make this a lot more efficient by building the schema
        // for the parameter tree.
        let errors: IValidationError[] | null = null;
        for (const parameterLocation of Object.keys(parameterValues)) {
            const parameters: Parameter[] = (this._parameters as any)[
                parameterLocation
            ] as Parameter[];
            const values = (parameterValues as any)[parameterLocation] as ParametersMap<any>;

            for (const parameter of parameters) {
                const innerResult = parameter.validate(values[parameter.oaParameter.name]);
                if (innerResult && innerResult.errors && innerResult.errors.length > 0) {
                    errors = errors || [];
                    errors = errors.concat(innerResult.errors);
                } else {
                    values[parameter.oaParameter.name] = innerResult.value;
                }
            }
        }

        return errors;
    }

    /**
     * Validate a response.
     *
     * @param response - The response generated by a controller.
     * @param validateDefaultResponses - true to validate all responses, false
     *   to only validate non-default responses.
     */
    validateResponse(
        response: ExegesisResponse,
        validateDefaultResponses: boolean
    ): ResponseValidationResult {
        return this._responses.validateResponse(
            response.statusCode,
            response.headers,
            response.body,
            validateDefaultResponses
        );
    }

    private async _runAuthenticator(
        schemeName: string,
        triedSchemes: Dictionary<AuthenticationResult>,
        exegesisContext: ExegesisContext,
        requiredScopes: string[]
    ): Promise<AuthenticationResult> {
        if (!(schemeName in triedSchemes)) {
            const authenticator = this.context.options.authenticators[schemeName];
            const info = this._securitySchemes.getInfo(schemeName);

            const result: AuthenticationResult = (await pb.call(
                authenticator,
                null,
                exegesisContext,
                info
            )) || { type: 'missing', status: 401 };

            if (
                result.type !== 'success' &&
                result.type !== 'invalid' &&
                result.type !== 'missing'
            ) {
                throw new Error(
                    `Invalid result ${result.type} from authenticator for ${schemeName}`
                );
            }

            if (isAuthenticationFailure(result)) {
                result.status = result.status || 401;
                if (result.status === 401 && !result.challenge) {
                    result.challenge = this._securitySchemes.getChallenge(schemeName);
                }
            }

            triedSchemes[schemeName] = result;
        }

        let result = triedSchemes[schemeName];

        if (!isAuthenticationFailure(result)) {
            // For OAuth3, need to verify we have the oauth scopes defined in the API doc.
            const missingScopes = getMissing(requiredScopes, result.scopes);
            if (missingScopes.length > 0) {
                result = {
                    type: 'invalid',
                    status: 403,
                    message:
                        `Authenticated using '${schemeName}' but missing ` +
                        `required scopes: ${missingScopes.join(', ')}.`,
                };
            }
        }

        return result;
    }

    /**
     * Checks a single security requirement from an OAS3 `security` field.
     *
     * @param triedSchemes - A cache where keys are names of security schemes
     *   we've already tried, and values are the results returned by the
     *   authenticator.
     * @param errors - An array of strings - we can push any errors we encounter
     *   to this list.
     * @param securityRequirement - The security requirement to check.
     * @param exegesisContext - The context for the request to check.
     * @returns - If the security requirement matches, this returns a
     *   `{type: 'authenticated', result}` object, where result is an object
     *   where keys are security schemes and the values are the results from
     *   the authenticator.  If the requirements are not met, returns a
     *   `{type: 'missing', failure}` object or a `{type: 'invalid', failure}`,
     *   object where `failure` is the the failure that caused this security
     *   requirement to not pass.
     */
    private async _checkSecurityRequirement(
        triedSchemes: Dictionary<AuthenticationResult>,
        securityRequirement: oas3.SecurityRequirementObject,
        exegesisContext: ExegesisContext
    ) {
        const requiredSchemes = Object.keys(securityRequirement);

        const result: Dictionary<any> = Object.create(null);
        let failure: AuthenticationFailure | undefined;
        let failedSchemeName: string | undefined;

        for (const scheme of requiredSchemes) {
            if (exegesisContext.isResponseFinished()) {
                // Some authenticator has written a response.  We're done.  :(
                break;
            }

            const requiredScopes = securityRequirement[scheme];
            const authResult = await this._runAuthenticator(
                scheme,
                triedSchemes,
                exegesisContext,
                requiredScopes
            );

            if (isAuthenticationFailure(authResult)) {
                // Couldn't authenticate.  Try the next one.
                failure = authResult;
                failedSchemeName = scheme;
                break;
            }

            result[scheme] = authResult;
        }

        if (failure) {
            return { type: failure.type, failure, failedSchemeName };
        } else if (result) {
            return { type: 'authenticated', result };
        } else {
            return undefined;
        }
    }

    async authenticate(
        exegesisContext: ExegesisContext
    ): Promise<{ [scheme: string]: AuthenticationSuccess } | undefined> {
        if (this.securityRequirements.length === 0) {
            // No auth required
            return {};
        }
        let firstFailureResult: AuthenticationFailure | undefined;
        const challenges: { [schemeName: string]: string | undefined } = {};
        let firstAuthenticatedResult: Dictionary<AuthenticationSuccess> | undefined;

        const triedSchemes: Dictionary<AuthenticationSuccess> = Object.create(null);

        for (const securityRequirement of this.securityRequirements) {
            const securityRequirementResult = await this._checkSecurityRequirement(
                triedSchemes,
                securityRequirement,
                exegesisContext
            );

            if (!securityRequirementResult) {
                break;
            } else if (securityRequirementResult.type === 'authenticated') {
                firstAuthenticatedResult =
                    firstAuthenticatedResult || securityRequirementResult.result;
            } else if (
                securityRequirementResult.type === 'missing' ||
                securityRequirementResult.type === 'invalid'
            ) {
                const failure = securityRequirementResult.failure;
                if (!failure) {
                    throw new Error('Missing failure.');
                }
                if (!securityRequirementResult.failedSchemeName) {
                    throw new Error('Missing failed scheme name.');
                }

                // No luck with this security requirement.
                if (failure.status === 401 && failure.challenge) {
                    challenges[securityRequirementResult.failedSchemeName] = failure.challenge;
                }

                if (securityRequirementResult.type === 'invalid') {
                    firstFailureResult = firstFailureResult || failure;
                    break;
                }
            } else {
                /* istanbul ignore this */
                throw new Error('Invalid result from `_checkSecurityRequirement()`');
            }

            if (exegesisContext.isResponseFinished()) {
                // We're done!
                break;
            }
        }

        if (firstAuthenticatedResult && !firstFailureResult) {
            // Successs!
            return firstAuthenticatedResult;
        } else if (exegesisContext.isResponseFinished()) {
            // Someone already wrote a response.
            return undefined;
        } else {
            const authSchemes = this.securityRequirements.map((requirement) => {
                const schemes = Object.keys(requirement);
                return schemes.length === 1 ? schemes[0] : `(${schemes.join(' + ')})`;
            });

            const authChallenges = ld(this.securityRequirements)
                .map((requirement: any): string[] => Object.keys(requirement))
                .flatten()
                .map(
                    (schemeName: string) =>
                        challenges[schemeName] || this._securitySchemes.getChallenge(schemeName)
                )
                .filter((challenge) => challenge !== undefined)
                .value() as string[];

            const message =
                (firstFailureResult && firstFailureResult.message) ||
                `Must authenticate using one of the following schemes: ${authSchemes.join(', ')}.`;

            exegesisContext.res
                .setStatus((firstFailureResult && firstFailureResult.status) || 401)
                .set('WWW-Authenticate', authChallenges)
                .setBody({ message });

            return undefined;
        }
    }
}


================================================
FILE: src/oas3/Parameter.ts
================================================
import { JSONSchema4, JSONSchema6 } from 'json-schema';
import { ParameterLocation, ValidatorFunction, oas3 } from '../types';
import { extractSchema } from '../utils/jsonSchema';
import Oas3CompileContext from './Oas3CompileContext';
import { generateRequestValidator } from './Schema/validators';
import { isReferenceObject } from './oasUtils';
import { ParameterParser, generateParser } from './parameterParsers';
import * as urlEncodedBodyParser from './urlEncodedBodyParser';

const DEFAULT_STYLE: { [style: string]: string } = {
    path: 'simple',
    query: 'form',
    cookie: 'form',
    header: 'simple',
};

function getDefaultExplode(style: string): boolean {
    return style === 'form';
}

function generateSchemaParser(self: Parameter, schema: JSONSchema4 | JSONSchema6) {
    const style = self.oaParameter.style || DEFAULT_STYLE[self.oaParameter.in];
    const explode =
        self.oaParameter.explode === null || self.oaParameter.explode === undefined
            ? getDefaultExplode(style)
            : self.oaParameter.explode;
    const allowReserved = self.oaParameter.allowReserved || false;

    return generateParser({
        required: self.oaParameter.required,
        style,
        explode,
        allowReserved,
        schema,
    });
}

export default class Parameter {
    readonly context: Oas3CompileContext;
    readonly oaParameter: oas3.ParameterObject;

    readonly location: ParameterLocation;
    readonly validate: ValidatorFunction;

    /**
     * Parameter parser used to parse this parameter.
     */
    readonly name: string;
    readonly parser: ParameterParser;

    constructor(
        context: Oas3CompileContext,
        oaParameter: oas3.ParameterObject | oas3.ReferenceObject
    ) {
        const resOaParameter = isReferenceObject(oaParameter)
            ? (context.resolveRef(oaParameter.$ref) as oas3.ParameterObject)
            : oaParameter;

        this.location = {
            in: resOaParameter.in,
            name: resOaParameter.name,
            docPath: context.jsonPointer,
            path: '',
        };
        this.name = resOaParameter.name;

        this.context = context;
        this.oaParameter = resOaParameter;
        this.validate = (value) => ({ errors: null, value });

        // Find the schema for this parameter.
        if (resOaParameter.schema) {
            const schemaContext = context.childContext('schema');
            const schema = extractSchema(context.openApiDoc, schemaContext.jsonPointer, {
                resolveRef: context.resolveRef.bind(context),
            });
            this.parser = generateSchemaParser(this, schema);
            this.validate = generateRequestValidator(
                schemaContext,
                this.location,
                resOaParameter.required || false,
                'application/x-www-form-urlencoded'
            );
        } else if (resOaParameter.content) {
            // `parameter.content` must have exactly one key
            const mediaTypeString = Object.keys(resOaParameter.content)[0];
            const oaMediaType = resOaParameter.content[mediaTypeString];
            const mediaTypeContext = context.childContext(['content', mediaTypeString]);

            let parser = context.options.parameterParsers.get(mediaTypeString);

            // OAS3 has special handling for 'application/x-www-form-urlencoded'.
            if (!parser && mediaTypeString === 'application/x-www-form-urlencoded') {
                parser = urlEncodedBodyParser.generateStringParser(
                    mediaTypeContext,
                    oaMediaType,
                    this.location
                );
            }

            if (!parser) {
                throw new Error(
                    'Unable to find suitable mime type parser for ' +
                        `type ${mediaTypeString} in ${context.jsonPointer}/content`
                );
            }

            // FIXME: We don't handle 'application/x-www-form-urlencoded' here
            // correctly.
            this.parser = generateParser({
                required: resOaParameter.required || false,
                schema: oaMediaType.schema,
                contentType: mediaTypeString,
                parser,
                uriEncoded: ['query', 'path'].includes(resOaParameter.in),
            });

            if (oaMediaType.schema) {
                this.validate = generateRequestValidator(
                    mediaTypeContext.childContext('schema'),
                    this.location,
                    resOaParameter.required || false,
                    mediaTypeString
                );
            }
        } else {
            throw new Error(
                `Parameter ${resOaParameter.name} should have a 'schema' or a 'content'`
            );
        }
    }
}


================================================
FILE: src/oas3/Path.ts
================================================
import Operation from './Operation';

import Oas3CompileContext from './Oas3CompileContext';
import * as oas3 from 'openapi3-ts';
import Parameter from './Parameter';
import { EXEGESIS_CONTROLLER } from './extensions';

// CONNECT not included, as it is not valid for OpenAPI 3.0.1.
export const HTTP_METHODS = [
    'get',
    'head',
    'post',
    'put',
    'delete',
    'options',
    'trace',
    'patch',
] as const;

interface OperationsMap {
    [key: string]: Operation;
}

export default class Path {
    readonly context: Oas3CompileContext;
    readonly oaPath: oas3.PathItemObject;
    private readonly _operations: OperationsMap;

    constructor(
        context: Oas3CompileContext,
        oaPath: oas3.PathItemObject,
        exegesisController: string | undefined
    ) {
        this.context = context;
        if (oaPath.$ref) {
            this.oaPath = context.resolveRef(oaPath.$ref) as oas3.PathItemObject;
        } else {
            this.oaPath = oaPath;
        }
        const parameters = (oaPath.parameters || []).map(
            (p, i) => new Parameter(context.childContext(['parameters', '' + i]), p)
        );

        exegesisController = oaPath[EXEGESIS_CONTROLLER] || exegesisController;
        this._operations = HTTP_METHODS.reduce((result: OperationsMap, method) => {
            const operation = oaPath[method];
            if (operation) {
                result[method] = new Operation(
                    context.childContext(method),
                    operation,
                    oaPath,
                    method,
                    exegesisController,
                    parameters
                );
            }
            return result;
        }, Object.create(null));
    }

    getOperation(method: string): Operation | undefined {
        return this._operations[method.toLowerCase()];
    }
}


================================================
FILE: src/oas3/Paths/PathResolver.ts
================================================
import { escapeRegExp } from 'lodash';
const TEMPLATE_RE = /^(.*?){(.*?)}(.*)$/;

import { ParametersMap } from '../../types';

export type PathParserFunction = (
    pathname: string
) => { matched: string; rawPathParams: ParametersMap<any> } | null;

/**
 * @param path - The path to check.
 * @returns true if the specified path uses templating, false otherwise.
 */
export function hasTemplates(path: string): boolean {
    return !!TEMPLATE_RE.exec(path);
}

/**
 * Given a path containing template parts (e.g. "/foo/{bar}/baz"), returns
 * a regular expression that matches the path, and a list of parameters found.
 *
 * @param path - The path to convert.
 * @param options.openEnded - If true, then the returned `regex` will
 *   accept extra input at the end of the path.
 *
 * @returns A `{regex, params, parser}`, where:
 * - `params` is a list of parameters found in the path.
 * - `regex` is a regular expression that will match the path.  When calling
 *   `match = regex.exec(str)`, each parameter in `params[i]` will be present
 *   in `match[i+1]`.
 * - `parser` is a `fn(str)` that, given a path, will return null if
 *   the string does not match, and a `{matched, pathParams}` object if the
 *   path matches.  `pathParams` is an object where keys are parameter names
 *   and values are strings from the `path`.  `matched` is full string matched
 *   by the regex.
 */
export function compileTemplatePath(
    path: string,
    options: {
        openEnded?: boolean;
    } = {}
): {
    params: string[];
    regex: RegExp;
    parser: PathParserFunction;
} {
    const params: string[] = [];

    // Split up the path at each parameter.
    const regexParts: string[] = [];
    let remainingPath = path;
    let tempateMatch;
    do {
        tempateMatch = TEMPLATE_RE.exec(remainingPath);
        if (tempateMatch) {
            regexParts.push(tempateMatch[1]);
            params.push(tempateMatch[2]);
            remainingPath = tempateMatch[3];
        }
    } while (tempateMatch);
    regexParts.push(remainingPath);

    const regexStr = regexParts.map(escapeRegExp).join('([^/]*)');
    const regex = options.openEnded ? new RegExp(`^${regexStr}`) : new RegExp(`^${regexStr}$`);

    const parser = (urlPathname: string) => {
        const match = regex.exec(urlPathname);
        if (match) {
            return {
                matched: match[0],
                rawPathParams: params.reduce(
                    (result: ParametersMap<string | string[]>, paramName, index) => {
                        result[paramName] = match[index + 1];
                        return result;
                    },
                    {}
                ),
            };
        } else {
            return null;
        }
    };

    return { regex, params, parser };
}

export default class PathResolver<T> {
    // Static paths with no templating are stored in a hash, for easy lookup.
    private readonly _staticPaths: { [key: string]: T };

    // Paths with templates are stored in an array, with parser functions that
    // recognize the path.
    private readonly _dynamicPaths: {
        parser: PathParserFunction;
        value: T;
        path: string;
    }[];

    // TODO: Pass in variable styles.  Some variable styles start with a special
    // character, and we can check to see if the character is there or not.
    // (Or, replace this whole class with a uri-template engine.)
    constructor() {
        this._staticPaths = Object.create(null);
        this._dynamicPaths = [];
    }

    registerPath(path: string, value: T) {
        if (!path.startsWith('/')) {
            throw new Error(`Invalid path "${path}"`);
        }

        if (hasTemplates(path)) {
            const { parser } = compileTemplatePath(path);
            this._dynamicPaths.push({ value, parser, path });
        } else {
            this._staticPaths[path] = value;
        }
    }

    /**
     * Given a `pathname` from a URL (e.g. "/foo/bar") this will return the
     * a static path if one exists, otherwise a path with templates if one
     * exists.
     *
     * @param urlPathname - The pathname to search for.
     * @returns A `{value, rawPathParams} object if a path is matched, or
     *   undefined if there was no match.
     */
    resolvePath(urlPathname: string) {
        let value: T | undefined = this._staticPaths[urlPathname];
        let rawPathParams: ParametersMap<string | string[]> | undefined;
        let path = urlPathname;

        if (!value) {
            for (const dynamicPath of this._dynamicPaths) {
                const matched = dynamicPath.parser(urlPathname);
                if (matched) {
                    value = dynamicPath.value;
                    rawPathParams = matched.rawPathParams;
                    path = dynamicPath.path;
                }
            }
        }

        if (value) {
            return {
                value,
                rawPathParams,
                path,
            };
        } else {
            return undefined;
        }
    }
}


================================================
FILE: src/oas3/Paths/index.ts
================================================
import { isSpecificationExtension } from '../oasUtils';
import Oas3CompileContext from '../Oas3CompileContext';
import Path from '../Path';
import PathResolver from './PathResolver';
import { EXEGESIS_CONTROLLER } from '../extensions';

import { ParametersMap } from '../../types';

export interface ResolvedPath {
    path: Path;
    pathKey: string;
    rawPathParams: ParametersMap<string | string[]> | undefined;
}

export default class Paths {
    private readonly _pathResolver: PathResolver<Path> = new PathResolver();

    constructor(context: Oas3CompileContext, exegesisController: string | undefined) {
        const { openApiDoc } = context;

        exegesisController = openApiDoc.paths[EXEGESIS_CONTROLLER] || exegesisController;
        for (const path of Object.keys(openApiDoc.paths)) {
            const pathObject = new Path(
                context.childContext(path),
                openApiDoc.paths[path],
                exegesisController
            );

            if (isSpecificationExtension(path)) {
                // Skip extentions
                continue;
            }

            this._pathResolver.registerPath(path, pathObject);
        }
    }

    /**
     * Given a `pathname` from a URL (e.g. "/foo/bar") this will return the
     * PathObject from the OpenAPI document's `paths` section.
     *
     * @param urlPathname - The pathname to search for.  Note that any
     *   URL prefix defined by the `servers` section of the OpenAPI doc needs
     *   to be stripped before calling this.
     * @returns A `{path, rawPathParams}` object.
     *   `rawPathParams` will be an object where keys are parameter names from path
     *   templating.  If the path cannot be resolved, returns null, although
     *   note that if the path is resolved and the operation is not found, this
     *   will return an object with a null `operationObject`.
     */
    resolvePath(urlPathname: string): ResolvedPath | undefined {
        const result = this._pathResolver.resolvePath(urlPathname);
        if (result) {
            return {
                path: result.value,
                rawPathParams: result.rawPathParams,
                pathKey: result.path,
            };
        } else {
            return undefined;
        }
    }
}


================================================
FILE: src/oas3/README.md
================================================
# oas3

This represents a "parsed" OpenAPI 3.x.x document.

This is a rough view of the heirachy of objects in an OpenApi object:

```text
OpenApi
+- Servers
+- Paths
   +- Path[]
      +- Operation
         +- Parameter
         +- RequestMediaType
         +- Responses
           +- Response
```

Every object has an Oas3CompileContext which has "global" information about
configuration and about where that object came from.


================================================
FILE: src/oas3/RequestMediaType.ts
================================================
import ld from 'lodash';
import * as oas3 from 'openapi3-ts';

import { ValidatorFunction, ParameterLocation, BodyParser } from '../types';
import { generateRequestValidator } from './Schema/validators';
import Oas3CompileContext from './Oas3CompileContext';
import * as urlEncodedBodyParser from './urlEncodedBodyParser';

function generateAddDefaultParser(parser: BodyParser, def: any): BodyParser {
    return {
        parseReq(req, res, next) {
            parser.parseReq(req, res, (err, result) => {
                if (err) {
                    return next(err);
                }
                // TODO: How to test this?  How do you even get here?  If there's
                // no 'content-type' you'll never get to a RequestMediaType in
                // the first place.  If the type is `application/json`, a 0-length
                // body will be invalid.  If the type is `text/plain`, a 0-length
                // body is the empty string, which is not undefined.  I don't
                // think this is ever going to be called.
                if (result === undefined && req.body === undefined) {
                    req.body = ld.cloneDeep(def);
                    next(null, req.body);
                } else {
                    next(err, result);
                }
            });
        },
    };
}

export default class RequestMediaType {
    readonly context: Oas3CompileContext;
    readonly oaMediaType: oas3.MediaTypeObject;
    readonly parser: BodyParser;
    readonly validator: ValidatorFunction;

    constructor(
        context: Oas3CompileContext,
        oaMediaType: oas3.MediaTypeObject,
        mediaType: string,
        parameterLocation: ParameterLocation,
        parameterRequired: boolean
    ) {
        this.context = context;
        this.oaMediaType = oaMediaType;

        let parser = this.context.options.bodyParsers.get(mediaType);

        // OAS3 has special handling for 'application/x-www-form-urlencoded'.
        if (!parser && mediaType === 'application/x-www-form-urlencoded') {
            parser = urlEncodedBodyParser.generateBodyParser(
                context,
                oaMediaType,
                parameterLocation
            );
        }

        if (!parser) {
            throw new Error(
                'Unable to find suitable mime type parser for ' +
                    `type ${mediaType} in ${context.jsonPointer}`
            );
        }

        const schema = oaMediaType.schema && context.resolveRef(oaMediaType.schema);

        if (schema && 'default' in schema) {
            this.parser = generateAddDefaultParser(parser, schema.default);
        } else {
            this.parser = parser;
        }

        if (schema) {
            const schemaContext = context.childContext('schema');
            this.validator = generateRequestValidator(
                schemaContext,
                parameterLocation,
                parameterRequired,
                mediaType
            );
        } else {
            this.validator = (value) => ({ errors: null, value });
        }
    }
}


================================================
FILE: src/oas3/Response.ts
================================================
import * as oas3 from 'openapi3-ts';
import Oas3CompileContext from './Oas3CompileContext';
import { ValidatorFunction, IValidationError, ParameterLocation, HttpHeaders } from '../types';
import { MimeTypeRegistry } from '../utils/mime';
import { generateResponseValidator } from './Schema/validators';
import { isReadable } from '../utils/typeUtils';

export default class Responses {
    readonly context: Oas3CompileContext;
    private readonly _responseValidators: MimeTypeRegistry<ValidatorFunction>;
    private readonly _hasResponses: boolean;
    private readonly _location: ParameterLocation;

    constructor(context: Oas3CompileContext, response: oas3.ResponseObject) {
        this.context = context;
        this._hasResponses = false;
        this._responseValidators = new MimeTypeRegistry<ValidatorFunction>();
        this._location = {
            in: 'response',
            name: 'body',
            docPath: this.context.jsonPointer,
        };

        if (response.content) {
            for (const mimeType of Object.keys(response.content)) {
                this._hasResponses = true;
                const mediaTypeObject = response.content[mimeType];
                let validator;
                if (mediaTypeObject.schema) {
                    const schemaContext = context.childContext(['content', mimeType, 'schema']);
                    validator = generateResponseValidator(
                        schemaContext,
                        this._location,
                        true // Responses are always required.
                    );
                } else {
                    validator = () => ({ errors: null, value: undefined });
                }
                this._responseValidators.set(mimeType, validator);
            }
        }
    }

    validateResponse(
        statusCode: number,
        headers: HttpHeaders,
        body: any
    ): IValidationError[] | null {
        const contentType = headers['content-type'];

        if (!contentType) {
            if (body) {
                return [
                    {
                        location: this._location,
                        message: `Response for ${statusCode} is missing content-type.`,
                    },
                ];
            } else if (this._hasResponses) {
                return [
                    {
                        location: this._location,
                        message: `Response for ${statusCode} expects body.`,
                    },
                ];
            } else {
                return null;
            }
        } else if (typeof contentType !== 'string') {
            return [
                {
                    location: this._location,
                    message: `Invalid content type for ${statusCode} response: ${contentType}`,
                },
            ];
        } else {
            const validator = this._responseValidators.get(contentType);
            const isJson = contentType.startsWith('application/json');

            if (body === null || body === undefined) {
                return [
                    {
                        location: this._location,
                        message: `Missing response body for ${statusCode}.`,
                    },
                ];
            } else if (!validator) {
                return [
                    {
                        location: this._location,
                        message: `Unexpected content-type for ${statusCode} response: ${contentType}.`,
                    },
                ];
            } else if (isJson) {
                if (body instanceof Buffer || isReadable(body)) {
                    // Can't validate this.
                    return null;
                }

                try {
                    let jsonData: any;
                    if (typeof body === 'string') {
                        if (body.trim() === '') {
                            jsonData = undefined;
                        } else {
                            jsonData = JSON.parse(body);
                        }
                    } else {
                        jsonData = body;
                    }

                    return validator(jsonData).errors;
                } catch (err) {
                    return [
                        {
                            location: this._location,
                            message: `Could not parse content as JSON.`,
                        },
                    ];
                }
            } else if (typeof body === 'string' || body instanceof Buffer || isReadable(body)) {
                // Can't validate this.
                return null;
            } else {
                return validator(body).errors;
            }
        }
    }
}


================================================
FILE: src/oas3/Responses.ts
================================================
import * as oas3 from 'openapi3-ts';
import Oas3CompileContext from './Oas3CompileContext';
import Response from './Response';
import { ParameterLocation, ResponseValidationResult, HttpHeaders } from '../types';

export default class Responses {
    readonly context: Oas3CompileContext;
    private readonly _responses: { [statusCode: string]: Response };
    private readonly _location: ParameterLocation;

    constructor(context: Oas3CompileContext, responses: oas3.ResponsesObject) {
        this.context = context;
        this._location = {
            in: 'response',
            name: 'body',
            docPath: this.context.jsonPointer,
        };

        this._responses = {};
        for (const statusCode of Object.keys(responses)) {
            const response: oas3.ResponseObject = context.resolveRef(responses[statusCode]);
            this._responses[statusCode] = new Response(context.childContext(statusCode), response);
        }
    }

    validateResponse(
        statusCode: number,
        headers: HttpHeaders,
        body: any,
        validateDefaultResponses: boolean
    ): ResponseValidationResult {
        const responseObject = this._responses[statusCode] || this._responses.default;
        if (!responseObject) {
            return {
                errors: [
                    {
                        location: this._location,
                        message: `No response defined for status code ${statusCode}.`,
                    },
                ],
                isDefault: false,
            };
        } else {
            const isDefault = !this._responses[statusCode];
            if (isDefault && !validateDefaultResponses) {
                return { errors: null, isDefault };
            } else {
                return {
                    errors: responseObject.validateResponse(statusCode, headers, body),
                    isDefault,
                };
            }
        }
    }
}


================================================
FILE: src/oas3/Schema/validators.ts
================================================
import Ajv, { ValidateFunction } from 'ajv';
import traveseSchema from 'json-schema-traverse';
import { IValidationError, ParameterLocation, ValidatorFunction } from '../../types';
import { resolveRef } from '../../utils/json-schema-resolve-ref';
import * as jsonPaths from '../../utils/jsonPaths';
import * as jsonSchema from '../../utils/jsonSchema';
import { MimeTypeRegistry } from '../../utils/mime';
import Oas3CompileContext from '../Oas3CompileContext';

// urlencoded and form-data requests do not contain any type information;
// for example `?foo=9` doesn't tell us if `foo` is the number 9, or the string
// "9", so we need to use type coercion to make sure the data passed in matches
// our schema.
const REQUEST_TYPE_COERCION_ALLOWED = new MimeTypeRegistry<boolean>({
    'application/x-www-form-urlencoded': true,
    'multipart/form-data': true,
});

// TODO tests
// * readOnly
// * readOnly with additionalProperties and value supplied
// * readOnly not supplied but required
// * writeOnly (all cases as readOnly)
// * Make sure validation errors are correct format.

function assertNever(x: never): never {
    throw new Error('Unexpected object: ' + x);
}

function getParameterDescription(parameterLocation: ParameterLocation) {
    let description = '';
    switch (parameterLocation.in) {
        case 'path':
        case 'server':
        case 'query':
        case 'cookie':
        case 'header':
            description = `${parameterLocation.in} parameter "${parameterLocation.name}"`;
            break;
        case 'request':
        case 'response':
            description = `${parameterLocation.in} body`;
            break;
        default:
            assertNever(parameterLocation.in);
    }

    return description;
}

function removeExamples(schema: any) {
    // ajv will print "schema id ignored" to stdout if an example contains a filed
    // named "id", so just axe all the examples.
    traveseSchema(schema, (childSchema: any) => {
        if (childSchema.example) {
            delete childSchema.example;
        }
    });
}

export function _fixNullables(schema: any) {
    traveseSchema(schema, {
        cb: {
            post: (
                childSchema: any,
                _jsonPtr,
                rootSchema: any,
                _parentJsonPtr,
                parentKeyword,
                _parentSchema,
                keyIndex
            ) => {
                if (childSchema.nullable) {
                    let ref = rootSchema;
                    let key = parentKeyword;
                    if (key && keyIndex) {
                        ref = ref[key];
                        key = `${keyIndex}`;
                    }
                    if (ref && key) {
                        ref[key] = {
                            anyOf: [{ type: 'null' }, childSchema],
                        };
                    } else if (childSchema === schema) {
                        schema = {
                            anyOf: [{ type: 'null' }, schema],
                        };
                    }
                }
            },
        },
    });

    return schema;
}

export function _filterRequiredProperties(schema: any, propNameToFilter: string) {
    traveseSchema(schema, (childSchema: any) => {
        if (childSchema.properties && childSchema.required) {
            for (const propName of Object.keys(childSchema.properties)) {
                const prop = childSchema.properties[propName];

                // Resolve the prop, in case it's a `{$ref: ....}`.
                const resolvedProp = resolveRef(schema, prop);

                if (resolvedProp && resolvedProp[propNameToFilter]) {
                    childSchema.required = childSchema.required.filter(
                        (r: string) => r !== propName
                    );
                }
            }
        }
    });
}

function doValidate(
    schemaPtr: string,
    parameterLocation: ParameterLocation,
    parameterRequired: boolean,
    getAjvValidate: () => ValidateFunction,
    json: any
) {
    const value = { value: json };
    let errors: IValidationError[] | null = null;

    if (json === null || json === undefined) {
        if (parameterRequired) {
            errors = [
                {
                    message: `Missing required ${getParameterDescription(parameterLocation)}`,
                    location: {
                        in: parameterLocation.in,
                        name: parameterLocation.name,
                        // docPath comes from parameter here, not schema, since the parameter
                        // is the one that defines it is required.
                        docPath: parameterLocation.docPath,
                        path: '',
                    },
                },
            ];
        }
    }

    if (!errors) {
        const ajvValidate = getAjvValidate();
        ajvValidate(value);
        if (ajvValidate.errors) {
            errors = ajvValidate.errors.map((err) => {
                let pathPtr = err.instancePath || '';
                if (pathPtr.startsWith('/value')) {
                    pathPtr = pathPtr.slice(6);
                }

                return {
                    message: err.message || 'Unspecified error',
                    location: {
                        in: parameterLocation.in,
                        name: parameterLocation.name,
                        docPath: schemaPtr,
                        path: pathPtr,
                    },
                    ajvError: err,
                };
            });
        }
    }

    return { errors, value: value.value };
}

function createValidateGetter(schema: any, ajv: Ajv, lazy: boolean): () => ValidateFunction {
    if (lazy) {
        let validate: ValidateFunction | null = null;
        return function () {
            if (!validate) {
                validate = ajv.compile(schema);
            }
            return validate;
        };
    } else {
        const validate = ajv.compile(schema);
        return function () {
            return validate;
        };
    }
}

function generateValidator(
    schemaContext: Oas3CompileContext,
    parameterLocation: ParameterLocation,
    parameterRequired: boolean,
    propNameToFilter: string,
    allowTypeCoercion: boolean
): ValidatorFunction {
    const { openApiDoc, jsonPointer: schemaPtr } = schemaContext;
    const customFormats = schemaContext.options.customFormats;

    let schema: any = jsonSchema.extractSchema(openApiDoc, schemaPtr);
    _filterRequiredProperties(schema, propNameToFilter);
    removeExamples(schema);
    // TODO: Should we do this?  Or should we rely on the schema being correct in the first place?
    // schema = _fixNullables(schema);

    // So that we can replace the "root" value of the schema using ajv's type coercion...
    traveseSchema(schema, (node: any) => {
        if (node.$ref) {
            if (node.$ref.startsWith('#')) {
                node.$ref = `#/properties/value/${node.$ref.slice(2)}`;
            } else {
                node.$ref = jsonPaths.toUriFragment(`/properties/value/${node.$ref.slice(1)}`);
            }
        }
    });
    schema = {
        type: 'object',
        properties: {
            value: schema,
        },
    };

    const ajv = new Ajv({
        useDefaults: true,
        coerceTypes: allowTypeCoercion ? 'array' : false,
        removeAdditional: allowTypeCoercion ? 'failing' : false,
        allErrors: schemaContext.options.allErrors,
        strict: schemaContext.options.strictValidation,
    });

    for (const key of Object.keys(customFormats)) {
        ajv.addFormat(key, customFormats[key]);
    }

    const getValidate = createValidateGetter(
        schema,
        ajv,
        schemaContext.options.lazyCompileValidationSchemas
    );

    return function (json: any) {
        return doValidate(schemaPtr, parameterLocation, parameterRequired, getValidate, json);
    };
}

export function generateRequestValidator(
    schemaContext: Oas3CompileContext,
    parameterLocation: ParameterLocation,
    parameterRequired: boolean,
    mediaType: string
): ValidatorFunction {
    const allowTypeCoercion = mediaType
        ? REQUEST_TYPE_COERCION_ALLOWED.get(mediaType) || false
        : false;
    return generateValidator(
        schemaContext,
        parameterLocation,
        parameterRequired,
        'readOnly',
        allowTypeCoercion
    );
}

export function generateResponseValidator(
    schemaContext: Oas3CompileContext,
    parameterLocation: ParameterLocation,
    parameterRequired: boolean
): ValidatorFunction {
    return generateValidator(
        schemaContext,
        parameterLocation,
        parameterRequired,
        'writeOnly',
        false
    );
}


================================================
FILE: src/oas3/SecuritySchemes.ts
================================================
import ld from 'lodash';
import * as oas3 from 'openapi3-ts';
import { AuthenticatorInfo } from '..';
import { resolveRef } from '../utils/json-schema-resolve-ref';

export default class SecuritySchemes {
    private readonly _securitySchemes: { [securityScheme: string]: oas3.SecuritySchemeObject };
    private readonly _challenges: { [securityScheme: string]: string | undefined };
    private readonly _infos: { [securityScheme: string]: AuthenticatorInfo };

    constructor(openApiDoc: oas3.OpenAPIObject) {
        const securitySchemes =
            (openApiDoc.components && openApiDoc.components.securitySchemes) || {};
        this._securitySchemes = securitySchemes.ref
            ? resolveRef(openApiDoc, securitySchemes)
            : securitySchemes;

        this._challenges = ld.mapValues(this._securitySchemes, (scheme) => {
            if (scheme.type === 'http') {
                return scheme.scheme || 'Basic';
            }
            if (scheme.type === 'oauth2' || scheme.type === 'openIdConnect') {
                return 'Bearer';
            }
            return undefined;
        });

        this._infos = ld.mapValues(this._securitySchemes, (scheme) => {
            if (scheme.type === 'apiKey') {
                return {
                    in: scheme.in as any,
                    name: scheme.name,
                };
            } else if (scheme.type === 'http') {
                return {
                    scheme: scheme.scheme,
                };
            } else {
                return {};
            }
        });
    }

    getChallenge(schemeName: string): string | undefined {
        return this._challenges[schemeName];
    }

    getInfo(schemeName: string): AuthenticatorInfo {
        return this._infos[schemeName];
    }
}


================================================
FILE: src/oas3/Servers.ts
================================================
import * as oas3 from 'openapi3-ts';
import { compileTemplatePath, PathParserFunction } from './Paths/PathResolver';

import { ParametersMap } from '../types';

const FULL_URL_RE = /^(.*?):\/\/([^/]*?)(?:\/(.*))?$/; // e.g. https://foo.bar/v1
const ABSOLUTE_URL_RE = /^(\/.*)$/; // e.g. /v1

export interface ResolvedServer {
    /**
     * The server definition that was matched from the `servers`
     * section of the OpenAPI document.
     */
    oaServer: oas3.ServerObject;
    /**
     * The values of any template parameters defined in
     * the `server.url` of the matching `server` object.
     */
    serverParams: ParametersMap<string | string[]>;
    /** The unmatched portion of the `pathname`. */
    pathnameRest: string;
    /** The matched portion of the `pathname`. */
    baseUrl: string;
}

type ServerParser = (host: string, pathname: string) => ResolvedServer | null;

function generateServerParser(oaServer: oas3.ServerObject): ServerParser {
    // Strip trailing '/'.
    const serverUrl = oaServer.url.endsWith('/')
        ? oaServer.url.slice(0, oaServer.url.length - 1)
        : oaServer.url;

    let result: ServerParser;
    let match;

    if (serverUrl === '') {
        result = (_host, pathname) => ({
            oaServer,
            pathnameRest: pathname,
            serverParams: Object.create(null),
            baseUrl: '',
        });
    } else if ((match = FULL_URL_RE.exec(serverUrl))) {
        const hostname = match[2];
        const basepath = match[3] || '';
        const { parser: hostnameAcceptFunction } = compileTemplatePath(hostname);
        let basepathAcceptFunction: PathParserFunction;
        if (basepath) {
            basepathAcceptFunction = compileTemplatePath('/' + basepath, { openEnded: true })
                .parser;
        } else {
            basepathAcceptFunction = () => ({
                matched: '',
                rawPathParams: Object.create(null),
            });
        }

        result = (host, pathname) => {
            const hostMatch = hostnameAcceptFunction(host);
            const pathMatch = basepathAcceptFunction(pathname);
            if (hostMatch && pathMatch) {
                return {
                    oaServer,
                    pathnameRest: pathname.slice(pathMatch.matched.length),
                    serverParams: Object.assign(
                        {},
                        hostMatch.rawPathParams,
                        pathMatch.rawPathParams
                    ),
                    baseUrl: pathMatch.matched,
                };
            } else {
                return null;
            }
        };
    } else if ((match = ABSOLUTE_URL_RE.exec(serverUrl))) {
        const basepath = match[1];
        const { parser: basepathParser } = compileTemplatePath(basepath, { openEnded: true });

        result = (_host, pathname) => {
            const pathMatch = basepathParser(pathname);
            if (pathMatch) {
                return {
                    oaServer,
                    serverParams: pathMatch.rawPathParams,
                    pathnameRest: pathname.slice(pathMatch.matched.length),
                    baseUrl: pathMatch.matched,
                };
            } else {
                return null;
            }
        };
    } else {
        // TODO: deal with relative URLs.
        throw new Error(`Don't know how to deal with server URL ${oaServer.url}`);
    }

    return result;
}

export default class Servers {
    private readonly _servers: ServerParser[];

    constructor(servers: oas3.ServerObject[] | undefined) {
        servers = servers || [{ url: '/' }];
        this._servers = servers.map((server) => generateServerParser(server));
    }

    /**
     * Resolve the `server` that's being accessed.
     *
     * @param host - The hostname to match.
     * @param pathname - The URL pathname to match.
     * @returns If a matching `server` is found, returns a
     * `ResolvedServer` object.  Returns `null` if no match was found.
     */
    resolveServer(host: string, pathname: string): ResolvedServer | null {
        for (const server of this._servers) {
            const result = server(host, pathname);
            if (result) {
                return result;
            }
        }

        return null;
    }
}


================================================
FILE: src/oas3/extensions.ts
================================================
export const EXEGESIS_CONTROLLER = 'x-exegesis-controller';
export const EXEGESIS_OPERATION_ID = 'x-exegesis-operationId';


================================================
FILE: src/oas3/index.ts
================================================
import oas3 from 'openapi3-ts';

import OpenApi from './OpenApi';
import { ExegesisCompiledOptions } from '../options';

export { OpenApi };

/**
 * Reads an OpenAPI document from a YAML or JSON file.
 *
 * @param openApiDocFile - The file containing the OpenAPI document.
 * @returns - Returns the parsed OpenAPI document.
 */
export async function compile(
    openApiDoc: oas3.OpenAPIObject,
    options: ExegesisCompiledOptions
): Promise<OpenApi> {
    return new OpenApi(openApiDoc, options);
}


================================================
FILE: src/oas3/oasUtils/index.ts
================================================
import { MimeTypeRegistry } from '../../utils/mime';
import RequestMediaType from '../RequestMediaType';

import * as oas3 from 'openapi3-ts';
import Oas3CompileContext from '../Oas3CompileContext';
import { ParameterLocation } from '../..';

export function isSpecificationExtension(key: string) {
    return key.startsWith('x-');
}

export function isReferenceObject(obj: any): obj is oas3.ReferenceObject {
    return !!obj.$ref;
}

/**
 *
 * @param openApiDoc - The openApiDocument this `content` object is from.
 * @param path - The path to the `content` object.
 * @param content - The `content` object.
 */
export function contentToRequestMediaTypeRegistry(
    context: Oas3CompileContext,
    parameterLocation: ParameterLocation,
    parameterRequired: boolean,
    content?: oas3.ContentObject
) {
    const answer = new MimeTypeRegistry<RequestMediaType>();

    if (content) {
        for (const mediaType of Object.keys(content)) {
            const oaMediaType = content[mediaType];
            answer.set(
                mediaType,
                new RequestMediaType(
                    context.childContext(mediaType),
                    oaMediaType,
                    mediaType,
                    parameterLocation,
                    parameterRequired
                )
            );
        }
    }

    return answer;
}


================================================
FILE: src/oas3/parameterParsers/README.md
================================================
# Parameter Parsing

Cookie parameters are not well defined by OAS3, so at the moment Exegesis
doesn't support them. See discussion
[here](https://github.com/OAI/OpenAPI-Specification/issues/1528). If you
really need cookie parameters, please raise an issue.

Parameters in OAS3 come in two different flavors; parameters with a
"content-type" which are parsed using a custom string parser (e.g. an
"application/json" parameter) and parameters that are parsed using a "style".
This document covers styled parameters.

## OAS3 Styles

- matrix - Path-style expansion from RFC6570 - `{;var}`
- form - Form-style Query expansion from RFC6570 - `{?var}`
- label - Label expansion from RFC6570 - `{.var}`
- simple - Simple string expansion from RFC6570 - `{var}`
- spaceDelimited
- pipeDelimited
- deepObject - `qs` style form parameters.

Allowed styles by parameter location:

- path: 'matrix', 'label', 'simple'
- query: 'form', 'spaceDelimited', pipeDelimited', 'deepObject'
- cookie: 'form' ???
- header: 'simple'

For `query` we also have to worry about 'allowEmptyValue' and 'allowReserved'.
It's not clear to me that these have any effect on the parsing side, though.

To parse a given parameter, we need to know:

- The parameter name.
- The style of the parameter.
- If the parameter is exploded.
- The type or types we are allowed to decode the parameter to (some combination
  of "string", "array", "object" - we don't worry about numbers or integers -
  strings can be converted to these other basic types).
- For exploded parameters, it would be nice to have a list of expected property
  names.

## Implementation

While OAS3 is largely based on RFC6570, aside from the query string, OAS3 never
allows us to have two variables in the same expression (e.g. in URI Templates
you can do `{foo,bar}`).

We do a pre-processing step and extract all the variables out of the path
into a dictionary where variables are keyed by name (wouldn't be able to do this
with full RF6570 templates, because you can have multiple variables in the
same expression e.g. `/path/{foo,bar}`). We extract all the querystring
variables out by passing the querystring through a querty string parser (although
we do _not_ call `decodeURIComponent()` on values, since pct-encoded ","s are
different from ","s for RFC6570 expansions). node.js will extract all the
headers for us in a similar fashion.

For most cases, a parameter parser could take in just a string or array of
strings. Path-style expansions and query-style expansions throw a wrench into
this, however, because an exploded parameter will need to read values from keys
other than the parameter name.

So a parameter parser is a function of the form:

```js
    function(name, rawValues, querystring)
```

Given a function which just takes in a string or array of strings, we can
convert that into the above function trivially, so where possible parameter
parsers just take in the raw value for their name.

Parsers always try to return _something_. If we're given a query parameter
that can only be a string, but the query parameter is in the query string more
than once, we'll produce an array of strings, and validation will take care of
realizing that something is wrong. Parsers can also return a "string" when
an array is wanted, and we'll do type cooercion to fix the array after the fact.


================================================
FILE: src/oas3/parameterParsers/common.ts
================================================
import ld from 'lodash';

export function arrayToObject(values: string | string[] | undefined) {
    if (!values) {
        return values;
    }

    if (typeof values === 'string') {
        // ???
        return values;
    }

    const result: any = {};
    for (let i = 0; i < values.length; i = i + 2) {
        // Note if the array is an odd length, we'll end up with an "undefined" parameter at the end.
        result[values[i]] = values[i + 1];
    }

    return result;
}

// Converts all simple types that are not "string" into "string".
export function removeSimpleTypes(allowedTypes: string[]) {
    return ld.uniq(
        allowedTypes.map((t) => {
            if (t === 'object') {
                return 'object';
            } else if (t === 'array') {
                return 'array';
            } else {
                return 'string';
            }
        })
    );
}

export function allowedTypesToMap(allowedTypes: string[]): any {
    return allowedTypes.reduce<any>((m, t) => {
        m[t] = true;
        return m;
    }, {});
}


================================================
FILE: src/oas3/parameterParsers/delimitedParser.ts
================================================
// Implements 'spaceDelimited' and 'pipeDelimited' from OAS 3.

import { RawValues } from './types';
import * as exegesisTypes from '../../types';

export function generateDelimitedParser(delimiter: string) {
    return function delimitedParser(
        location: exegesisTypes.ParameterLocation,
        rawParamValues: RawValues
    ): string[] | undefined {
        const value = rawParamValues[location.name];
        if (value === null || value === undefined) {
            return value;
        } else if (Array.isArray(value)) {
            // Client is supposed to send us a delimited string, but it looks
            // like they sent us multiple copies of the var instead.  Just
            // decode the array.
            return value.map(decodeURIComponent);
        } else {
            return decodeURIComponent(value).split(delimiter);
        }
    };
}

export const pipeDelimitedParser = generateDelimitedParser('|');
export const spaceDelimitedParser = generateDelimitedParser(' ');


================================================
FILE: src/oas3/parameterParsers/index.ts
================================================
import ld from 'lodash';
import querystring from 'querystring';
import qs from 'qs';

import { ParametersMap, ParameterLocation } from '../../types';
import { ValidationError } from '../../errors';
import { pipeDelimitedParser, spaceDelimitedParser } from './delimitedParser';
import { generateStructuredParser } from './structuredParser';
import { getSimpleStringParser } from './simpleStringParser';
import { generatePathStyleParser } from './pathStyleParser';
import {
    RawStringParameterParser,
    ParameterParser,
    RawValues,
    ParameterDescriptor,
    MediaTypeParameterDescriptor,
    StyledParameterDescriptor,
} from './types';

export * from './types';

function isMediaTypeParameterDescriptor(
    descriptor: ParameterDescriptor
): descriptor is MediaTypeParameterDescriptor {
    return descriptor && (descriptor as any).contentType && (descriptor as any).parser;
}

export function generateParser(parameterDescriptor: ParameterDescriptor): ParameterParser {
    let answer: ParameterParser;
    if (isMediaTypeParameterDescriptor(parameterDescriptor)) {
        answer = generateMediaTypeParser(parameterDescriptor);
    } else {
        answer = generateStyleParser(parameterDescriptor);
    }
    return answer;
}

function generateMediaTypeParser(
    parameterDescriptor: MediaTypeParameterDescriptor
): ParameterParser {
    // request and response are here for application/x-www-form-urlencoded.

    let answer: ParameterParser = (location: ParameterLocation, values: RawValues): any => {
        try {
            let value = values[location.name];
            if (value === undefined || value === null) {
                return value;
            }

            if (parameterDescriptor.uriEncoded) {
                if (Array.isArray(value)) {
                    value = value.map(decodeURIComponent);
                } else {
                    value = decodeURIComponent(value);
                }
            }

            if (Array.isArray(value)) {
                return value.map((v) => parameterDescriptor.parser.parseString(v));
            } else {
                return parameterDescriptor.parser.parseString(value);
            }
        } catch (err) {
            throw new ValidationError({
                message:
                    `Error parsing parameter ${location.name} of ` +
                    `type ${parameterDescriptor.contentType}: ${err}`,
                location,
            });
        }
    };

    if (parameterDescriptor.schema && parameterDescriptor.schema.default) {
        answer = setDefault(answer, parameterDescriptor.schema.default);
    }
    return answer;
}

function generateStyleParser(descriptor: StyledParameterDescriptor) {
    const { schema, explode } = descriptor;
    let answer: ParameterParser;

    switch (descriptor.style) {
        case 'simple':
            answer = toStructuredParser(getSimpleStringParser(schema, explode));
            break;
        case 'form':
            answer = generateStructuredParser(schema, explode);
            break;
        case 'matrix':
            answer = generatePathStyleParser(schema, explode);
            break;
        case 'spaceDelimited':
            answer = spaceDelimitedParser;
            break;
        case 'pipeDelimited':
            answer = pipeDelimitedParser;
            break;
        case 'deepObject':
            answer = deepObjectParser;
            break;
        default:
            throw new Error(`Don't know how to p
Download .txt
gitextract_vv9qn1ho/

├── .eslintrc.js
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── github-ci.yaml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .markdownlint.json
├── .mocharc.js
├── .npmrc
├── .nycrc
├── .prettierrc.js
├── .releaserc.yml
├── .vscode/
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── Exegesis Controllers.md
│   ├── Exegesis Plugins.md
│   ├── OAS3 Parameter Parsing.md
│   ├── OAS3 Security.md
│   ├── OAS3 Specification Extensions.md
│   ├── Options.md
│   └── Tutorial.md
├── package.json
├── samples/
│   ├── javascript-example/
│   │   ├── controllers/
│   │   │   └── greetController.js
│   │   ├── index.js
│   │   ├── openapi.yaml
│   │   └── package.json
│   └── typescript-example/
│       ├── README.md
│       ├── controllers/
│       │   └── greetController.ts
│       ├── openapi.yaml
│       ├── package.json
│       ├── server.ts
│       └── tsconfig.json
├── src/
│   ├── bodyParsers/
│   │   ├── BodyParserWrapper.ts
│   │   ├── JsonBodyParser.ts
│   │   └── TextBodyParser.ts
│   ├── controllers/
│   │   ├── invoke.ts
│   │   └── loadControllers.ts
│   ├── core/
│   │   ├── ExegesisContextImpl.ts
│   │   ├── ExegesisResponseImpl.ts
│   │   ├── PluginsManager.ts
│   │   └── exegesisRunner.ts
│   ├── errors.ts
│   ├── index.ts
│   ├── oas3/
│   │   ├── Oas3CompileContext.ts
│   │   ├── OpenApi.ts
│   │   ├── Operation.ts
│   │   ├── Parameter.ts
│   │   ├── Path.ts
│   │   ├── Paths/
│   │   │   ├── PathResolver.ts
│   │   │   └── index.ts
│   │   ├── README.md
│   │   ├── RequestMediaType.ts
│   │   ├── Response.ts
│   │   ├── Responses.ts
│   │   ├── Schema/
│   │   │   └── validators.ts
│   │   ├── SecuritySchemes.ts
│   │   ├── Servers.ts
│   │   ├── extensions.ts
│   │   ├── index.ts
│   │   ├── oasUtils/
│   │   │   └── index.ts
│   │   ├── parameterParsers/
│   │   │   ├── README.md
│   │   │   ├── common.ts
│   │   │   ├── delimitedParser.ts
│   │   │   ├── index.ts
│   │   │   ├── pathStyleParser.ts
│   │   │   ├── simpleStringParser.ts
│   │   │   ├── structuredParser.ts
│   │   │   └── types.ts
│   │   └── urlEncodedBodyParser.ts
│   ├── options.ts
│   ├── types/
│   │   ├── basicTypes.ts
│   │   ├── bodyParser.ts
│   │   ├── core.ts
│   │   ├── index.ts
│   │   ├── options.ts
│   │   └── validation.ts
│   └── utils/
│       ├── bufferToStream.ts
│       ├── httpUtils.ts
│       ├── json-schema-infer-types.ts
│       ├── json-schema-resolve-ref.ts
│       ├── jsonPaths.ts
│       ├── jsonSchema.ts
│       ├── mime.ts
│       ├── stringToStream.ts
│       └── typeUtils.ts
├── test/
│   ├── controllers/
│   │   ├── fixtures/
│   │   │   ├── badControllers/
│   │   │   │   └── a.md
│   │   │   ├── controllers/
│   │   │   │   ├── a.js
│   │   │   │   ├── b/
│   │   │   │   │   └── c.js
│   │   │   │   └── d/
│   │   │   │       └── index.js
│   │   │   └── shadow/
│   │   │       ├── a/
│   │   │       │   └── index.js
│   │   │       └── a.js
│   │   └── loadControllersTest.ts
│   ├── core/
│   │   ├── ExegesisResponseImplTest.ts
│   │   └── pluginTest.ts
│   ├── fixtures/
│   │   ├── FakeApiInterface.ts
│   │   ├── FakeExegesisContext.ts
│   │   └── index.ts
│   ├── integration/
│   │   ├── integration/
│   │   │   ├── controllers/
│   │   │   │   ├── echoController.ts
│   │   │   │   ├── greetController.ts
│   │   │   │   ├── malformed.ts
│   │   │   │   ├── postWithDefault.ts
│   │   │   │   ├── postWithOptionalBody.ts
│   │   │   │   ├── secureController.js
│   │   │   │   ├── statusController.ts
│   │   │   │   └── streamController.ts
│   │   │   ├── customErrorFormattingTest.ts
│   │   │   ├── customErrorHandler.ts
│   │   │   ├── integrationTest.ts
│   │   │   └── openapi.yaml
│   │   └── issue-132/
│   │       ├── entry.yaml
│   │       ├── issue132Test.ts
│   │       └── openapi.yaml
│   ├── oas3/
│   │   ├── OperationTest.ts
│   │   ├── Paths/
│   │   │   ├── PathResolverTest.ts
│   │   │   └── PathsTest.ts
│   │   ├── ResponsesTest.ts
│   │   ├── Schema/
│   │   │   └── validatorsTest.ts
│   │   ├── ServersTest.ts
│   │   ├── oas3ControllersTest.ts
│   │   ├── oas3ParametersTest.ts
│   │   ├── parameterParsers/
│   │   │   ├── delimitedParserTest.ts
│   │   │   └── indexTest.ts
│   │   └── samplesTest.ts
│   ├── samples/
│   │   └── petstore.yaml
│   ├── tsconfig.json
│   └── utils/
│       ├── json-schema-infer-typesTest.ts
│       ├── json-schema-resolve-refTest.ts
│       ├── jsonPathsTest.ts
│       ├── jsonSchemaTest.ts
│       ├── mimeTest.ts
│       └── stringToStreamTest.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (367 symbols across 73 files)

FILE: samples/javascript-example/index.js
  function createServer (line 6) | async function createServer() {

FILE: samples/typescript-example/server.ts
  constant PORT (line 10) | const PORT = 3000;
  function createServer (line 14) | async function createServer() {

FILE: src/bodyParsers/BodyParserWrapper.ts
  class BodyParserWrapper (line 8) | class BodyParserWrapper implements MimeTypeParser {
    method constructor (line 12) | constructor(parser: StringParser, maxBodySize: number) {
    method parseString (line 17) | parseString(value: string) {
    method parseReq (line 21) | parseReq(req: HttpIncomingMessage, _res: http.ServerResponse, done: Ca...

FILE: src/bodyParsers/JsonBodyParser.ts
  class JsonBodyParser (line 6) | class JsonBodyParser implements MimeTypeParser {
    method constructor (line 9) | constructor(maxBodySize: number) {
    method parseString (line 18) | parseString(value: string) {
    method parseReq (line 22) | parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Ca...

FILE: src/bodyParsers/TextBodyParser.ts
  class TextBodyParser (line 6) | class TextBodyParser implements MimeTypeParser {
    method constructor (line 9) | constructor(maxBodySize: number) {
    method parseString (line 18) | parseString(value: string) {
    method parseReq (line 22) | parseReq(req: http.IncomingMessage, res: http.ServerResponse, done: Ca...

FILE: src/controllers/invoke.ts
  function invokeController (line 5) | function invokeController(

FILE: src/controllers/loadControllers.ts
  function loadControllersSync (line 21) | function loadControllersSync(

FILE: src/core/ExegesisContextImpl.ts
  constant EMPTY_PARAMS (line 21) | const EMPTY_PARAMS = deepFreeze({
  constant EMPTY_PARAM_LOCATIONS (line 29) | const EMPTY_PARAM_LOCATIONS: ParameterLocations = deepFreeze<ParameterLo...
  constant EMPTY_ROUTE (line 36) | const EMPTY_ROUTE = deepFreeze({
  class ExegesisContextImpl (line 40) | class ExegesisContextImpl<T> implements ExegesisContext, ExegesisPluginC...
    method constructor (line 58) | constructor(
    method _setOperation (line 77) | _setOperation(baseUrl: string, path: string, operation: ResolvedOperat...
    method makeError (line 93) | makeError(statusCode: number, message: string): HttpError {
    method makeValidationError (line 97) | makeValidationError(message: string, parameterLocation: ParameterLocat...
    method isResponseFinished (line 104) | isResponseFinished() {
    method getParams (line 110) | getParams(done?: Callback<any>): Promise<ParametersByLocation<Paramete...
    method getRequestBody (line 130) | getRequestBody(done?: Callback<any>): Promise<any> | void {

FILE: src/core/ExegesisResponseImpl.ts
  class ExegesisResponseImpl (line 6) | class ExegesisResponseImpl implements types.ExegesisResponse {
    method constructor (line 19) | constructor(
    method setStatus (line 30) | setStatus(status: number) {
    method status (line 38) | status(status: number) {
    method header (line 42) | header(header: string, value: number | string | string[]) {
    method set (line 47) | set(header: string, value: number | string | string[]) {
    method json (line 52) | json(json: any) {
    method pureJson (line 65) | pureJson(json: any) {
    method setBody (line 70) | setBody(body: any): this {
    method body (line 78) | set body(body: any) {
    method body (line 83) | get body(): any {
    method end (line 87) | end() {
    method redirect (line 94) | redirect(a: number | string, b?: string): this {
    method setHeader (line 107) | setHeader(name: string, value: number | string | string[]) {
    method getHeader (line 114) | getHeader(name: string) {
    method getHeaderNames (line 118) | getHeaderNames() {
    method getHeaders (line 122) | getHeaders() {
    method hasHeader (line 126) | hasHeader(name: string) {
    method removeHeader (line 130) | removeHeader(name: string) {
    method writeHead (line 137) | writeHead(statusCode: number, statusMessage?: string | HttpHeaders, he...

FILE: src/core/PluginsManager.ts
  function callFn (line 5) | function callFn(
  class PluginsManager (line 18) | class PluginsManager {
    method constructor (line 26) | constructor(apiDoc: any, plugins: exegesis.ExegesisPlugin[]) {
    method preCompile (line 36) | async preCompile(data: { apiDoc: any; options: exegesis.ExegesisOption...
    method preRouting (line 44) | async preRouting(data: { req: http.IncomingMessage; res: http.ServerRe...
    method postRouting (line 50) | async postRouting(pluginContext: exegesis.ExegesisPluginContext) {
    method postSecurity (line 56) | async postSecurity(pluginContext: exegesis.ExegesisPluginContext) {
    method postController (line 62) | async postController(context: exegesis.ExegesisContext) {
    method postResponseValidation (line 68) | async postResponseValidation(context: exegesis.ExegesisContext) {

FILE: src/core/exegesisRunner.ts
  function handleSecurity (line 25) | async function handleSecurity(operation: ResolvedOperation, context: Exe...
  function setDefaultContentType (line 36) | function setDefaultContentType(res: ExegesisResponse) {
  function resultToHttpResponse (line 53) | function resultToHttpResponse(context: ExegesisContext, result: any): Ht...
  function handleError (line 79) | function handleError(err: Error) {
  function generateExegesisRunner (line 115) | async function generateExegesisRunner<T>(

FILE: src/errors.ts
  class ExtendableError (line 3) | class ExtendableError extends Error {
    method constructor (line 4) | constructor(message: string) {
  class HttpError (line 15) | class HttpError extends ExtendableError {
    method constructor (line 18) | constructor(status: number, message: string) {
  class HttpBadRequestError (line 24) | class HttpBadRequestError extends HttpError {
    method constructor (line 25) | constructor(message: string) {
  class ValidationError (line 30) | class ValidationError extends HttpBadRequestError {
    method constructor (line 33) | constructor(errors: IValidationError[] | IValidationError) {
  class HttpNotFoundError (line 42) | class HttpNotFoundError extends HttpError {
    method constructor (line 43) | constructor(message: string) {
  class HttpPayloadTooLargeError (line 48) | class HttpPayloadTooLargeError extends HttpError {
    method constructor (line 49) | constructor(message: string) {
  function asError (line 57) | function asError(err: any): Error {

FILE: src/index.ts
  function bundle (line 34) | function bundle(openApiDocFile: string | unknown): Promise<any> {
  function compileDependencies (line 40) | async function compileDependencies(
  function compileApiInterface (line 81) | function compileApiInterface(
  function compileRunner (line 124) | function compileRunner(
  function writeHttpResult (line 169) | function writeHttpResult(
  function compileApi (line 218) | function compileApi(

FILE: src/oas3/Oas3CompileContext.ts
  type JsonPath (line 12) | type JsonPath = string[];
  type ReadOnlyJsonPath (line 13) | type ReadOnlyJsonPath = readonly string[];
  class Oas3CompileContext (line 20) | class Oas3CompileContext {
    method constructor (line 39) | constructor(a: any, path: ReadOnlyJsonPath, options?: ExegesisCompiled...
    method childContext (line 56) | childContext(relativePath: JsonPath | string) {
    method resolveRef (line 64) | resolveRef(ref: string | any) {

FILE: src/oas3/OpenApi.ts
  class OpenApi (line 27) | class OpenApi implements ApiInterface<OAS3ApiInfo> {
    method constructor (line 39) | constructor(openApiDoc: oas3.OpenAPIObject, options: ExegesisCompiledO...
    method resolve (line 64) | resolve(

FILE: src/oas3/Operation.ts
  constant METHODS_WITH_BODY (line 30) | const METHODS_WITH_BODY = ['post', 'put', 'patch', 'delete'];
  function isAuthenticationFailure (line 32) | function isAuthenticationFailure(result: any): result is AuthenticationF...
  function getMissing (line 36) | function getMissing(required: string[], have: string[] | undefined) {
  function validateController (line 44) | function validateController(
  function validateControllers (line 74) | function validateControllers(
  class Operation (line 93) | class Operation {
    method constructor (line 116) | constructor(
    method getRequestMediaType (line 204) | getRequestMediaType(contentType: string): RequestMediaType | undefined {
    method parseParameters (line 214) | parseParameters(params: {
    method validateParameters (line 231) | validateParameters(
    method validateResponse (line 264) | validateResponse(
    method _runAuthenticator (line 276) | private async _runAuthenticator(
    method _checkSecurityRequirement (line 350) | private async _checkSecurityRequirement(
    method authenticate (line 394) | async authenticate(

FILE: src/oas3/Parameter.ts
  constant DEFAULT_STYLE (line 10) | const DEFAULT_STYLE: { [style: string]: string } = {
  function getDefaultExplode (line 17) | function getDefaultExplode(style: string): boolean {
  function generateSchemaParser (line 21) | function generateSchemaParser(self: Parameter, schema: JSONSchema4 | JSO...
  class Parameter (line 38) | class Parameter {
    method constructor (line 51) | constructor(

FILE: src/oas3/Path.ts
  constant HTTP_METHODS (line 9) | const HTTP_METHODS = [
  type OperationsMap (line 20) | interface OperationsMap {
  class Path (line 24) | class Path {
    method constructor (line 29) | constructor(
    method getOperation (line 61) | getOperation(method: string): Operation | undefined {

FILE: src/oas3/Paths/PathResolver.ts
  constant TEMPLATE_RE (line 2) | const TEMPLATE_RE = /^(.*?){(.*?)}(.*)$/;
  type PathParserFunction (line 6) | type PathParserFunction = (
  function hasTemplates (line 14) | function hasTemplates(path: string): boolean {
  function compileTemplatePath (line 37) | function compileTemplatePath(
  class PathResolver (line 87) | class PathResolver<T> {
    method constructor (line 102) | constructor() {
    method registerPath (line 107) | registerPath(path: string, value: T) {
    method resolvePath (line 129) | resolvePath(urlPathname: string) {

FILE: src/oas3/Paths/index.ts
  type ResolvedPath (line 9) | interface ResolvedPath {
  class Paths (line 15) | class Paths {
    method constructor (line 18) | constructor(context: Oas3CompileContext, exegesisController: string | ...
    method resolvePath (line 51) | resolvePath(urlPathname: string): ResolvedPath | undefined {

FILE: src/oas3/RequestMediaType.ts
  function generateAddDefaultParser (line 9) | function generateAddDefaultParser(parser: BodyParser, def: any): BodyPar...
  class RequestMediaType (line 33) | class RequestMediaType {
    method constructor (line 39) | constructor(

FILE: src/oas3/Response.ts
  class Responses (line 8) | class Responses {
    method constructor (line 14) | constructor(context: Oas3CompileContext, response: oas3.ResponseObject) {
    method validateResponse (line 44) | validateResponse(

FILE: src/oas3/Responses.ts
  class Responses (line 6) | class Responses {
    method constructor (line 11) | constructor(context: Oas3CompileContext, responses: oas3.ResponsesObje...
    method validateResponse (line 26) | validateResponse(

FILE: src/oas3/Schema/validators.ts
  constant REQUEST_TYPE_COERCION_ALLOWED (line 14) | const REQUEST_TYPE_COERCION_ALLOWED = new MimeTypeRegistry<boolean>({
  function assertNever (line 26) | function assertNever(x: never): never {
  function getParameterDescription (line 30) | function getParameterDescription(parameterLocation: ParameterLocation) {
  function removeExamples (line 51) | function removeExamples(schema: any) {
  function _fixNullables (line 61) | function _fixNullables(schema: any) {
  function _filterRequiredProperties (line 97) | function _filterRequiredProperties(schema: any, propNameToFilter: string) {
  function doValidate (line 116) | function doValidate(
  function createValidateGetter (line 171) | function createValidateGetter(schema: any, ajv: Ajv, lazy: boolean): () ...
  function generateValidator (line 188) | function generateValidator(
  function generateRequestValidator (line 244) | function generateRequestValidator(
  function generateResponseValidator (line 262) | function generateResponseValidator(

FILE: src/oas3/SecuritySchemes.ts
  class SecuritySchemes (line 6) | class SecuritySchemes {
    method constructor (line 11) | constructor(openApiDoc: oas3.OpenAPIObject) {
    method getChallenge (line 44) | getChallenge(schemeName: string): string | undefined {
    method getInfo (line 48) | getInfo(schemeName: string): AuthenticatorInfo {

FILE: src/oas3/Servers.ts
  constant FULL_URL_RE (line 6) | const FULL_URL_RE = /^(.*?):\/\/([^/]*?)(?:\/(.*))?$/;
  constant ABSOLUTE_URL_RE (line 7) | const ABSOLUTE_URL_RE = /^(\/.*)$/;
  type ResolvedServer (line 9) | interface ResolvedServer {
  type ServerParser (line 26) | type ServerParser = (host: string, pathname: string) => ResolvedServer |...
  function generateServerParser (line 28) | function generateServerParser(oaServer: oas3.ServerObject): ServerParser {
  class Servers (line 102) | class Servers {
    method constructor (line 105) | constructor(servers: oas3.ServerObject[] | undefined) {
    method resolveServer (line 118) | resolveServer(host: string, pathname: string): ResolvedServer | null {

FILE: src/oas3/extensions.ts
  constant EXEGESIS_CONTROLLER (line 1) | const EXEGESIS_CONTROLLER = 'x-exegesis-controller';
  constant EXEGESIS_OPERATION_ID (line 2) | const EXEGESIS_OPERATION_ID = 'x-exegesis-operationId';

FILE: src/oas3/index.ts
  function compile (line 14) | async function compile(

FILE: src/oas3/oasUtils/index.ts
  function isSpecificationExtension (line 8) | function isSpecificationExtension(key: string) {
  function isReferenceObject (line 12) | function isReferenceObject(obj: any): obj is oas3.ReferenceObject {
  function contentToRequestMediaTypeRegistry (line 22) | function contentToRequestMediaTypeRegistry(

FILE: src/oas3/parameterParsers/common.ts
  function arrayToObject (line 3) | function arrayToObject(values: string | string[] | undefined) {
  function removeSimpleTypes (line 23) | function removeSimpleTypes(allowedTypes: string[]) {
  function allowedTypesToMap (line 37) | function allowedTypesToMap(allowedTypes: string[]): any {

FILE: src/oas3/parameterParsers/delimitedParser.ts
  function generateDelimitedParser (line 6) | function generateDelimitedParser(delimiter: string) {

FILE: src/oas3/parameterParsers/index.ts
  function isMediaTypeParameterDescriptor (line 22) | function isMediaTypeParameterDescriptor(
  function generateParser (line 28) | function generateParser(parameterDescriptor: ParameterDescriptor): Param...
  function generateMediaTypeParser (line 38) | function generateMediaTypeParser(
  function generateStyleParser (line 79) | function generateStyleParser(descriptor: StyledParameterDescriptor) {
  function setDefault (line 112) | function setDefault(parser: ParameterParser, def: any) {
  function toStructuredParser (line 128) | function toStructuredParser(parser: RawStringParameterParser) {
  function deepObjectParser (line 139) | function deepObjectParser(
  function _parseParameterGroup (line 152) | function _parseParameterGroup(
  function parseParameterGroup (line 167) | function parseParameterGroup(
  function parseQueryParameters (line 177) | function parseQueryParameters(

FILE: src/oas3/parameterParsers/pathStyleParser.ts
  function parsePathParameter (line 7) | function parsePathParameter(
  function generatePathStyleParser (line 21) | function generatePathStyleParser(schema: any, explode: boolean): Paramet...

FILE: src/oas3/parameterParsers/simpleStringParser.ts
  function getSimpleStringParser (line 5) | function getSimpleStringParser(schema: any, explode: boolean): RawString...
  function simpleStringParser (line 24) | function simpleStringParser(value: string | undefined): string | string[...
  function simpleArrayParser (line 29) | function simpleArrayParser(value: string | undefined): string[] | undefi...
  function simpleStringArrayParser (line 33) | function simpleStringArrayParser(value: string | undefined): string | st...
  function generateGenericSimpleParser (line 46) | function generateGenericSimpleParser(schema: any, explode: boolean) {

FILE: src/oas3/parameterParsers/structuredParser.ts
  function generateStructuredParser (line 20) | function generateStructuredParser(schema: any, explode: boolean): Parame...
  function structuredStringParser (line 38) | function structuredStringParser(
  function structuredArrayParser (line 53) | function structuredArrayParser(
  function explodedStructuredArrayParser (line 70) | function explodedStructuredArrayParser(
  function generateGenericStructuredParser (line 84) | function generateGenericStructuredParser(schema: any): ParameterParser {
  function explodedKeysStructuredParser (line 104) | function explodedKeysStructuredParser(values: RawValues) {
  function generateGenericExplodedStructuredParser (line 116) | function generateGenericExplodedStructuredParser(schema: any) {

FILE: src/oas3/parameterParsers/types.ts
  type StyledParameterDescriptor (line 6) | interface StyledParameterDescriptor {
  type MediaTypeParameterDescriptor (line 19) | interface MediaTypeParameterDescriptor {
  type ParameterDescriptor (line 27) | type ParameterDescriptor = StyledParameterDescriptor | MediaTypeParamete...
  type RawValues (line 38) | type RawValues = ParametersMap<string | string[] | undefined>;
  type RawStringParameterParser (line 43) | interface RawStringParameterParser {
  type ParameterParser (line 52) | interface ParameterParser {

FILE: src/oas3/urlEncodedBodyParser.ts
  function findProperty (line 22) | function findProperty(
  function generateStringParser (line 43) | function generateStringParser(
  function generateBodyParser (line 122) | function generateBodyParser(

FILE: src/options.ts
  type ExegesisCompiledOptions (line 25) | interface ExegesisCompiledOptions {
  function compileOptions (line 67) | function compileOptions(options: ExegesisOptions = {}): ExegesisCompiled...

FILE: src/types/basicTypes.ts
  type Callback (line 3) | type Callback<T> = (err?: Error | null | undefined, value?: T) => void;
  type MiddlewareFunction (line 5) | type MiddlewareFunction = (
  type Dictionary (line 11) | interface Dictionary<T> {
  type ParametersByLocation (line 18) | interface ParametersByLocation<T> {
  type ParametersMap (line 38) | interface ParametersMap<T> {
  type HttpIncomingMessage (line 42) | interface HttpIncomingMessage extends http.IncomingMessage {

FILE: src/types/bodyParser.ts
  type ReqParserFunction (line 5) | type ReqParserFunction = (
  type StringParserFunction (line 11) | type StringParserFunction = (encoded: string) => any;
  type StringParser (line 13) | interface StringParser {
  type BodyParser (line 24) | interface BodyParser {
  type MimeTypeParser (line 40) | interface MimeTypeParser extends StringParser, BodyParser {}

FILE: src/types/core.ts
  type HttpHeaders (line 10) | interface HttpHeaders {
  type ExegesisRoute (line 14) | interface ExegesisRoute {
  type ExegesisResponse (line 18) | interface ExegesisResponse {
  type ExegesisContextBase (line 66) | interface ExegesisContextBase {
  type ExegesisContext (line 84) | interface ExegesisContext extends ExegesisContextBase {
  type ExegesisPluginContext (line 93) | interface ExegesisPluginContext extends ExegesisContextBase {
  type OAS3ApiInfo (line 100) | interface OAS3ApiInfo {
  type PromiseController (line 112) | type PromiseController = (context: ExegesisContext) => any;
  type CallbackController (line 113) | type CallbackController = (context: ExegesisContext, done: Callback<any>...
  type Controller (line 114) | type Controller = PromiseController | CallbackController;
  type ControllerModule (line 116) | interface ControllerModule {
  type Controllers (line 120) | interface Controllers {
  type AuthenticationFailure (line 125) | interface AuthenticationFailure {
  type AuthenticationSuccess (line 132) | interface AuthenticationSuccess {
  type AuthenticationResult (line 140) | type AuthenticationResult = AuthenticationSuccess | AuthenticationFailure;
  type AuthenticatorInfo (line 142) | interface AuthenticatorInfo {
  type PromiseAuthenticator (line 148) | type PromiseAuthenticator = (
  type CallbackAuthenticator (line 152) | type CallbackAuthenticator = (
  type Authenticator (line 157) | type Authenticator = PromiseAuthenticator | CallbackAuthenticator;
  type Authenticators (line 158) | interface Authenticators {
  type HttpResult (line 165) | interface HttpResult {
  type ExegesisRunner (line 178) | type ExegesisRunner = (
  type ParsedParameterValidator (line 183) | type ParsedParameterValidator = (
  type ResolvedOperation (line 187) | interface ResolvedOperation {
  type ResolvedPath (line 209) | interface ResolvedPath<T> {
  type ApiInterface (line 229) | interface ApiInterface<T> {
  type ExegesisPluginInstance (line 245) | interface ExegesisPluginInstance {
  type ExegesisPlugin (line 325) | interface ExegesisPlugin {

FILE: src/types/options.ts
  type CustomFormatChecker (line 9) | type CustomFormatChecker = RegExp | ((value: string) => boolean);
  type HandleErrorFunction (line 11) | type HandleErrorFunction = (err: Error, context: { req: http.IncomingMes...
  type StringCustomFormatChecker (line 13) | interface StringCustomFormatChecker {
  type NumberCustomFormatChecker (line 18) | interface NumberCustomFormatChecker {
  type CustomFormats (line 33) | interface CustomFormats {
  type ExegesisOptions (line 40) | interface ExegesisOptions {

FILE: src/types/validation.ts
  type ParameterLocationIn (line 4) | type ParameterLocationIn =
  type ParameterLocation (line 25) | interface ParameterLocation {
  type ParameterLocations (line 35) | interface ParameterLocations {
  type IValidationError (line 52) | interface IValidationError {
  type ValidatorFunction (line 69) | interface ValidatorFunction {
  type ResponseValidationResult (line 76) | interface ResponseValidationResult {
  type ResponseValidationCallback (line 86) | interface ResponseValidationCallback {

FILE: src/utils/bufferToStream.ts
  function bufferToStream (line 3) | function bufferToStream(buf: Buffer) {

FILE: src/utils/httpUtils.ts
  function httpHasBody (line 1) | function httpHasBody(headers: { [header: string]: any }): boolean {
  constant HTTP_METHODS_WITHOUT_BODY (line 10) | const HTTP_METHODS_WITHOUT_BODY = ['get', 'head', 'trace', 'options'];
  function requestMayHaveBody (line 12) | function requestMayHaveBody(method: string) {

FILE: src/utils/json-schema-infer-types.ts
  constant VALID_SCHEMA_TYPES (line 4) | const VALID_SCHEMA_TYPES = ['null', 'boolean', 'object', 'array', 'numbe...
  constant ALL_ALLOWED_TYPES (line 6) | const ALL_ALLOWED_TYPES = new Set(VALID_SCHEMA_TYPES);
  constant NO_ALLOWED_TYPES (line 7) | const NO_ALLOWED_TYPES = new Set<string>([]);
  function getType (line 9) | function getType(val: any): string {
  function toArray (line 29) | function toArray(val: string | string[]) {
  function union (line 37) | function union<T>(a: Set<T>, b: Set<T>) {
  function intersection (line 41) | function intersection<T>(a: Set<T>, b: Set<T>) {
  function inferTypesOneOf (line 45) | function inferTypesOneOf(rootSchema: any, oneOf: any[], stack: any[]): S...
  function inferTypesPriv (line 60) | function inferTypesPriv(
  function inferTypes (line 121) | function inferTypes(

FILE: src/utils/json-schema-resolve-ref.ts
  function resolveRefPriv (line 3) | function resolveRefPriv(document: any, ref: string): any {
  function resolveRef (line 21) | function resolveRef(document: any, ref: string | any): any | undefined {

FILE: src/utils/jsonPaths.ts
  function normalize (line 3) | function normalize(path: string): string {
  function toUriFragment (line 7) | function toUriFragment(path: string) {
  function jsonPointerStartsWith (line 11) | function jsonPointerStartsWith(path: string, prefix: string): boolean {
  function jsonPointerStripPrefix (line 17) | function jsonPointerStripPrefix(path: string, prefix: string): string {

FILE: src/utils/jsonSchema.ts
  function extractSchemaPriv (line 8) | function extractSchemaPriv(
  function extractSchema (line 105) | function extractSchema(

FILE: src/utils/mime.ts
  constant TCHAR (line 2) | const TCHAR = "[!#$%&'*+-.^_`|~A-Za-z0-9]";
  constant TOKEN (line 3) | const TOKEN = `${TCHAR}+`;
  constant OWS (line 4) | const OWS = '[ \t]*';
  constant MIME_TYPE_REGEX (line 5) | const MIME_TYPE_REGEX = new RegExp(`^(${TOKEN})/(${TOKEN})${OWS}(.*)$`);
  type ParsedMimeType (line 7) | interface ParsedMimeType {
  function parseMimeType (line 16) | function parseMimeType(mimeType: string): ParsedMimeType {
  function isParsedMimeType (line 27) | function isParsedMimeType(val: string | ParsedMimeType): val is ParsedMi...
  class MimeTypeRegistry (line 31) | class MimeTypeRegistry<T> {
    method constructor (line 39) | constructor(map?: { [mimeType: string]: T | undefined } | undefined) {
    method set (line 50) | set(mimeType: string | ParsedMimeType, value: T) {
    method get (line 66) | get(mimeType: string | ParsedMimeType): T | undefined {
    method getRegisteredTypes (line 76) | getRegisteredTypes() {

FILE: src/utils/stringToStream.ts
  function stringToStream (line 3) | function stringToStream(str: string, encoding: BufferEncoding = 'utf-8') {

FILE: src/utils/typeUtils.ts
  function isReadable (line 3) | function isReadable(obj: any): obj is Readable {

FILE: test/core/ExegesisResponseImplTest.ts
  class StringWrapper (line 7) | class StringWrapper {
    method constructor (line 10) | constructor(content: string) {
    method toJSON (line 14) | public toJSON() {
    method constructor (line 34) | constructor(content: string) {
    method toJSON (line 38) | public toJSON() {
  class StringWrapper (line 31) | class StringWrapper {
    method constructor (line 10) | constructor(content: string) {
    method toJSON (line 14) | public toJSON() {
    method constructor (line 34) | constructor(content: string) {
    method toJSON (line 38) | public toJSON() {

FILE: test/core/pluginTest.ts
  method preCompile (line 22) | preCompile() {
  method preRouting (line 26) | preRouting() {
  method postRouting (line 30) | postRouting() {
  method postSecurity (line 34) | postSecurity() {
  method postController (line 38) | postController() {
  method postResponseValidation (line 42) | postResponseValidation() {
  method makeExegesisPlugin (line 51) | makeExegesisPlugin() {

FILE: test/fixtures/FakeApiInterface.ts
  class FakeApiInterface (line 4) | class FakeApiInterface implements ApiInterface<void> {
    method constructor (line 6) | constructor(controller: Controller) {
    method resolve (line 10) | resolve(_method: string, url: string): ResolvedPath<void> | undefined {

FILE: test/fixtures/FakeExegesisContext.ts
  class FakeExegesisContext (line 16) | class FakeExegesisContext implements ExegesisContext {
    method constructor (line 43) | constructor() {
    method makeError (line 49) | makeError(statusCode: number, message: string): Error {
    method makeValidationError (line 53) | makeValidationError(message: string, location: ParameterLocation) {
    method isResponseFinished (line 60) | isResponseFinished() {

FILE: test/fixtures/index.ts
  function makeOpenApiDoc (line 8) | function makeOpenApiDoc(): oas3.OpenAPIObject {
  function makeContext (line 19) | function makeContext(

FILE: test/integration/integration/controllers/echoController.ts
  function echo (line 3) | function echo(context: exegesis.ExegesisContext) {

FILE: test/integration/integration/controllers/greetController.ts
  function greetGet (line 3) | function greetGet(context: ExegesisContext) {

FILE: test/integration/integration/controllers/malformed.ts
  function getBadResponse (line 1) | function getBadResponse() {

FILE: test/integration/integration/controllers/postWithDefault.ts
  function postWithDefault (line 3) | function postWithDefault(context: ExegesisContext) {

FILE: test/integration/integration/controllers/postWithOptionalBody.ts
  function postWithOptionalBody (line 3) | function postWithOptionalBody(context: ExegesisContext) {

FILE: test/integration/integration/controllers/statusController.ts
  function setStatus (line 3) | function setStatus(context: ExegesisContext) {
  function status (line 7) | function status(context: ExegesisContext) {

FILE: test/integration/integration/controllers/streamController.ts
  function replyWithStream (line 4) | function replyWithStream(context: exegesis.ExegesisContext) {

FILE: test/integration/integration/customErrorFormattingTest.ts
  function sessionAuthenticator (line 7) | async function sessionAuthenticator(
  function createServer (line 31) | async function createServer() {

FILE: test/integration/integration/customErrorHandler.ts
  function handleError (line 4) | function handleError(err: Error) {

FILE: test/integration/integration/integrationTest.ts
  function sessionAuthenticator (line 7) | async function sessionAuthenticator(
  function createServer (line 31) | async function createServer(options: exegesis.ExegesisOptions) {

FILE: test/oas3/OperationTest.ts
  constant BASIC_AUTH_SUCCESS_RESULT (line 18) | const BASIC_AUTH_SUCCESS_RESULT = { type: 'success', user: 'benbria' };
  constant DEFAULT_OAUTH_RESULT (line 19) | const DEFAULT_OAUTH_RESULT = { type: 'success', user: 'benbria', scopes:...
  function makeOperation (line 21) | function makeOperation(
  method oauth (line 120) | oauth() {
  method oauth (line 138) | oauth() {
  method oauth (line 167) | oauth() {
  method basicAuth (line 183) | basicAuth() {
  method oauth (line 186) | oauth() {
  method basicAuth (line 208) | basicAuth() {
  method oauth (line 211) | oauth() {
  method basicAuth (line 233) | basicAuth() {
  method oauth (line 236) | oauth() {
  method basicAuth (line 264) | basicAuth() {
  method oauth (line 267) | oauth() {
  method basicAuth (line 295) | basicAuth() {
  method oauth (line 298) | oauth() {
  method basicAuth (line 327) | basicAuth() {
  method oauth (line 330) | oauth() {
  method basicAuth (line 353) | basicAuth() {
  method oauth (line 356) | oauth() {
  method basicAuth (line 384) | basicAuth() {
  method oauth (line 387) | oauth() {
  method basicAuth (line 415) | basicAuth() {
  method oauth (line 418) | oauth() {
  method read (line 676) | read() {
  method read (line 705) | read() {
  method transform (line 712) | transform(chunk, _encoding, callback) {

FILE: test/oas3/Paths/PathsTest.ts
  constant DUMMY_PATH_OBJECT (line 8) | const DUMMY_PATH_OBJECT = {

FILE: test/oas3/ResponsesTest.ts
  constant DEFAULT_RESPONSE (line 15) | const DEFAULT_RESPONSE: ResponsesObject = {
  constant RESPONSE_WITH_MULTIPLE_CONTENT_TYPES (line 49) | const RESPONSE_WITH_MULTIPLE_CONTENT_TYPES = {
  constant RESPONSE_WITH_NO_SCHEMA (line 75) | const RESPONSE_WITH_NO_SCHEMA = {
  constant RESPONSE_WITH_TEXT_PLAIN (line 84) | const RESPONSE_WITH_TEXT_PLAIN = {
  function makeResponses (line 93) | function makeResponses(responses: oas3.ResponsesObject) {

FILE: test/oas3/Schema/validatorsTest.ts
  constant REQUEST_BODY_LOCATION (line 87) | const REQUEST_BODY_LOCATION: ParameterLocation = {
  constant QUERY_PARAM_LOCATION (line 93) | const QUERY_PARAM_LOCATION: ParameterLocation = {

FILE: test/oas3/oas3ControllersTest.ts
  function generateOpenApi (line 15) | function generateOpenApi(): oas3.OpenAPIObject {
  method otherOp (line 49) | otherOp() {
  method op (line 52) | op() {
  function findControllerTest (line 63) | async function findControllerTest(

FILE: test/oas3/oas3ParametersTest.ts
  function generateOpenApi (line 12) | function generateOpenApi(paths: oas3.PathObject): OpenApi {
Condensed preview — 136 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (445K chars).
[
  {
    "path": ".eslintrc.js",
    "chars": 631,
    "preview": "module.exports = {\n    parser: '@typescript-eslint/parser',\n    parserOptions: {\n        project: ['./tsconfig.json', '."
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 171,
    "preview": "version: 2\nupdates:\n    - package-ecosystem: npm\n      directory: '/'\n      schedule:\n          interval: daily\n        "
  },
  {
    "path": ".github/workflows/github-ci.yaml",
    "chars": 843,
    "preview": "name: GitHub CI\non: [push]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: ["
  },
  {
    "path": ".gitignore",
    "chars": 192,
    "preview": "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_St"
  },
  {
    "path": ".husky/.gitignore",
    "chars": 2,
    "preview": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 67,
    "preview": "npx --no-install pretty-quick --staged\nnpx --no-install lint-staged"
  },
  {
    "path": ".markdownlint.json",
    "chars": 216,
    "preview": "{\n    \"line-length\": false,\n    \"no-inline-html\": {\n      \"allowed_elements\": [\n        \"a\"\n      ]\n    },\n    \"no-trail"
  },
  {
    "path": ".mocharc.js",
    "chars": 186,
    "preview": "module.exports = {\n    extension: ['js', 'jsx', 'ts', 'tsx'],\n    require: ['ts-node/register'],\n    recursive: true,\n  "
  },
  {
    "path": ".npmrc",
    "chars": 19,
    "preview": "package-lock=false\n"
  },
  {
    "path": ".nycrc",
    "chars": 157,
    "preview": "{\n    \"extension\": [\".ts\"],\n    \"exclude\": [\"**/*.d.ts\"],\n    \"include\": [\"src/**/*.ts\"],\n    \"reporter\": [\"html\", \"text"
  },
  {
    "path": ".prettierrc.js",
    "chars": 381,
    "preview": "module.exports = {\n    trailingComma: 'es5',\n    printWidth: 100,\n    tabWidth: 4,\n    semi: true,\n    singleQuote: true"
  },
  {
    "path": ".releaserc.yml",
    "chars": 43,
    "preview": "extends: '@jwalton/semantic-release-config'"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 857,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 59,
    "preview": "{\n    \"typescript.tsdk\": \"./node_modules/typescript/lib/\"\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 544,
    "preview": " {\n    // See https://go.microsoft.com/fwlink/?LinkId=733558\n    // for the documentation about the tasks.json format\n  "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 598,
    "preview": "# Contributing\n\nThis project uses [semantic-release](https://github.com/semantic-release/semantic-release)\nso commit mes"
  },
  {
    "path": "LICENSE",
    "chars": 1053,
    "preview": "Copyright 2018 Jason Walton \n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis softw"
  },
  {
    "path": "README.md",
    "chars": 6837,
    "preview": "# Exegesis OpenAPI Engine\n\n[![NPM version](https://badge.fury.io/js/exegesis.svg)](https://npmjs.org/package/exegesis)\n!"
  },
  {
    "path": "docs/Exegesis Controllers.md",
    "chars": 6987,
    "preview": "# Introduction to Controllers\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Introduction to Controll"
  },
  {
    "path": "docs/Exegesis Plugins.md",
    "chars": 7040,
    "preview": "# Plugins\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Plugins](#plugins)\n  - [Writing a Plugin](#w"
  },
  {
    "path": "docs/OAS3 Parameter Parsing.md",
    "chars": 6557,
    "preview": "# Parameter Parsing\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Parameter Parsing](#parameter-pars"
  },
  {
    "path": "docs/OAS3 Security.md",
    "chars": 9512,
    "preview": "# OAS3 Security\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [OAS3 Security](#oas3-security)\n  - [Au"
  },
  {
    "path": "docs/OAS3 Specification Extensions.md",
    "chars": 1801,
    "preview": "# Controllers\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Controllers](#controllers)\n  - [x-exeges"
  },
  {
    "path": "docs/Options.md",
    "chars": 9893,
    "preview": "# Exegesis Options\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Exegesis Options](#exegesis-options"
  },
  {
    "path": "docs/Tutorial.md",
    "chars": 8718,
    "preview": "# Exegesis Tutorial\n\n<!-- markdownlint-disable MD007 -->\n<!-- TOC depthFrom:2 -->\n\n- [Exegesis Tutorial](#exegesis-tutor"
  },
  {
    "path": "package.json",
    "chars": 3115,
    "preview": "{\n    \"name\": \"exegesis\",\n    \"version\": \"0.0.0-semantic-release\",\n    \"description\": \"Parses OpenAPI documents\",\n    \"m"
  },
  {
    "path": "samples/javascript-example/controllers/greetController.js",
    "chars": 139,
    "preview": "exports.getGreeting = function getGreeting(context) {\n    const name = context.params.query.name;\n    return {message: `"
  },
  {
    "path": "samples/javascript-example/index.js",
    "chars": 1359,
    "preview": "const express = require('express');\nconst exegesisExpress = require('exegesis-express');\nconst http = require('http');\nc"
  },
  {
    "path": "samples/javascript-example/openapi.yaml",
    "chars": 990,
    "preview": "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      o"
  },
  {
    "path": "samples/javascript-example/package.json",
    "chars": 261,
    "preview": "{\n  \"name\": \"exegesis-tutorial\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Exegesis tutorial sample\",\n  \"main\": \"index.js\""
  },
  {
    "path": "samples/typescript-example/README.md",
    "chars": 653,
    "preview": "#Exegesis-Typescript-Example\n\nA sample setup of a Typescript based API which uses Exegesis to work with the OpenAPI spec"
  },
  {
    "path": "samples/typescript-example/controllers/greetController.ts",
    "chars": 500,
    "preview": "/**\n * Here a simple function is exported.\n * \n * If you wanted a class with its own state and functions you will need s"
  },
  {
    "path": "samples/typescript-example/openapi.yaml",
    "chars": 945,
    "preview": "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      o"
  },
  {
    "path": "samples/typescript-example/package.json",
    "chars": 440,
    "preview": "{\n  \"name\": \"exegesis-typescript-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\":"
  },
  {
    "path": "samples/typescript-example/server.ts",
    "chars": 1947,
    "preview": "//Import librarys used\nimport * as express from 'express';\nimport * as exegesisExpress from 'exegesis-express';\nimport *"
  },
  {
    "path": "samples/typescript-example/tsconfig.json",
    "chars": 338,
    "preview": "// tsconfig.json\n{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"moduleResolution\": \"node\",\n        \"p"
  },
  {
    "path": "src/bodyParsers/BodyParserWrapper.ts",
    "chars": 1491,
    "preview": "import http from 'http';\nimport contentType from 'content-type';\nimport getRawBody from 'raw-body';\n\nimport { httpHasBod"
  },
  {
    "path": "src/bodyParsers/JsonBodyParser.ts",
    "chars": 785,
    "preview": "import http from 'http';\nimport expressBodyParser from 'body-parser';\n\nimport { MimeTypeParser, ReqParserFunction, Callb"
  },
  {
    "path": "src/bodyParsers/TextBodyParser.ts",
    "chars": 773,
    "preview": "import http from 'http';\nimport expressBodyParser from 'body-parser';\n\nimport { MimeTypeParser, ReqParserFunction, Callb"
  },
  {
    "path": "src/controllers/invoke.ts",
    "chars": 970,
    "preview": "import pb from 'promise-breaker';\nimport { Controller, ControllerModule, ExegesisContext } from '../types';\nimport { isR"
  },
  {
    "path": "src/controllers/loadControllers.ts",
    "chars": 2057,
    "preview": "import fs from 'fs';\nimport * as glob from 'glob';\nimport path from 'path';\n\nimport { Controllers, ControllerModule } fr"
  },
  {
    "path": "src/core/ExegesisContextImpl.ts",
    "chars": 5912,
    "preview": "import * as http from 'http';\nimport pb from 'promise-breaker';\nimport deepFreeze from 'deep-freeze';\nimport {\n    Param"
  },
  {
    "path": "src/core/ExegesisResponseImpl.ts",
    "chars": 4291,
    "preview": "import * as http from 'http';\nimport * as net from 'net';\nimport * as types from '../types';\nimport { HttpHeaders } from"
  },
  {
    "path": "src/core/PluginsManager.ts",
    "chars": 2857,
    "preview": "import * as exegesis from '../types';\nimport pb from 'promise-breaker';\nimport http from 'http';\n\nfunction callFn(\n    p"
  },
  {
    "path": "src/core/exegesisRunner.ts",
    "chars": 8414,
    "preview": "import * as http from 'http';\nimport { Readable } from 'stream';\nimport { asError, HttpError } from '../errors';\n\nimport"
  },
  {
    "path": "src/errors.ts",
    "chars": 1657,
    "preview": "import { IValidationError } from './types';\n\nexport class ExtendableError extends Error {\n    constructor(message: strin"
  },
  {
    "path": "src/index.ts",
    "chars": 8687,
    "preview": "import * as http from 'http';\nimport * as oas3 from 'openapi3-ts';\nimport pb from 'promise-breaker';\nimport { pipeline }"
  },
  {
    "path": "src/oas3/Oas3CompileContext.ts",
    "chars": 2248,
    "preview": "import * as ld from 'lodash';\n\nimport * as oas3 from 'openapi3-ts';\nimport * as jsonPtr from 'json-ptr';\nimport { resolv"
  },
  {
    "path": "src/oas3/OpenApi.ts",
    "chars": 7794,
    "preview": "import { parse as parseUrl } from 'url';\nimport * as semver from 'semver';\nimport * as http from 'http';\nimport * as oas"
  },
  {
    "path": "src/oas3/Operation.ts",
    "chars": 18992,
    "preview": "import deepFreeze from 'deep-freeze';\nimport ld from 'lodash';\nimport pb from 'promise-breaker';\nimport * as oas3 from '"
  },
  {
    "path": "src/oas3/Parameter.ts",
    "chars": 4828,
    "preview": "import { JSONSchema4, JSONSchema6 } from 'json-schema';\nimport { ParameterLocation, ValidatorFunction, oas3 } from '../t"
  },
  {
    "path": "src/oas3/Path.ts",
    "chars": 1867,
    "preview": "import Operation from './Operation';\n\nimport Oas3CompileContext from './Oas3CompileContext';\nimport * as oas3 from 'open"
  },
  {
    "path": "src/oas3/Paths/PathResolver.ts",
    "chars": 5057,
    "preview": "import { escapeRegExp } from 'lodash';\nconst TEMPLATE_RE = /^(.*?){(.*?)}(.*)$/;\n\nimport { ParametersMap } from '../../t"
  },
  {
    "path": "src/oas3/Paths/index.ts",
    "chars": 2279,
    "preview": "import { isSpecificationExtension } from '../oasUtils';\nimport Oas3CompileContext from '../Oas3CompileContext';\nimport P"
  },
  {
    "path": "src/oas3/README.md",
    "chars": 429,
    "preview": "# oas3\n\nThis represents a \"parsed\" OpenAPI 3.x.x document.\n\nThis is a rough view of the heirachy of objects in an OpenAp"
  },
  {
    "path": "src/oas3/RequestMediaType.ts",
    "chars": 3093,
    "preview": "import ld from 'lodash';\nimport * as oas3 from 'openapi3-ts';\n\nimport { ValidatorFunction, ParameterLocation, BodyParser"
  },
  {
    "path": "src/oas3/Response.ts",
    "chars": 4787,
    "preview": "import * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport { ValidatorFunction, "
  },
  {
    "path": "src/oas3/Responses.ts",
    "chars": 1953,
    "preview": "import * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from './Oas3CompileContext';\nimport Response from './Resp"
  },
  {
    "path": "src/oas3/Schema/validators.ts",
    "chars": 8784,
    "preview": "import Ajv, { ValidateFunction } from 'ajv';\nimport traveseSchema from 'json-schema-traverse';\nimport { IValidationError"
  },
  {
    "path": "src/oas3/SecuritySchemes.ts",
    "chars": 1789,
    "preview": "import ld from 'lodash';\nimport * as oas3 from 'openapi3-ts';\nimport { AuthenticatorInfo } from '..';\nimport { resolveRe"
  },
  {
    "path": "src/oas3/Servers.ts",
    "chars": 4310,
    "preview": "import * as oas3 from 'openapi3-ts';\nimport { compileTemplatePath, PathParserFunction } from './Paths/PathResolver';\n\nim"
  },
  {
    "path": "src/oas3/extensions.ts",
    "chars": 123,
    "preview": "export const EXEGESIS_CONTROLLER = 'x-exegesis-controller';\nexport const EXEGESIS_OPERATION_ID = 'x-exegesis-operationId"
  },
  {
    "path": "src/oas3/index.ts",
    "chars": 501,
    "preview": "import oas3 from 'openapi3-ts';\n\nimport OpenApi from './OpenApi';\nimport { ExegesisCompiledOptions } from '../options';\n"
  },
  {
    "path": "src/oas3/oasUtils/index.ts",
    "chars": 1352,
    "preview": "import { MimeTypeRegistry } from '../../utils/mime';\nimport RequestMediaType from '../RequestMediaType';\n\nimport * as oa"
  },
  {
    "path": "src/oas3/parameterParsers/README.md",
    "chars": 3342,
    "preview": "# Parameter Parsing\n\nCookie parameters are not well defined by OAS3, so at the moment Exegesis\ndoesn't support them. See"
  },
  {
    "path": "src/oas3/parameterParsers/common.ts",
    "chars": 1057,
    "preview": "import ld from 'lodash';\n\nexport function arrayToObject(values: string | string[] | undefined) {\n    if (!values) {\n    "
  },
  {
    "path": "src/oas3/parameterParsers/delimitedParser.ts",
    "chars": 1003,
    "preview": "// Implements 'spaceDelimited' and 'pipeDelimited' from OAS 3.\n\nimport { RawValues } from './types';\nimport * as exegesi"
  },
  {
    "path": "src/oas3/parameterParsers/index.ts",
    "chars": 5801,
    "preview": "import ld from 'lodash';\nimport querystring from 'querystring';\nimport qs from 'qs';\n\nimport { ParametersMap, ParameterL"
  },
  {
    "path": "src/oas3/parameterParsers/pathStyleParser.ts",
    "chars": 1477,
    "preview": "import querystring from 'querystring';\n\nimport { ParameterParser, RawValues } from './types';\nimport { generateStructure"
  },
  {
    "path": "src/oas3/parameterParsers/simpleStringParser.ts",
    "chars": 2764,
    "preview": "import inferTypes from '../../utils/json-schema-infer-types';\nimport { arrayToObject, removeSimpleTypes, allowedTypesToM"
  },
  {
    "path": "src/oas3/parameterParsers/structuredParser.ts",
    "chars": 5098,
    "preview": "import ld from 'lodash';\n\nimport { ParameterParser, RawValues } from './types';\nimport { removeSimpleTypes, allowedTypes"
  },
  {
    "path": "src/oas3/parameterParsers/types.ts",
    "chars": 1701,
    "preview": "import { ParametersMap, ParameterLocation, StringParser } from '../../types';\n\n/**\n * A descriptor for a parameter that "
  },
  {
    "path": "src/oas3/urlEncodedBodyParser.ts",
    "chars": 4681,
    "preview": "import * as oas3 from 'openapi3-ts';\nimport * as jsonPtr from 'json-ptr';\nimport querystring from 'querystring';\nimport "
  },
  {
    "path": "src/options.ts",
    "chars": 5444,
    "preview": "import ajvFormats from 'ajv-formats';\nimport ld from 'lodash';\nimport BodyParserWrapper from './bodyParsers/BodyParserWr"
  },
  {
    "path": "src/types/basicTypes.ts",
    "chars": 986,
    "preview": "import * as http from 'http';\n\nexport type Callback<T> = (err?: Error | null | undefined, value?: T) => void;\n\nexport ty"
  },
  {
    "path": "src/types/bodyParser.ts",
    "chars": 1445,
    "preview": "import * as http from 'http';\nimport { Callback, HttpIncomingMessage } from './basicTypes';\n\n// Stolen from @types/conne"
  },
  {
    "path": "src/types/core.ts",
    "chars": 11988,
    "preview": "import * as http from 'http';\nimport * as net from 'net';\nimport * as oas3 from 'openapi3-ts';\nimport { Readable } from "
  },
  {
    "path": "src/types/index.ts",
    "chars": 197,
    "preview": "import * as oas3 from 'openapi3-ts';\n\nexport { oas3 };\n\nexport * from './bodyParser';\nexport * from './basicTypes';\nexpo"
  },
  {
    "path": "src/types/options.ts",
    "chars": 6621,
    "preview": "import * as http from 'http';\nimport { BodyParser, StringParser } from './bodyParser';\nimport { Authenticators, Controll"
  },
  {
    "path": "src/types/validation.ts",
    "chars": 2897,
    "preview": "import { ExegesisContext } from '.';\nimport * as ajv from 'ajv';\n\nexport type ParameterLocationIn =\n    | 'path'\n    | '"
  },
  {
    "path": "src/utils/bufferToStream.ts",
    "chars": 211,
    "preview": "import { Readable } from 'stream';\n\nexport default function bufferToStream(buf: Buffer) {\n    return new Readable({\n    "
  },
  {
    "path": "src/utils/httpUtils.ts",
    "chars": 568,
    "preview": "export function httpHasBody(headers: { [header: string]: any }): boolean {\n    const contentLength = headers['content-le"
  },
  {
    "path": "src/utils/json-schema-infer-types.ts",
    "chars": 4077,
    "preview": "import { resolveRef } from './json-schema-resolve-ref';\nimport { JSONSchema4, JSONSchema6 } from 'json-schema';\n\nconst V"
  },
  {
    "path": "src/utils/json-schema-resolve-ref.ts",
    "chars": 996,
    "preview": "import * as jsonPtr from 'json-ptr';\n\nfunction resolveRefPriv(document: any, ref: string): any {\n    if (!ref.startsWith"
  },
  {
    "path": "src/utils/jsonPaths.ts",
    "chars": 900,
    "preview": "import * as jsonPtr from 'json-ptr';\n\nfunction normalize(path: string): string {\n    return jsonPtr.encodePointer(jsonPt"
  },
  {
    "path": "src/utils/jsonSchema.ts",
    "chars": 4451,
    "preview": "import ld from 'lodash';\nimport traveseSchema from 'json-schema-traverse';\nimport * as jsonPaths from './jsonPaths';\nimp"
  },
  {
    "path": "src/utils/mime.ts",
    "chars": 2937,
    "preview": "// A mime-type, per RFC 7231 section 3.1.1.1\nconst TCHAR = \"[!#$%&'*+-.^_`|~A-Za-z0-9]\";\nconst TOKEN = `${TCHAR}+`;\ncons"
  },
  {
    "path": "src/utils/stringToStream.ts",
    "chars": 257,
    "preview": "import { Readable } from 'stream';\n\nexport default function stringToStream(str: string, encoding: BufferEncoding = 'utf-"
  },
  {
    "path": "src/utils/typeUtils.ts",
    "chars": 156,
    "preview": "import { Readable } from 'stream';\n\nexport function isReadable(obj: any): obj is Readable {\n    return obj && obj.pipe &"
  },
  {
    "path": "test/controllers/fixtures/badControllers/a.md",
    "chars": 47,
    "preview": "# This is not javascript, it's a markdown file\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/a.js",
    "chars": 23,
    "preview": "exports.a = () => 'a';\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/b/c.js",
    "chars": 23,
    "preview": "exports.c = () => 'c';\n"
  },
  {
    "path": "test/controllers/fixtures/controllers/d/index.js",
    "chars": 23,
    "preview": "exports.d = () => 'd';\n"
  },
  {
    "path": "test/controllers/fixtures/shadow/a/index.js",
    "chars": 27,
    "preview": "exports.a = () => 'index';\n"
  },
  {
    "path": "test/controllers/fixtures/shadow/a.js",
    "chars": 23,
    "preview": "exports.a = () => 'a';\n"
  },
  {
    "path": "test/controllers/loadControllersTest.ts",
    "chars": 2088,
    "preview": "import path from 'path';\nimport { expect } from 'chai';\n\nimport * as load from '../../src/controllers/loadControllers';\n"
  },
  {
    "path": "test/core/ExegesisResponseImplTest.ts",
    "chars": 1992,
    "preview": "import { expect } from 'chai';\nimport ExegesisResponseImpl from '../../lib/core/ExegesisResponseImpl';\n\ndescribe('Exeges"
  },
  {
    "path": "test/core/pluginTest.ts",
    "chars": 2199,
    "preview": "import chai from 'chai';\nimport http from 'http';\nimport 'mocha';\nimport { ExegesisPlugin, ExegesisPluginInstance } from"
  },
  {
    "path": "test/fixtures/FakeApiInterface.ts",
    "chars": 1601,
    "preview": "import { ApiInterface, ResolvedPath } from '../../src';\nimport { Controller } from '../../lib/types';\n\nexport class Fake"
  },
  {
    "path": "test/fixtures/FakeExegesisContext.ts",
    "chars": 1702,
    "preview": "import * as http from 'http';\nimport {\n    ExegesisContext,\n    ExegesisResponse,\n    AuthenticationSuccess,\n    Paramet"
  },
  {
    "path": "test/fixtures/index.ts",
    "chars": 839,
    "preview": "import * as jsonPtr from 'json-ptr';\nimport * as oas3 from 'openapi3-ts';\nimport Oas3CompileContext from '../../src/oas3"
  },
  {
    "path": "test/integration/integration/controllers/echoController.ts",
    "chars": 135,
    "preview": "import * as exegesis from '../../../../src';\n\nexport function echo(context: exegesis.ExegesisContext) {\n    return conte"
  },
  {
    "path": "test/integration/integration/controllers/greetController.ts",
    "chars": 194,
    "preview": "import { ExegesisContext } from '../../../../src';\n\nexport function greetGet(context: ExegesisContext) {\n    const { nam"
  },
  {
    "path": "test/integration/integration/controllers/malformed.ts",
    "chars": 60,
    "preview": "export function getBadResponse() {\n    return { wat: 7 };\n}\n"
  },
  {
    "path": "test/integration/integration/controllers/postWithDefault.ts",
    "chars": 200,
    "preview": "import { ExegesisContext } from '../../../../src';\n\nexport function postWithDefault(context: ExegesisContext) {\n    cons"
  },
  {
    "path": "test/integration/integration/controllers/postWithOptionalBody.ts",
    "chars": 186,
    "preview": "import { ExegesisContext } from '../../../../src';\n\nexport function postWithOptionalBody(context: ExegesisContext) {\n   "
  },
  {
    "path": "test/integration/integration/controllers/secureController.js",
    "chars": 137,
    "preview": "exports.secureGet = function secureGet(context) {\n    return {\n        security: context.security,\n        user: context"
  },
  {
    "path": "test/integration/integration/controllers/statusController.ts",
    "chars": 223,
    "preview": "import { ExegesisContext } from '../../../../src';\n\nexport function setStatus(context: ExegesisContext) {\n    context.re"
  },
  {
    "path": "test/integration/integration/controllers/streamController.ts",
    "chars": 390,
    "preview": "import { Readable } from 'stream';\nimport * as exegesis from '../../../../src';\n\nexport function replyWithStream(context"
  },
  {
    "path": "test/integration/integration/customErrorFormattingTest.ts",
    "chars": 5556,
    "preview": "import * as http from 'http';\nimport * as path from 'path';\nimport { makeFetch } from 'supertest-fetch';\nimport * as exe"
  },
  {
    "path": "test/integration/integration/customErrorHandler.ts",
    "chars": 1793,
    "preview": "import stringToStream from '../../../src/utils/stringToStream';\nimport { ValidationError } from '../../../src/errors';\n\n"
  },
  {
    "path": "test/integration/integration/integrationTest.ts",
    "chars": 10494,
    "preview": "import { expect } from 'chai';\nimport * as http from 'http';\nimport * as path from 'path';\nimport { makeFetch } from 'su"
  },
  {
    "path": "test/integration/integration/openapi.yaml",
    "chars": 4981,
    "preview": "openapi: '3.1.0'\ninfo:\n  version: 1.0.0\n  title: Exegesis Integration Test\n  license:\n    name: MIT\npaths:\n  /greet:\n   "
  },
  {
    "path": "test/integration/issue-132/entry.yaml",
    "chars": 484,
    "preview": "# entry.yaml\npaths:\n  Entry:\n    get:\n      operationId: fetch\n      responses:\n        default:\n          description: "
  },
  {
    "path": "test/integration/issue-132/issue132Test.ts",
    "chars": 323,
    "preview": "// import { expect } from 'chai';\nimport * as path from 'path';\nimport * as exegesis from '../../../src';\n\ndescribe('Iss"
  },
  {
    "path": "test/integration/issue-132/openapi.yaml",
    "chars": 109,
    "preview": "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",
    "chars": 27463,
    "preview": "import 'mocha';\nimport chai from 'chai';\nimport chaiAsPromised from 'chai-as-promised';\nimport * as oas3 from 'openapi3-"
  },
  {
    "path": "test/oas3/Paths/PathResolverTest.ts",
    "chars": 4553,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport PathResolver from '../../../src/oas3/Paths/PathResolver';\n\ndescri"
  },
  {
    "path": "test/oas3/Paths/PathsTest.ts",
    "chars": 2940,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport Paths from '../../../src/oas3/Paths';\nimport Oas3CompileContext f"
  },
  {
    "path": "test/oas3/ResponsesTest.ts",
    "chars": 14105,
    "preview": "import chai from 'chai';\nimport chaiAsPromised from 'chai-as-promised';\nimport 'mocha';\nimport * as oas3 from 'openapi3-"
  },
  {
    "path": "test/oas3/Schema/validatorsTest.ts",
    "chars": 23227,
    "preview": "import { expect } from 'chai';\nimport * as oas3 from 'openapi3-ts';\nimport * as validators from '../../../src/oas3/Schem"
  },
  {
    "path": "test/oas3/ServersTest.ts",
    "chars": 2784,
    "preview": "import { expect } from 'chai';\n\nimport Servers from '../../src/oas3/Servers';\n\ndescribe('oas3 Servers', () => {\n    desc"
  },
  {
    "path": "test/oas3/oas3ControllersTest.ts",
    "chars": 11491,
    "preview": "import ld from 'lodash';\nimport { IncomingHttpHeaders } from 'http';\nimport oas3 from 'openapi3-ts';\nimport { expect } f"
  },
  {
    "path": "test/oas3/oas3ParametersTest.ts",
    "chars": 12949,
    "preview": "import oas3 from 'openapi3-ts';\nimport { expect } from 'chai';\nimport { defaultCompiledOptions } from '../fixtures';\nimp"
  },
  {
    "path": "test/oas3/parameterParsers/delimitedParserTest.ts",
    "chars": 1581,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport {\n    pipeDelimitedParser,\n    spaceDelimitedParser,\n} from '../."
  },
  {
    "path": "test/oas3/parameterParsers/indexTest.ts",
    "chars": 2118,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\nimport { ParameterLocation } from '../../../src/types';\nimport * as param"
  },
  {
    "path": "test/oas3/samplesTest.ts",
    "chars": 607,
    "preview": "import * as path from 'path';\nimport { defaultCompiledOptions } from '../fixtures';\nimport { compileRunner, compileApiIn"
  },
  {
    "path": "test/samples/petstore.yaml",
    "chars": 2625,
    "preview": "# From https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml\nopenapi: '3.0.0'\ninfo:\n  ver"
  },
  {
    "path": "test/tsconfig.json",
    "chars": 232,
    "preview": "{\n    \"extends\": \"../tsconfig.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"typeRoots\": [\n           "
  },
  {
    "path": "test/utils/json-schema-infer-typesTest.ts",
    "chars": 4415,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport inferTypes from '../../src/utils/json-schema-infer-types';\nimport"
  },
  {
    "path": "test/utils/json-schema-resolve-refTest.ts",
    "chars": 1537,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport { resolveRef } from '../../src/utils/json-schema-resolve-ref';\n\nd"
  },
  {
    "path": "test/utils/jsonPathsTest.ts",
    "chars": 1338,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\nimport * as jsonPaths from '../../src/utils/jsonPaths';\n\ndescribe('jsonPa"
  },
  {
    "path": "test/utils/jsonSchemaTest.ts",
    "chars": 8549,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport * as jsonSchema from '../../src/utils/jsonSchema';\n\ndescribe('jso"
  },
  {
    "path": "test/utils/mimeTest.ts",
    "chars": 6029,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport { MimeTypeRegistry, parseMimeType } from '../../src/utils/mime';\n"
  },
  {
    "path": "test/utils/stringToStreamTest.ts",
    "chars": 723,
    "preview": "import 'mocha';\nimport { expect } from 'chai';\n\nimport stringToStream from '../../src/utils/stringToStream';\n\ndescribe('"
  },
  {
    "path": "tsconfig.json",
    "chars": 5344,
    "preview": "{\n    \"compilerOptions\": {\n        /* Basic Options */\n        \"target\": \"es2016\" /* Specify ECMAScript target version: "
  }
]

About this extraction

This page contains the full source code of the exegesis-js/exegesis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 136 files (413.7 KB), approximately 92.8k tokens, and a symbol index with 367 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!