'use strict'

const t = require('tap')
const test = t.test
const sget = require('simple-get').concat
const Fastify = require('..')

test('route', t => {
  t.plan(10)
  const test = t.test
  const fastify = Fastify()

  test('route - get', t => {
    t.plan(1)
    try {
      fastify.route({
        method: 'GET',
        url: '/',
        schema: {
          response: {
            '2xx': {
              type: 'object',
              properties: {
                hello: {
                  type: 'string'
                }
              }
            }
          }
        },
        handler: function (req, reply) {
          reply.send({ hello: 'world' })
        }
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test('missing schema - route', t => {
    t.plan(1)
    try {
      fastify.route({
        method: 'GET',
        url: '/missing',
        handler: function (req, reply) {
          reply.send({ hello: 'world' })
        }
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test('invalid handler attribute - route', t => {
    t.plan(1)
    try {
      fastify.get('/', { handler: 'not a function' }, () => {})
      t.fail()
    } catch (e) {
      t.pass()
    }
  })

  test('invalid schema - route', t => {
    t.plan(1)
    try {
      fastify.route({
        method: 'GET',
        url: '/invalid',
        schema: {
          querystring: {
            id: 'string'
          }
        },
        handler: function (req, reply) {
          reply.send({ hello: 'world' })
        }
      })
      fastify.after(err => {
        t.ok(err instanceof Error)
      })
    } catch (e) {
      t.fail()
    }
  })

  test('Multiple methods', t => {
    t.plan(1)
    try {
      fastify.route({
        method: ['GET', 'DELETE'],
        url: '/multiple',
        handler: function (req, reply) {
          reply.send({ hello: 'world' })
        }
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test('Add multiple methods', t => {
    t.plan(1)
    try {
      fastify.get('/add-multiple', function (req, reply) {
        reply.send({ hello: 'Bob!' })
      })
      fastify.route({
        method: ['PUT', 'DELETE'],
        url: '/add-multiple',
        handler: function (req, reply) {
          reply.send({ hello: 'world' })
        }
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  fastify.listen(0, function (err) {
    if (err) t.error(err)
    fastify.server.unref()

    test('cannot add another route after binding', t => {
      t.plan(1)
      try {
        fastify.route({
          method: 'GET',
          url: '/another-get-route',
          handler: function (req, reply) {
            reply.send({ hello: 'world' })
          }
        })
        t.fail()
      } catch (e) {
        t.pass()
      }
    })

    test('route - get', t => {
      t.plan(3)
      sget({
        method: 'GET',
        url: 'http://localhost:' + fastify.server.address().port
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(JSON.parse(body), { hello: 'world' })
      })
    })

    test('route - missing schema', t => {
      t.plan(3)
      sget({
        method: 'GET',
        url: 'http://localhost:' + fastify.server.address().port + '/missing'
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(JSON.parse(body), { hello: 'world' })
      })
    })

    test('route - multiple methods', t => {
      t.plan(6)
      sget({
        method: 'GET',
        url: 'http://localhost:' + fastify.server.address().port + '/multiple'
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(JSON.parse(body), { hello: 'world' })
      })

      sget({
        method: 'DELETE',
        url: 'http://localhost:' + fastify.server.address().port + '/multiple'
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(JSON.parse(body), { hello: 'world' })
      })
    })
  })
})

test('path can be specified in place of uri', t => {
  t.plan(3)
  const fastify = Fastify()

  fastify.route({
    method: 'GET',
    path: '/path',
    handler: function (req, reply) {
      reply.send({ hello: 'world' })
    }
  })

  const reqOpts = {
    method: 'GET',
    url: '/path'
  }

  fastify.inject(reqOpts, (err, res) => {
    t.error(err)
    t.strictEqual(res.statusCode, 200)
    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
  })
})

test('invalid bodyLimit option - route', t => {
  t.plan(2)
  const fastify = Fastify()

  try {
    fastify.route({
      bodyLimit: false,
      method: 'PUT',
      handler: () => null
    })
    t.fail('bodyLimit must be an integer')
  } catch (err) {
    t.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'")
  }

  try {
    fastify.post('/url', { bodyLimit: 10000.1 }, () => null)
    t.fail('bodyLimit must be an integer')
  } catch (err) {
    t.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'")
  }
})

test('handler function in options of shorthand route should works correctly', t => {
  t.plan(3)

  const fastify = Fastify()
  fastify.get('/foo', {
    handler: (req, reply) => {
      reply.send({ hello: 'world' })
    }
  })

  fastify.inject({
    method: 'GET',
    url: '/foo'
  }, (err, res) => {
    t.error(err)
    t.strictEqual(res.statusCode, 200)
    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
  })
})


t.deepEqual(res.payload, "Attached: Error: body should have required property 'name'")

keep keep keep keep replace keep keep keep keep

t.deepEqual(res.payload, "Attached: Error: body should have required property 'name', body should have required property 'work'")

'use strict'

const validation = require('./validation')
const validateSchema = validation.validate
const { hookRunner, hookIterator } = require('./hooks')
const wrapThenable = require('./wrapThenable')

function handleRequest (err, request, reply) {
  if (reply.sent === true) return
  if (err != null) {
    reply.send(err)
    return
  }

  var method = request.raw.method
  var headers = request.headers

  if (method === 'GET' || method === 'HEAD') {
    handler(request, reply)
    return
  }

  var contentType = headers['content-type']

  if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
    if (contentType === undefined) {
      if (
        headers['transfer-encoding'] === undefined &&
        (headers['content-length'] === '0' || headers['content-length'] === undefined)
      ) { // Request has no body to parse
        handler(request, reply)
      } else {
        reply.context.contentTypeParser.run('', handler, request, reply)
      }
    } else {
      reply.context.contentTypeParser.run(contentType, handler, request, reply)
    }
    return
  }

  if (method === 'OPTIONS' || method === 'DELETE') {
    if (
      contentType !== undefined &&
      (
        headers['transfer-encoding'] !== undefined ||
        headers['content-length'] !== undefined
      )
    ) {
      reply.context.contentTypeParser.run(contentType, handler, request, reply)
    } else {
      handler(request, reply)
    }
    return
  }

  // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
  reply.code(404).send(new Error('Not Found'))
}

function handler (request, reply) {
  if (reply.context.preValidation !== null) {
    hookRunner(
      reply.context.preValidation,
      hookIterator,
      request,
      reply,
      preValidationCallback
    )
  } else {
    preValidationCallback(null, request, reply)
  }
}

function preValidationCallback (err, request, reply) {
  if (reply.res.finished === true) return
  if (err != null) {
    reply.send(err)
    return
  }

  var result = validateSchema(reply.context, request)
  if (result) {
    if (reply.context.attachValidation === false) {
      reply.code(400).send(result)
      return
    }

    reply.request.validationError = result
  }

  // preHandler hook
  if (reply.context.preHandler !== null) {
    hookRunner(
      reply.context.preHandler,
      hookIterator,
      request,
      reply,
      preHandlerCallback
    )
  } else {
    preHandlerCallback(null, request, reply)
  }
}

function preHandlerCallback (err, request, reply) {
  if (reply.res.finished === true) return
  if (err != null) {
    reply.send(err)
    return
  }

  var result = reply.context.handler(request, reply)
  if (result && typeof result.then === 'function') {
    wrapThenable(result, reply)
  }
}

module.exports = handleRequest
module.exports[Symbol.for('internals')] = { handler }


.github
tools/
.travis.yml
azure-pipelines-npm-template.yml
azure-pipelines-yarn-template.yml
azure-pipelines.yml
CODE_OF_CONDUCT.md
CONTRIBUTING.md
.editorconfig
.gitattributes
.gitignore


displayName: Install Node.js
- bash: npm i
displayName: Install dependencies
- bash: npm test
displayName: Run tests

jobs:
- template: azure-pipelines-yarn-template.yml
  parameters:
    name: Linux
    vmImage: ubuntu-16.04

- template: azure-pipelines-npm-template.yml
  parameters:
    name: Windows
    vmImage: vs2017-win2016


'use strict'

const t = require('tap')
const test = t.test
const { createError } = require('../../lib/errors')

test('Create error with zero parameter', t => {
  t.plan(6)
  const NewError = createError('CODE', 'Not available')
  const err = new NewError()
  t.type(err, Error)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: Not available')
  t.equal(err.code, 'CODE')
  t.equal(err.statusCode, 500)
  t.ok(err.stack)
})

test('Create error with 1 parameter', t => {
  t.plan(6)
  const NewError = createError('CODE', 'hey %s')
  const err = new NewError('alice')
  t.type(err, Error)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: hey alice')
  t.equal(err.code, 'CODE')
  t.equal(err.statusCode, 500)
  t.ok(err.stack)
})

test('Create error with 2 parameters', t => {
  t.plan(6)
  const NewError = createError('CODE', 'hey %s, I like your %s')
  const err = new NewError('alice', 'attitude')
  t.type(err, Error)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: hey alice, I like your attitude')
  t.equal(err.code, 'CODE')
  t.equal(err.statusCode, 500)
  t.ok(err.stack)
})

test('Create error with 3 parameters', t => {
  t.plan(6)
  const NewError = createError('CODE', 'hey %s, I like your %s %s')
  const err = new NewError('alice', 'attitude', 'see you')
  t.type(err, Error)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: hey alice, I like your attitude see you')
  t.equal(err.code, 'CODE')
  t.equal(err.statusCode, 500)
  t.ok(err.stack)
})

test('Create error with no statusCode property', t => {
  t.plan(6)
  const NewError = createError('CODE', 'hey %s', 0)
  const err = new NewError('dude')
  t.type(err, Error)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: hey dude')
  t.equal(err.code, 'CODE')
  t.notOk(err.statusCode)
  t.ok(err.stack)
})

test('Should throw when error code has no fastify code', t => {
  t.plan(1)
  try {
    createError()
  } catch (err) {
    t.equal(err.message, 'Fastify error code must not be empty')
  }
})

test('Should throw when error code has no message', t => {
  t.plan(1)
  try {
    createError('code')
  } catch (err) {
    t.equal(err.message, `Fastify error message must not be empty`)
  }
})

test('Create error with different base', t => {
  t.plan(7)
  const NewError = createError('CODE', 'hey %s', 500, TypeError)
  const err = new NewError('dude')
  t.type(err, Error)
  t.type(err, TypeError)
  t.equal(err.name, 'FastifyError [CODE]')
  t.equal(err.message, 'CODE: hey dude')
  t.equal(err.code, 'CODE')
  t.equal(err.statusCode, 500)
  t.ok(err.stack)
})


'use strict'

const t = require('tap')
require('./helper').payloadMethod('options', t, true)
require('./input-validation').payloadMethod('options', t)


{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "noEmit": true,
    "strict": true,
  },
  "files": [
    "./index.ts"
  ]
}


testRunTitle: TestRun ${{ parameters.name }} $(node_version)

displayName: "Publish code coverage results"

fastify

Fast and low overhead web framework, for Node.js

npm run lint:standard && npm run lint:typescript

standard --parser typescript-eslint-parser --plugin typescript test/types/*.ts fastify.d.ts

npm run lint && npm run unit && npm run typescript

npm run unit -- --cov --coverage-report=html

branchcmp -r 2 -g -s "npm run benchmark"

npx concurrently -k -s first "node ./examples/simple.js" "npx autocannon -c 100 -d 5 -p 10 localhost:3000/"


Fastify

## Reply
The second parameter of the handler function is `Reply`.
Reply is a core Fastify object that exposes the following functions
and properties:

- `.code(statusCode)` - Sets the status code.
- `.status(statusCode)` - An alias for `.code(statusCode)`.
- `.header(name, value)` - Sets a response header.
- `.getHeader(name)` - Retrieve value of already set header.
- `.hasHeader(name)` - Determine if a header has been set.
- `.type(value)` - Sets the header `Content-Type`.
- `.redirect([code,] url)` - Redirect to the specified url, the status code is optional (default to `302`).
- `.callNotFound()` - Invokes the custom not found handler.
- `.serialize(payload)` - Serializes the specified payload using the default json serializer or using the custom serializer (if one is set) and returns the serialized payload.
- `.serializer(function)` - Sets a custom serializer for the payload.
- `.send(payload)` - Sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object.
- `.sent` - A boolean value that you can use if you need to know if `send` has already been called.
- `.res` - The [`http.ServerResponse`](https://nodejs.org/dist/latest/docs/api/http.html#http_class_http_serverresponse) from Node core.

```js
fastify.get('/', options, function (request, reply) {
  // Your code
  reply
    .code(200)
    .header('Content-Type', 'application/json; charset=utf-8')
    .send({ hello: 'world' })
})
```

Additionally, `Reply` provides access to the context of the request:

```js
fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) {
  reply.send('handler config.foo = ' + reply.context.config.foo)
})
```

### .code(statusCode)
If not set via `reply.code`, the resulting `statusCode` will be `200`.

### .header(key, value)
Sets a response header. If the value is omitted or undefined it is coerced
to `''`.

For more information, see [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest/docs/api/http.html#http_response_setheader_name_value).

### .getHeader(key)
Retrieves the value of a previously set header.
```js
reply.header('x-foo', 'foo')
reply.getHeader('x-foo') // 'foo'
```

### .removeHeader(key)

Removed the value of a previously set header.
```js
reply.header('x-foo', 'foo')
reply.removeHeader('x-foo')
reply.getHeader('x-foo') // undefined
```

### .hasHeader(key)
Returns a boolean indicating if the specified header has been set.

### .redirect(dest)
Redirects a request to the specified url, the status code is optional, default to `302` (if status code is not already set by calling `code`).
```js
reply.redirect('/home')
```

### .callNotFound()
Invokes the custom not found handler.
```js
reply.callNotFound()
```

### .type(contentType)
Sets the content type for the response.
This is a shortcut for `reply.header('Content-Type', 'the/type')`.

```js
reply.type('text/html')
```

### .serializer(func)
`.send()` will by default JSON-serialize any value that is not one of: `Buffer`, `stream`, `string`, `undefined`, `Error`. If you need to replace the default serializer with a custom serializer for a particular request, you can do so with the `.serializer()` utility. Be aware that if you are using a custom serializer, you must set a custom `'Content-Type'` header.

```js
reply
  .header('Content-Type', 'application/x-protobuf')
  .serializer(protoBuf.serialize)
```

Note that you don't need to use this utility inside a `handler` because Buffers, streams, and strings (unless a serializer is set) are considered to already be serialized.

```js
reply
  .header('Content-Type', 'application/x-protobuf')
  .send(protoBuf.serialize(data))
```

See [`.send()`](#send) for more information on sending different types of values.

### .sent

As the name suggests, `.sent` is a property to indicate if
a response has been sent via `reply.send()`.

In case a route handler is defined as an async function or it
returns a promise, it is possible to set `reply.sent = true`
to indicate that the automatic invocation of `reply.send()` once the
handler promise resolve should be skipped. By setting `reply.sent =
true`, an application claims full responsibility of the low-level
request and response. Moreover, hooks will not be invoked.

As an example:

```js
app.get('/', (req, reply) => {
  reply.sent = true
  reply.res.end('hello world')

  return Promise.resolve('this will be skipped')
})
```

If the handler rejects, the error will be logged.

### .send(data)
As the name suggests, `.send()` is the function that sends the payload to the end user.

#### Objects
As noted above, if you are sending JSON objects, `send` will serialize the object with [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) if you set an output schema, otherwise `JSON.stringify()` will be used.
```js
fastify.get('/json', options, function (request, reply) {
  reply.send({ hello: 'world' })
})
```

#### Strings
If you pass a string to `send` without a `Content-Type`, it will be sent as `text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a string to `send`, it will be serialized with the custom serializer if one is set, otherwise it will be sent unmodified (unless the `Content-Type` header is set to `application/json; charset=utf-8`, in which case it will be JSON-serialized like an object — see the section above).
```js
fastify.get('/json', options, function (request, reply) {
  reply.send('plain string')
})
```

#### Streams
*send* can also handle streams out of the box, internally uses [pump](https://www.npmjs.com/package/pump) to avoid leaks of file descriptors. If you are sending a stream and you have not set a `'Content-Type'` header, *send* will set it at `'application/octet-stream'`.
```js
fastify.get('/streams', function (request, reply) {
  const fs = require('fs')
  const stream = fs.createReadStream('some-file', 'utf8')
  reply.send(stream)
})
```

#### Buffers
If you are sending a buffer and you have not set a `'Content-Type'` header, *send* will set it to `'application/octet-stream'`.
```js
const fs = require('fs')
fastify.get('/streams', function (request, reply) {
  fs.readFile('some-file', (err, fileBuffer) => {
    reply.send(err || fileBuffer)
  })
})
```

#### Errors
If you pass to *send* an object that is an instance of *Error*, Fastify will automatically create an error structured as the following:
```js
{
  error: String        // the http error message
  message: String      // the user error message
  statusCode: Number   // the http status code
}
```
You can add some custom property to the Error object, such as `statusCode` and `headers`, that will be used to enhance the http response.

*Note: If you are passing an error to `send` and the statusCode is less than 400, Fastify will automatically set it at 500.*

Tip: you can simplify errors by using the [`http-errors`](https://npm.im/http-errors) module or [`fastify-sensible`](https://github.com/fastify/fastify-sensible) plugin to generate errors:

```js
fastify.get('/', function (request, reply) {
  reply.send(httpErrors.Gone())
})
```

If you want to completely customize the error handling, checkout [`setErrorHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#seterrorhandler) API.

*Note: you are responsibile for logging when customizing the error handler*

API:

```js
fastify.setErrorHandler(function (error, request, reply) {
  request.log.warn(error)
  var statusCode = error.statusCode >= 400 ? error.statusCode : 500
  reply
    .code(statusCode)
    .type('text/plain')
    .send(statusCode >= 500 ? 'Internal server error' : error.message)
})
```

The not found errors generated by the router will use the  [`setNotFoundHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#setnotfoundhandler)

API:

```js
fastify.setNotFoundHandler(function (request, reply) {
  reply
    .code(404)
    .type('text/plain')
    .send('a custom not found')
})
```

#### Type of the final payload
The type of the sent payload (after serialization and going through any [`onSend` hooks](https://github.com/fastify/fastify/blob/master/docs/Hooks.md#the-onsend-hook)) must be one of the following types, otherwise an error will be thrown:

- `string`
- `Buffer`
- `stream`
- `undefined`
- `null`

#### Async-Await and Promises
Fastify natively handles promises and supports async-await.

*Note that in the following examples we are not using reply.send.*
```js
fastify.get('/promises', options, function (request, reply) {
  return new Promise(function (resolve) {
    setTimeout(resolve, 200, { hello: 'world' })
  })
})

fastify.get('/async-await', options, async function (request, reply) {
  var res = await new Promise(function (resolve) {
    setTimeout(resolve, 200, { hello: 'world' })
  })
  return res
})
```

Rejected promises default to a `500` HTTP status code. Reject the promise, or `throw` in an `async function`, with an object that has `statusCode` (or `status`) and `message` properties to modify the reply.

```js
fastify.get('/teapot', async function (request, reply) => {
  const err = new Error()
  err.statusCode = 418
  err.message = 'short and stout'
  throw err
})
```

If you want to know more please review [Routes#async-await](https://github.com/fastify/fastify/blob/master/docs/Routes.md#async-await).


Fastify

## Validation and Serialization
Fastify uses a schema-based approach, and even if it is not mandatory we recommend using [JSON Schema](http://json-schema.org/) to validate your routes and serialize your outputs. Internally, Fastify compiles the schema into a highly performant function.

### Validation
The route validation internally relies upon [Ajv](https://www.npmjs.com/package/ajv), which is a high-performance JSON schema validator. Validating the input is very easy: just add the fields that you need inside the route schema, and you are done! The supported validations are:
- `body`: validates the body of the request if it is a POST or a PUT.
- `querystring`: validates the query string. This can be a complete JSON Schema object (with a `type` property of `'object'` and a `'properties'` object containing parameters) or a simpler variation in which the `type` and `properties` attributes are forgone and the query parameters are listed at the top level (see the example below).
- `params`: validates the route params.
- `headers`: validates the request headers.

Example:
```js
const schema = {
  body: {
    type: 'object',
    properties: {
      someKey: { type: 'string' },
      someOtherKey: { type: 'number' }
    }
  },

  querystring: {
    name: { type: 'string' },
    excitement: { type: 'integer' }
  },

  params: {
    type: 'object',
    properties: {
      par1: { type: 'string' },
      par2: { type: 'number' }
    }
  },

  headers: {
    type: 'object',
    properties: {
      'x-foo': { type: 'string' }
    },
    required: ['x-foo']
  }
}

fastify.post('/the/url', { schema }, handler)
```
*Note that Ajv will try to [coerce](https://github.com/epoberezkin/ajv#coercing-data-types) the values to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards.*

#### Adding a shared schema
Thanks to the `addSchema` API, you can add multiple schemas to the Fastify instance and then reuse them in multiple parts of your application. *(Note that this API is not encapsulated)*
```js
const fastify = require('fastify')()

fastify.addSchema({
  $id: 'greetings',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.route({
  method: 'POST',
  url: '/',
  schema: {
    body: 'greetings#'
  },
  handler: () => {}
})
```
You can use the shared schema everywhere, as top level schema or nested inside other schemas:
```js
const fastify = require('fastify')()

fastify.addSchema({
  $id: 'greetings',
  type: 'object',
  properties: {
    hello: { type: 'string' }
  }
})

fastify.route({
  method: 'POST',
  url: '/',
  schema: {
    body: {
      type: 'object',
      properties: {
        greeting: 'greetings#',
        timestamp: { type: 'number' }
      }
    }
  },
  handler: () => {}
})
```

#### Retrieving a copy of all shared schemas

The function `getSchemas` returns all shared schemas that were added by `addSchema` method.

#### Schema Compiler

The `schemaCompiler` is a function that returns a function that validates the body, url parameters, headers, and query string. The default `schemaCompiler` returns a function that implements the [ajv](https://ajv.js.org/) validation interface. Fastify uses it internally to speed the validation up.

Fastify's [baseline ajv configuration](https://github.com/epoberezkin/ajv#options-to-modify-validated-data) is:

```js
{
  removeAdditional: true, // remove additional properties
  useDefaults: true, // replace missing properties and items with the values from corresponding default keyword
  coerceTypes: true  // change data type of data to match type keyword
}
```

This baseline configuration cannot be modified. If you want to change or set additional config options, you will need to create your own instance and override the existing one like:

```js
const fastify = require('fastify')()
const Ajv = require('ajv')
const ajv = new Ajv({
  // the fastify defaults (if needed)
  removeAdditional: true,
  useDefaults: true,
  coerceTypes: true
  // any other options
  // ...
})
fastify.setSchemaCompiler(function (schema) {
  return ajv.compile(schema)
})
```

But maybe you want to change the validation library. Perhaps you like `Joi`. In this case, you can use it to validate the url parameters, body, and query string!

```js
const Joi = require('joi')

fastify.post('/the/url', {
  schema: {
    body: Joi.object().keys({
      hello: Joi.string().required()
    }).required()
  },
  schemaCompiler: schema => data => Joi.validate(data, schema)
}, handler)
```

In that case the function returned by `schemaCompiler` returns an object like:
* `error`: filled with an instance of `Error` or a string that describes the validation error
* `value`: the coerced value that passed the validation

### Serialization
Usually you will send your data to the clients via JSON, and Fastify has a powerful tool to help you, [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify), which is used if you have provided an output schema in the route options. We encourage you to use an output schema, as it will increase your throughput by 100-400% depending on your payload and will prevent accidental disclosure of sensitive information.

Example:
```js
const schema = {
  response: {
    200: {
      type: 'object',
      properties: {
        value: { type: 'string' },
        otherValue: { type: 'boolean' }
      }
    }
  }
}

fastify.post('/the/url', { schema }, handler)
```

As you can see, the response schema is based on the status code. If you want to use the same schema for multiple status codes, you can use `'2xx'`, for example:
```js
const schema = {
  response: {
    '2xx': {
      type: 'object',
      properties: {
        value: { type: 'string' },
        otherValue: { type: 'boolean' }
      }
    },
    201: {
      type: 'object',
      properties: {
        value: { type: 'string' }
      }
    }
  }
}

fastify.post('/the/url', { schema }, handler)
```

*If you need a custom serializer in a very specific part of your code, you can set one with `reply.serializer(...)`.*

### Error Handling
When schema validation fails for a request, Fastify will automtically return a  status 400 response including the result from the validator in the payload. As an example, if you have the following schema for your route

```js
const schema = {
  body: {
    type: 'object',
    properties: {
      name: { type: 'string' }
    },
    required: ['name']
  }
}
```

and fail to satisfy it, the route will immediately return a response with the following payload

```js
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body should have required property 'name'"
}
```

If you want to handle errors inside the route, you can specify the `attachValidation` option for your route. If there is a validation error, the `validationError` property of the request will contain the `Error` object with the raw `validation` result as shown below

```js
const fastify = Fastify()

fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
  if (req.validation) {
    // `req.validationError.validation` contains the raw validation error
    reply.code(400).send(req.validationError)
  }
})
```

You can also use [setErrorHandler](https://www.fastify.io/docs/latest/Server/#seterrorhandler) to define a custom response for validation errors such as

```js
fastify.setErrorHandler(function (error, request, reply) {
  if (error.validation) {
     reply.status(422).send(new Error('validation failed'))
  }
})
```

### Resources
- [JSON Schema](http://json-schema.org/)
- [Understanding JSON schema](https://spacetelescope.github.io/understanding-json-schema/)
- [fast-json-stringify documentation](https://github.com/fastify/fast-json-stringify)
- [Ajv documentation](https://github.com/epoberezkin/ajv/blob/master/README.md)


'use strict'

const t = require('tap')
const test = t.test
const sget = require('simple-get').concat
const fastify = require('..')()

const opts = {
  schema: {
    response: {
      '2xx': {
        type: 'object',
        properties: {
          hello: {
            type: 'string'
          }
        }
      }
    }
  }
}

fastify.get('/return', opts, function (req, reply) {
  const promise = new Promise((resolve, reject) => {
    resolve({ hello: 'world' })
  })
  return promise
})

fastify.get('/return-error', opts, function (req, reply) {
  const promise = new Promise((resolve, reject) => {
    reject(new Error('some error'))
  })
  return promise
})

fastify.get('/double', function (req, reply) {
  setTimeout(function () {
    // this should not throw
    reply.send({ hello: 'world' })
  }, 20)
  return Promise.resolve({ hello: '42' })
})

fastify.listen(0, err => {
  t.error(err)
  fastify.server.unref()

  test('shorthand - sget return promise es6 get', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/return'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })

  test('shorthand - sget promise es6 get return error', t => {
    t.plan(2)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/return-error'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 500)
    })
  })

  test('sget promise double send', t => {
    t.plan(3)

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/double'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(JSON.parse(body), { hello: '42' })
    })
  })
})


displayName: Install Node.js
- bash: yarn install
displayName: Install dependencies
- bash: yarn test
displayName: Run tests


'use strict'

const t = require('tap')
const test = t.test
const fs = require('fs')
const path = require('path')
const Fastify = require('../..')
const h2url = require('h2url')
const sget = require('simple-get').concat
const msg = { hello: 'world' }

var fastify
try {
  fastify = Fastify({
    http2: true,
    https: {
      allowHTTP1: true,
      key: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.key')),
      cert: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.cert'))
    }
  })
  t.pass('Key/cert successfully loaded')
} catch (e) {
  t.fail('Key/cert loading failed', e)
}

fastify.get('/', function (req, reply) {
  reply.code(200).send(msg)
})

fastify.post('/', function (req, reply) {
  reply.code(200).send(req.body)
})

fastify.get('/error', async function (req, reply) {
  throw new Error('kaboom')
})

fastify.listen(0, err => {
  t.error(err)
  fastify.server.unref()

  test('https get error', async (t) => {
    t.plan(1)

    const url = `https://localhost:${fastify.server.address().port}/error`
    const res = await h2url.concat({ url })

    t.strictEqual(res.headers[':status'], 500)
  })

  test('https post', async (t) => {
    t.plan(2)

    const url = `https://localhost:${fastify.server.address().port}`
    const res = await h2url.concat({
      url,
      method: 'POST',
      body: JSON.stringify({ hello: 'http2' }),
      headers: {
        'content-type': 'application/json'
      }
    })

    t.strictEqual(res.headers[':status'], 200)
    t.deepEqual(JSON.parse(res.body), { hello: 'http2' })
  })

  test('https get request', async (t) => {
    t.plan(3)

    const url = `https://localhost:${fastify.server.address().port}`
    const res = await h2url.concat({ url })

    t.strictEqual(res.headers[':status'], 200)
    t.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length)
    t.deepEqual(JSON.parse(res.body), msg)
  })

  test('http1 get request', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'https://localhost:' + fastify.server.address().port,
      rejectUnauthorized: false
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })

  test('http1 get error', (t) => {
    t.plan(2)
    sget({
      method: 'GET',
      url: 'https://localhost:' + fastify.server.address().port + '/error',
      rejectUnauthorized: false
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 500)
    })
  })
})


# Fastify is an OPEN Open Source Project

## What?

Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.

## Rules

There are a few basic ground-rules for contributors:

1. **No `--force` pushes** on `master` or modifying the Git history in any way after a PR has been merged.
1. **Non-master branches** ought to be used for ongoing work.
1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.
1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor.
1. Contributors should attempt to adhere to the prevailing code-style.
1. At least two contributors, or one core member, must approve pull-requests prior to merging.
1. All integrated CI services must be green, unless an agreement has been reached in the conversation about the exception, before a pull-request can be merged.
1. Breaking changes or significant new features must be merged by a core member. Small pull-requests, e.g. documentation typo corrections, may be merged by any member with commit access.

### Fastify v1.x

Code for Fastify's **v1.x** is in [branch 1.x](https://github.com/fastify/fastify/tree/1.x), so all Fastify 1.x related changes should be based on **`branch 1.x`**.

## Releases

Declaring formal releases remains the prerogative of the project maintainers.

## Changes to this arrangement

This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.

-----------------------------------------

## Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

* (a) The contribution was created in whole or in part by me and I
  have the right to submit it under the open source license
  indicated in the file; or

* (b) The contribution is based upon previous work that, to the best
  of my knowledge, is covered under an appropriate open source
  license and I have the right under that license to submit that
  work with modifications, whether created in whole or in part
  by me, under the same open source license (unless I am
  permitted to submit under a different license), as indicated
  in the file; or

* (c) The contribution was provided directly to me by some other
  person who certified (a), (b) or (c) and I have not modified
  it.

* (d) I understand and agree that this project and the contribution
  are public and that a record of the contribution (including all
  personal information I submit with it, including my sign-off) is
  maintained indefinitely and may be redistributed consistent with
  this project or the open source license(s) involved.


---
name: 👮 Security Issue
about: Responsible Disclosure
---

## 👮 [Responsible Disclosure](https://github.com/nodejs/security-wg/blob/master/processes/responsible_disclosure_template.md)

Do not open issues that might have security implications. It is critical that security related issues
are reported privately so we have time to address them before they become public knowledge.

Individuals who find potential vulnerabilities in a package are invited
to complete a vulnerability report on the dedicated HackerOne organization:

[https://hackerone.com/nodejs-ecosystem](https://hackerone.com/nodejs-ecosystem)

Vulnerabilities can also be reported by emailing security-ecosystem@nodejs.org or Fastify core members:

- [Matteo Collina](mailto:matteo.collina@gmail.com)
- [Tomas Della Vedova](https://twitter.com/delvedor)


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('../..')
const https = require('https')
const fs = require('fs')
const path = require('path')
const sget = require('simple-get').concat

test('Should support a custom https server', t => {
  t.plan(6)

  const serverFactory = (handler, opts) => {
    t.ok(opts.serverFactory)

    const options = {
      key: fs.readFileSync(path.join(__dirname, 'fastify.key')),
      cert: fs.readFileSync(path.join(__dirname, 'fastify.cert'))
    }

    const server = https.createServer(options, (req, res) => {
      req.custom = true
      handler(req, res)
    })

    return server
  }

  const fastify = Fastify({ serverFactory })

  t.teardown(fastify.close.bind(fastify))

  fastify.get('/', (req, reply) => {
    t.ok(req.raw.custom)
    reply.send({ hello: 'world' })
  })

  fastify.listen(0, err => {
    t.error(err)

    sget({
      method: 'GET',
      url: 'https://localhost:' + fastify.server.address().port,
      rejectUnauthorized: false
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})


'use strict'

const fastJsonStringify = require('fast-json-stringify')
const Ajv = require('ajv')

const bodySchema = Symbol('body-schema')
const querystringSchema = Symbol('querystring-schema')
const paramsSchema = Symbol('params-schema')
const responseSchema = Symbol('response-schema')
const headersSchema = Symbol('headers-schema')

function getValidatorForStatusCodeSchema (statusCodeDefinition) {
  return fastJsonStringify(statusCodeDefinition)
}

function getResponseSchema (responseSchemaDefinition) {
  var statusCodes = Object.keys(responseSchemaDefinition)
  return statusCodes.reduce(function (r, statusCode) {
    r[statusCode] = getValidatorForStatusCodeSchema(responseSchemaDefinition[statusCode])
    return r
  }, {})
}

function build (context, compile, schemas) {
  if (!context.schema) {
    return
  }

  context.schema = schemas.resolveRefs(context.schema)

  const headers = context.schema.headers

  if (headers && Object.getPrototypeOf(headers) !== Object.prototype) {
    // do not mess with non-literals, e.g. Joi schemas
    context[headersSchema] = compile(headers)
  } else if (headers) {
    // The header keys are case insensitive
    //  https://tools.ietf.org/html/rfc2616#section-4.2
    const headersSchemaLowerCase = {}
    Object.keys(headers).forEach(k => { headersSchemaLowerCase[k] = headers[k] })
    if (headersSchemaLowerCase.required instanceof Array) {
      headersSchemaLowerCase.required = headersSchemaLowerCase.required.map(h => h.toLowerCase())
    }
    if (headers.properties) {
      Object.keys(headers.properties).forEach(k => {
        headersSchemaLowerCase.properties[k.toLowerCase()] = headers.properties[k]
      })
    }
    context[headersSchema] = compile(headersSchemaLowerCase)
  }

  if (context.schema.response) {
    context[responseSchema] = getResponseSchema(context.schema.response)
  }

  if (context.schema.body) {
    context[bodySchema] = compile(context.schema.body)
  }

  if (context.schema.querystring) {
    context[querystringSchema] = compile(context.schema.querystring)
  }

  if (context.schema.params) {
    context[paramsSchema] = compile(context.schema.params)
  }
}

function validateParam (validatorFunction, request, paramName) {
  var ret = validatorFunction && validatorFunction(request[paramName])
  if (ret === false) return validatorFunction.errors
  if (ret && ret.error) return ret.error
  if (ret && ret.value) request[paramName] = ret.value
  return false
}

function validate (context, request) {
  var params = validateParam(context[paramsSchema], request, 'params')
  if (params) {
    return wrapValidationError(params, 'params')
  }
  var body = validateParam(context[bodySchema], request, 'body')
  if (body) {
    return wrapValidationError(body, 'body')
  }
  var query = validateParam(context[querystringSchema], request, 'query')
  if (query) {
    return wrapValidationError(query, 'querystring')
  }
  var headers = validateParam(context[headersSchema], request, 'headers')
  if (headers) {
    return wrapValidationError(headers, 'headers')
  }
  return null
}

function wrapValidationError (result, dataVar) {
  if (result instanceof Error) {
    return result
  }
  var error = new Error(schemaErrorsText(result, {
    dataVar
  }))
  error.validation = result
  return error
}

function serialize (context, data, statusCode) {
  var responseSchemaDef = context[responseSchema]
  if (!responseSchemaDef) {
    return JSON.stringify(data)
  }
  if (responseSchemaDef[statusCode]) {
    return responseSchemaDef[statusCode](data)
  }
  var fallbackStatusCode = (statusCode + '')[0] + 'xx'
  if (responseSchemaDef[fallbackStatusCode]) {
    return responseSchemaDef[fallbackStatusCode](data)
  }
  return JSON.stringify(data)
}

function isValidLogger (logger) {
  if (!logger) {
    return false
  }

  var result = true
  const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
  for (var i = 0; i < methods.length; i += 1) {
    if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') {
      result = false
      break
    }
  }
  return result
}

function schemaErrorsText (errors, options) {
  errors = errors || this.errors
  if (!errors) return 'No errors'
  options = options || {}
  var separator = options.separator === undefined ? ', ' : options.separator
  var dataVar = options.dataVar === undefined ? 'data' : options.dataVar

  var text = ''
  for (var i = 0; i < errors.length; i++) {
    var e = errors[i]
    if (e) text += dataVar + e.dataPath + ' ' + e.message + separator
  }
  return text.slice(0, -separator.length)
}

function buildSchemaCompiler () {
  // This instance of Ajv is private
  // it should not be customized or used
  const ajv = new Ajv({
    coerceTypes: true,
    useDefaults: true,
    removeAdditional: true
  })

  return ajv.compile.bind(ajv)
}

module.exports = { build, validate, serialize, isValidLogger, buildSchemaCompiler }
module.exports.symbols = { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema }


keep keep keep replace keep keep keep keep


'use strict'

const t = require('tap')
const test = t.test
const querystring = require('querystring')
const sget = require('simple-get').concat
const Fastify = require('..')

test('Custom querystring parser', t => {
  t.plan(9)

  const fastify = Fastify({
    querystringParser: function (str) {
      t.strictEqual(str, 'foo=bar&baz=faz')
      return querystring.parse(str)
    }
  })

  fastify.get('/', (req, reply) => {
    t.deepEqual(req.query, {
      foo: 'bar',
      baz: 'faz'
    })
    reply.send({ hello: 'world' })
  })

  fastify.listen(0, (err, address) => {
    t.error(err)
    t.tearDown(() => fastify.close())

    sget({
      method: 'GET',
      url: `${address}?foo=bar&baz=faz`
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })

    fastify.inject({
      method: 'GET',
      url: `${address}?foo=bar&baz=faz`
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })
  })
})

test('Custom querystring parser should be called also if there is nothing to parse', t => {
  t.plan(9)

  const fastify = Fastify({
    querystringParser: function (str) {
      t.strictEqual(str, '')
      return querystring.parse(str)
    }
  })

  fastify.get('/', (req, reply) => {
    t.deepEqual(req.query, {})
    reply.send({ hello: 'world' })
  })

  fastify.listen(0, (err, address) => {
    t.error(err)
    t.tearDown(() => fastify.close())

    sget({
      method: 'GET',
      url: address
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })

    fastify.inject({
      method: 'GET',
      url: address
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })
  })
})

test('Querystring without value', t => {
  t.plan(9)

  const fastify = Fastify({
    querystringParser: function (str) {
      t.strictEqual(str, 'foo')
      return querystring.parse(str)
    }
  })

  fastify.get('/', (req, reply) => {
    t.deepEqual(req.query, { foo: '' })
    reply.send({ hello: 'world' })
  })

  fastify.listen(0, (err, address) => {
    t.error(err)
    t.tearDown(() => fastify.close())

    sget({
      method: 'GET',
      url: `${address}?foo`
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })

    fastify.inject({
      method: 'GET',
      url: `${address}?foo`
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
    })
  })
})

test('Custom querystring parser should be a function', t => {
  t.plan(1)

  try {
    Fastify({
      querystringParser: 10
    })
    t.fail('Should throw')
  } catch (err) {
    t.strictEqual(
      err.message,
      `querystringParser option should be a function, instead got 'number'`
    )
  }
})


'use strict'

const sget = require('simple-get').concat
const stream = require('stream')
const symbols = require('../lib/symbols')

/**
 * @param method HTTP request method
 * @param t tap instance
 * @param isSetErrorHandler true: using setErrorHandler
 */
module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
  const test = t.test
  const fastify = require('..')()

  if (isSetErrorHandler) {
    fastify.setErrorHandler(function (err, request, reply) {
      t.type(request, 'object')
      t.type(request, fastify[symbols.kRequest])
      reply
        .code(err.statusCode)
        .type('application/json; charset=utf-8')
        .send(err)
    })
  }

  const upMethod = method.toUpperCase()
  const loMethod = method.toLowerCase()

  const schema = {
    schema: {
      response: {
        '2xx': {
          type: 'object',
          properties: {
            hello: {
              type: 'string'
            }
          }
        }
      }
    }
  }

  test(`${upMethod} can be created`, t => {
    t.plan(1)
    try {
      fastify[loMethod]('/', schema, function (req, reply) {
        reply.code(200).send(req.body)
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test(`${upMethod} without schema can be created`, t => {
    t.plan(1)
    try {
      fastify[loMethod]('/missing', function (req, reply) {
        reply.code(200).send(req.body)
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test(`${upMethod} with body and querystring`, t => {
    t.plan(1)
    try {
      fastify[loMethod]('/with-query', function (req, reply) {
        req.body.hello = req.body.hello + req.query.foo
        reply.code(200).send(req.body)
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  test(`${upMethod} with bodyLimit option`, t => {
    t.plan(1)
    try {
      fastify[loMethod]('/with-limit', { bodyLimit: 1 }, function (req, reply) {
        reply.send(req.body)
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

  fastify.listen(0, function (err) {
    if (err) {
      t.error(err)
      return
    }

    fastify.server.unref()

    test(`${upMethod} - correctly replies`, t => {
      t.plan(3)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        body: {
          hello: 'world'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 'world' })
      })
    })

    test(`${upMethod} - correctly replies with very large body`, t => {
      t.plan(3)

      const largeString = 'world'.repeat(13200)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        body: { hello: largeString },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: largeString })
      })
    })

    test(`${upMethod} - correctly replies if the content type has the charset`, t => {
      t.plan(3)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        body: JSON.stringify({ hello: 'world' }),
        headers: {
          'content-type': 'application/json; charset=utf-8'
        }
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body.toString(), JSON.stringify({ hello: 'world' }))
      })
    })

    test(`${upMethod} without schema - correctly replies`, t => {
      t.plan(3)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port + '/missing',
        body: {
          hello: 'world'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 'world' })
      })
    })

    test(`${upMethod} with body and querystring - correctly replies`, t => {
      t.plan(3)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port + '/with-query?foo=hello',
        body: {
          hello: 'world'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 'worldhello' })
      })
    })

    test(`${upMethod} with no body - correctly replies`, t => {
      t.plan(6)

      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port + '/missing',
        headers: { 'Content-Length': '0' },
        timeout: 500
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.strictEqual(JSON.parse(body.toString()), null)
      })

      // Must use inject to make a request without a Content-Length header
      fastify.inject({
        method: upMethod,
        url: '/missing'
      }, (err, res) => {
        t.error(err)
        t.strictEqual(res.statusCode, 200)
        t.strictEqual(JSON.parse(res.payload), null)
      })
    })

    test(`${upMethod} returns 415 - incorrect media type if body is not json`, t => {
      t.plan(2)
      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port + '/missing',
        body: 'hello world',
        timeout: 500
      }, (err, response, body) => {
        t.error(err)
        if (upMethod === 'OPTIONS') {
          t.strictEqual(response.statusCode, 200)
        } else {
          t.strictEqual(response.statusCode, 415)
        }
      })
    })

    if (loMethod === 'options') {
      test(`OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text`, t => {
        t.plan(2)
        sget({
          method: upMethod,
          url: 'http://localhost:' + fastify.server.address().port + '/missing',
          body: 'hello world',
          headers: {
            'Content-Type': 'text/xml'
          },
          timeout: 500
        }, (err, response, body) => {
          t.error(err)
          t.strictEqual(response.statusCode, 415)
        })
      })
    }

    test(`${upMethod} returns 400 - Bad Request`, t => {
      t.plan(4)

      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        body: 'hello world',
        headers: {
          'Content-Type': 'application/json'
        },
        timeout: 500
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
      })

      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': '0'
        },
        timeout: 500
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
      })
    })

    test(`${upMethod} returns 413 - Payload Too Large`, t => {
      t.plan(upMethod === 'OPTIONS' ? 4 : 6)

      sget({
        method: upMethod,
        url: 'http://localhost:' + fastify.server.address().port,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': 1024 * 1024 + 1
        }
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 413)
      })

      // Node errors for OPTIONS requests with a stream body and no Content-Length header
      if (upMethod !== 'OPTIONS') {
        var chunk = Buffer.alloc(1024 * 1024 + 1, 0)
        const largeStream = new stream.Readable({
          read () {
            this.push(chunk)
            chunk = null
          }
        })
        sget({
          method: upMethod,
          url: 'http://localhost:' + fastify.server.address().port,
          headers: { 'Content-Type': 'application/json' },
          body: largeStream,
          timeout: 500
        }, (err, response, body) => {
          t.error(err)
          t.strictEqual(response.statusCode, 413)
        })
      }

      sget({
        method: upMethod,
        url: `http://localhost:${fastify.server.address().port}/with-limit`,
        headers: { 'Content-Type': 'application/json' },
        body: {},
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 413)
      })
    })

    test(`${upMethod} should fail with empty body and application/json content-type`, t => {
      if (upMethod === 'OPTIONS') return t.end()

      t.plan(12)

      fastify.inject({
        method: `${upMethod}`,
        url: '/',
        headers: {
          'Content-Type': 'application/json'
        }
      }, (err, res) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(res.payload), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })

      sget({
        method: upMethod,
        url: `http://localhost:${fastify.server.address().port}`,
        headers: {
          'Content-Type': 'application/json'
        }
      }, (err, res, body) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(body.toString()), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })

      fastify.inject({
        method: `${upMethod}`,
        url: '/',
        headers: {
          'Content-Type': 'application/json'
        },
        payload: null
      }, (err, res) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(res.payload), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })

      sget({
        method: upMethod,
        url: `http://localhost:${fastify.server.address().port}`,
        headers: {
          'Content-Type': 'application/json'
        },
        payload: null
      }, (err, res, body) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(body.toString()), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })

      fastify.inject({
        method: `${upMethod}`,
        url: '/',
        headers: {
          'Content-Type': 'application/json'
        },
        payload: undefined
      }, (err, res) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(res.payload), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })

      sget({
        method: upMethod,
        url: `http://localhost:${fastify.server.address().port}`,
        headers: {
          'Content-Type': 'application/json'
        },
        payload: undefined
      }, (err, res, body) => {
        t.error(err)
        t.strictDeepEqual(JSON.parse(body.toString()), {
          error: 'Bad Request',
          code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
          message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
          statusCode: 400
        })
      })
    })
  })
}


'use strict'

const t = require('tap')
const test = t.test
const sget = require('simple-get').concat
const Fastify = require('..')

test('contentTypeParser should add a custom async parser', t => {
  t.plan(3)
  const fastify = Fastify()

  fastify.post('/', (req, reply) => {
    reply.send(req.body)
  })

  fastify.options('/', (req, reply) => {
    reply.send(req.body)
  })

  fastify.addContentTypeParser('application/jsoff', async function (req) {
    var res = await new Promise((resolve, reject) => resolve(req))
    return res
  })

  fastify.listen(0, err => {
    t.error(err)

    t.tearDown(() => fastify.close())

    t.test('in POST', t => {
      t.plan(3)

      sget({
        method: 'POST',
        url: 'http://localhost:' + fastify.server.address().port,
        body: '{"hello":"world"}',
        headers: {
          'Content-Type': 'application/jsoff'
        }
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body.toString(), JSON.stringify({ hello: 'world' }))
      })
    })

    t.test('in OPTIONS', t => {
      t.plan(3)

      sget({
        method: 'OPTIONS',
        url: 'http://localhost:' + fastify.server.address().port,
        body: '{"hello":"world"}',
        headers: {
          'Content-Type': 'application/jsoff'
        }
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body.toString(), JSON.stringify({ hello: 'world' }))
      })
    })
  })
})


'use strict'

const t = require('tap')
const test = t.test

const pluginUtilsPublic = require('../../lib/pluginUtils.js')
const pluginUtils = require('../../lib/pluginUtils')[Symbol.for('internals')]
const symbols = require('../../lib/symbols.js')

test(`shouldSkipOverride should check the 'skip-override' symbol`, t => {
  t.plan(2)

  yes[Symbol.for('skip-override')] = true

  t.true(pluginUtils.shouldSkipOverride(yes))
  t.false(pluginUtils.shouldSkipOverride(no))

  function yes () {}
  function no () {}
})

test(`getMeta should return the object stored with the 'plugin-meta' symbol`, t => {
  t.plan(1)

  const meta = { hello: 'world' }
  fn[Symbol.for('plugin-meta')] = meta

  t.deepEqual(meta, pluginUtils.getMeta(fn))

  function fn () {}
})

test('checkDecorators should check if the given decorator is present in the instance', t => {
  t.plan(1)

  fn[Symbol.for('plugin-meta')] = {
    decorators: {
      fastify: ['plugin'],
      reply: ['plugin'],
      request: ['plugin']
    }
  }

  function context () {}
  context.plugin = true
  context[symbols.kReply] = { prototype: { plugin: true } }
  context[symbols.kRequest] = { prototype: { plugin: true } }

  try {
    pluginUtils.checkDecorators.call(context, fn)
    t.pass('Everything ok')
  } catch (err) {
    t.fail(err)
  }

  function fn () {}
})

test('checkDecorators should check if the given decorator is present in the instance (errored)', t => {
  t.plan(1)

  fn[Symbol.for('plugin-meta')] = {
    decorators: {
      fastify: ['plugin'],
      reply: ['plugin'],
      request: ['plugin']
    }
  }

  function context () {}
  context.plugin = true
  context[symbols.kReply] = { prototype: { plugin: true } }
  context[symbols.kRequest] = { prototype: {} }

  try {
    pluginUtils.checkDecorators.call(context, fn)
    t.fail('should throw')
  } catch (err) {
    t.is(err.message, `The decorator 'plugin' is not present in Request`)
  }

  function fn () {}
})

test('checkDependencies should check if the given dependency is present in the instance', t => {
  t.plan(1)

  fn[Symbol.for('plugin-meta')] = {
    dependencies: ['plugin']
  }

  function context () {}
  context[pluginUtilsPublic.registeredPlugins] = ['plugin']

  try {
    pluginUtils.checkDependencies.call(context, fn)
    t.pass('Everything ok')
  } catch (err) {
    t.fail(err)
  }

  function fn () {}
})

test('checkDependencies should check if the given dependency is present in the instance (errored)', t => {
  t.plan(1)

  fn[Symbol.for('plugin-meta')] = {
    name: 'test-plugin',
    dependencies: ['plugin']
  }

  function context () {}
  context[pluginUtilsPublic.registeredPlugins] = []

  try {
    pluginUtils.checkDependencies.call(context, fn)
    t.fail('should throw')
  } catch (err) {
    t.is(err.message, `The dependency 'plugin' of plugin 'test-plugin' is not registered`)
  }

  function fn () {}
})


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('../..')
const split = require('split2')
const proxyquire = require('proxyquire')
const loggerUtils = require('../../lib/logger')

test('time resolution', t => {
  t.plan(2)
  t.is(typeof loggerUtils.now, 'function')
  t.is(typeof loggerUtils.now(), 'number')
})

test('The logger should add a unique id for every request', t => {
  const ids = []

  const fastify = Fastify()
  fastify.get('/', (req, reply) => {
    t.ok(req.raw.id)
    reply.send({ id: req.raw.id })
  })

  fastify.listen(0, err => {
    t.error(err)
    const queue = new Queue()
    for (var i = 0; i < 10; i++) {
      queue.add(checkId)
    }
    queue.add(() => {
      fastify.close()
      t.end()
    })
  })

  function checkId (done) {
    fastify.inject({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, res) => {
      t.error(err)
      const payload = JSON.parse(res.payload)
      t.ok(ids.indexOf(payload.id) === -1, 'the id should not be duplicated')
      ids.push(payload.id)
      done()
    })
  }
})

test('The logger should reuse request id header for req.id', t => {
  const fastify = Fastify()
  fastify.get('/', (req, reply) => {
    t.ok(req.raw.id)
    reply.send({ id: req.raw.id })
  })

  fastify.listen(0, err => {
    t.error(err)

    fastify.inject({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port,
      headers: {
        'Request-Id': 'request-id-1'
      }
    }, (err, res) => {
      t.error(err)
      const payload = JSON.parse(res.payload)
      t.ok(payload.id === 'request-id-1', 'the request id from the header should be returned')
      fastify.close()
      t.end()
    })
  })
})

test('The logger should add a timestamp if logging to stdout', t => {
  t.plan(5)

  const stream = split(JSON.parse)
  const stub = Object.create(loggerUtils)
  stub.createLogger = function (opts) {
    return loggerUtils.createLogger(opts, stream)
  }

  const Fastify = proxyquire('../..', {
    './lib/logger': stub
  })
  const fastify = Fastify({ logger: true })

  stream.once('data', (line) => {
    t.equal(line.msg, 'incoming request')
    stream.once('data', (line) => {
      t.equal(line.msg, 'Not Found')
      stream.once('data', (line) => {
        t.equal(line.msg, 'request completed')
        t.ok(line.responseTime, 'responseTime exists')
      })
    })
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, function (err, res) {
    t.error(err)
  })
})

function Queue () {
  this.q = []
  this.running = false
}

Queue.prototype.add = function add (job) {
  this.q.push(job)
  if (!this.running) this.run()
}

Queue.prototype.run = function run () {
  this.running = true
  const job = this.q.shift()
  job(() => {
    if (this.q.length) {
      this.run()
    } else {
      this.running = false
    }
  })
}

test('The logger should error if both stream and file destination are given', t => {
  t.plan(2)

  const stream = require('stream').Writable

  try {
    Fastify({
      logger: {
        level: 'info',
        stream: stream,
        file: '/test'
      }
    })
  } catch (err) {
    t.is(err.code, 'FST_ERR_LOG_INVALID_DESTINATION')
    t.is(err.message, 'FST_ERR_LOG_INVALID_DESTINATION: Cannot specify both logger.stream and logger.file options')
  }
})


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('../..')
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']

test('fastify.all should add all the methods to the same url', t => {
  t.plan(supportedMethods.length * 2)

  const fastify = Fastify()

  fastify.all('/', (req, reply) => {
    reply.send({ method: req.raw.method })
  })

  supportedMethods.forEach(injectRequest)

  function injectRequest (method) {
    const options = {
      url: '/',
      method: method
    }

    if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
      options.payload = { hello: 'world' }
    }

    fastify.inject(options, (err, res) => {
      t.error(err)
      var payload = JSON.parse(res.payload)
      t.deepEqual(payload, { method: method })
    })
  }
})


"lint": "npm run lint:standard && npm run lint:typescript",

"lint:standard": "standard --verbose | snazzy",

"lint:typescript": "standard --parser typescript-eslint-parser --plugin typescript test/types/*.ts fastify.d.ts",

"unit": "tap -J test/*.test.js test/*/*.test.js",

"typescript": "tsc --project ./test/types/tsconfig.json",

"unit:report": "tap -J test/*.test.js test/*/*.test.js --cov --coverage-report=html --coverage-report=cobertura | tee out.tap",

"unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",

jobs:
- template: azure-pipelines-yarn-template.yml
  parameters:
    name: Linux
    vmImage: ubuntu-16.04

- template: azure-pipelines-npm-template.yml
  parameters:
    name: Windows
    vmImage: vs2017-win2016


#! /bin/bash

NODE_VER=`node -v`

if [[ $NODE_VER =~ ^v6 && $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_BRANCH == "master" ]]; then
  curl -X POST --header 'Content-Type: application/json' "https://circleci.com/api/v1.1/project/github/fastify/website?circle-token=$CIRCLECI_TOKEN"
fi


'use strict'

const sget = require('simple-get').concat
const Fastify = require('..')
const fs = require('fs')
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

function asyncHookTest (t) {
  const test = t.test
  test('async hooks', t => {
    t.plan(21)

    const fastify = Fastify()
    fastify.addHook('onRequest', async function (request, reply) {
      await sleep(1)
      request.test = 'the request is coming'
      reply.test = 'the reply has come'
      if (request.raw.method === 'DELETE') {
        throw new Error('some error')
      }
    })

    fastify.addHook('preHandler', async function (request, reply) {
      await sleep(1)
      t.is(request.test, 'the request is coming')
      t.is(reply.test, 'the reply has come')
      if (request.raw.method === 'HEAD') {
        throw new Error('some error')
      }
    })

    fastify.addHook('onSend', async function (request, reply, payload) {
      await sleep(1)
      t.ok('onSend called')
    })

    fastify.addHook('onResponse', async function (request, reply) {
      await sleep(1)
      t.ok('onResponse called')
    })

    fastify.get('/', function (request, reply) {
      t.is(request.test, 'the request is coming')
      t.is(reply.test, 'the reply has come')
      reply.code(200).send({ hello: 'world' })
    })

    fastify.head('/', function (req, reply) {
      reply.code(200).send({ hello: 'world' })
    })

    fastify.delete('/', function (req, reply) {
      reply.code(200).send({ hello: 'world' })
    })

    fastify.listen(0, err => {
      t.error(err)
      fastify.server.unref()

      sget({
        method: 'GET',
        url: 'http://localhost:' + fastify.server.address().port
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.strictEqual(response.headers['content-length'], '' + body.length)
        t.deepEqual(JSON.parse(body), { hello: 'world' })
      })

      sget({
        method: 'HEAD',
        url: 'http://localhost:' + fastify.server.address().port
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 500)
      })

      sget({
        method: 'DELETE',
        url: 'http://localhost:' + fastify.server.address().port
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 500)
      })
    })
  })

  test('modify payload', t => {
    t.plan(10)
    const fastify = Fastify()
    const payload = { hello: 'world' }
    const modifiedPayload = { hello: 'modified' }
    const anotherPayload = '"winter is coming"'

    fastify.addHook('onSend', async function (request, reply, thePayload) {
      t.ok('onSend called')
      t.deepEqual(JSON.parse(thePayload), payload)
      return thePayload.replace('world', 'modified')
    })

    fastify.addHook('onSend', async function (request, reply, thePayload) {
      t.ok('onSend called')
      t.deepEqual(JSON.parse(thePayload), modifiedPayload)
      return anotherPayload
    })

    fastify.addHook('onSend', async function (request, reply, thePayload) {
      t.ok('onSend called')
      t.strictEqual(thePayload, anotherPayload)
    })

    fastify.get('/', (req, reply) => {
      reply.send(payload)
    })

    fastify.inject({
      method: 'GET',
      url: '/'
    }, (err, res) => {
      t.error(err)
      t.strictEqual(res.payload, anotherPayload)
      t.strictEqual(res.statusCode, 200)
      t.strictEqual(res.headers['content-length'], '18')
    })
  })

  test('onRequest hooks should be able to block a request', t => {
    t.plan(5)
    const fastify = Fastify()

    fastify.addHook('onRequest', async (req, reply) => {
      reply.send('hello')
    })

    fastify.addHook('onRequest', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('preHandler', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.ok('called')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
      t.is(res.payload, 'hello')
    })
  })

  test('preHandler hooks should be able to block a request', t => {
    t.plan(5)
    const fastify = Fastify()

    fastify.addHook('preHandler', async (req, reply) => {
      reply.send('hello')
    })

    fastify.addHook('preHandler', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.equal(payload, 'hello')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
      t.is(res.payload, 'hello')
    })
  })

  test('preValidation hooks should be able to block a request', t => {
    t.plan(5)
    const fastify = Fastify()

    fastify.addHook('preValidation', async (req, reply) => {
      reply.send('hello')
    })

    fastify.addHook('preValidation', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.equal(payload, 'hello')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
      t.is(res.payload, 'hello')
    })
  })

  test('onRequest hooks should be able to block a request (last hook)', t => {
    t.plan(5)
    const fastify = Fastify()

    fastify.addHook('onRequest', async (req, reply) => {
      reply.send('hello')
    })

    fastify.addHook('preHandler', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.ok('called')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
      t.is(res.payload, 'hello')
    })
  })

  test('preHandler hooks should be able to block a request (last hook)', t => {
    t.plan(5)
    const fastify = Fastify()

    fastify.addHook('preHandler', async (req, reply) => {
      reply.send('hello')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.equal(payload, 'hello')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
      t.is(res.payload, 'hello')
    })
  })

  test('onRequest respond with a stream', t => {
    t.plan(4)
    const fastify = Fastify()

    fastify.addHook('onRequest', async (req, reply) => {
      return new Promise((resolve, reject) => {
        const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8')
        // stream.pipe(res)
        // res.once('finish', resolve)
        reply.send(stream)
        reply.res.once('finish', () => resolve())
      })
    })

    fastify.addHook('onRequest', async (req, res) => {
      t.fail('this should not be called')
    })

    fastify.addHook('preHandler', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.ok('called')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
    })
  })

  test('preHandler respond with a stream', t => {
    t.plan(7)
    const fastify = Fastify()

    fastify.addHook('onRequest', async (req, res) => {
      t.ok('called')
    })

    // we are calling `reply.send` inside the `preHandler` hook with a stream,
    // this triggers the `onSend` hook event if `preHanlder` has not yet finished
    const order = [1, 2]

    fastify.addHook('preHandler', async (req, reply) => {
      return new Promise((resolve, reject) => {
        const stream = fs.createReadStream(process.cwd() + '/test/stream.test.js', 'utf8')
        reply.send(stream)
        reply.res.once('finish', () => {
          t.is(order.shift(), 2)
          resolve()
        })
      })
    })

    fastify.addHook('preHandler', async (req, reply) => {
      t.fail('this should not be called')
    })

    fastify.addHook('onSend', async (req, reply, payload) => {
      t.is(order.shift(), 1)
      t.is(typeof payload.pipe, 'function')
    })

    fastify.addHook('onResponse', async (request, reply) => {
      t.ok('called')
    })

    fastify.get('/', function (request, reply) {
      t.fail('we should not be here')
    })

    fastify.inject({
      url: '/',
      method: 'GET'
    }, (err, res) => {
      t.error(err)
      t.is(res.statusCode, 200)
    })
  })
}

module.exports = asyncHookTest


Fastify

## Reply
The second parameter of the handler function is `Reply`.
Reply is a core Fastify object that exposes the following functions
and properties:

- `.code(statusCode)` - Sets the status code.
- `.status(statusCode)` - An alias for `.code(statusCode)`.
- `.header(name, value)` - Sets a response header.
- `.getHeader(name)` - Retrieve value of already set header.
- `.hasHeader(name)` - Determine if a header has been set.
- `.type(value)` - Sets the header `Content-Type`.
- `.redirect([code,] url)` - Redirect to the specified url, the status code is optional (default to `302`).
- `.callNotFound()` - Invokes the custom not found handler.
- `.serialize(payload)` - Serializes the specified payload using the default json serializer or using the custom serializer (if one is set) and returns the serialized payload.
- `.serializer(function)` - Sets a custom serializer for the payload.
- `.send(payload)` - Sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object.
- `.sent` - A boolean value that you can use if you need to know if `send` has already been called.
- `.res` - The [`http.ServerResponse`](https://nodejs.org/dist/latest/docs/api/http.html#http_class_http_serverresponse) from Node core.

```js
fastify.get('/', options, function (request, reply) {
  // Your code
  reply
    .code(200)
    .header('Content-Type', 'application/json; charset=utf-8')
    .send({ hello: 'world' })
})
```

Additionally, `Reply` provides access to the context of the request:

```js
fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) {
  reply.send('handler config.foo = ' + reply.context.config.foo)
})
```

### .code(statusCode)
If not set via `reply.code`, the resulting `statusCode` will be `200`.

### .header(key, value)
Sets a response header. If the value is omitted or undefined it is coerced
to `''`.

For more information, see [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest/docs/api/http.html#http_response_setheader_name_value).

### .getHeader(key)
Retrieves the value of a previously set header.
```js
reply.header('x-foo', 'foo')
reply.getHeader('x-foo') // 'foo'
```

### .removeHeader(key)

Removed the value of a previously set header.
```js
reply.header('x-foo', 'foo')
reply.removeHeader('x-foo')
reply.getHeader('x-foo') // undefined
```

### .hasHeader(key)
Returns a boolean indicating if the specified header has been set.

### .redirect(dest)
Redirects a request to the specified url, the status code is optional, default to `302` (if status code is not already set by calling `code`).
```js
reply.redirect('/home')
```

### .callNotFound()
Invokes the custom not found handler.
```js
reply.callNotFound()
```

### .type(contentType)
Sets the content type for the response.
This is a shortcut for `reply.header('Content-Type', 'the/type')`.

```js
reply.type('text/html')
```

### .serializer(func)
`.send()` will by default JSON-serialize any value that is not one of: `Buffer`, `stream`, `string`, `undefined`, `Error`. If you need to replace the default serializer with a custom serializer for a particular request, you can do so with the `.serializer()` utility. Be aware that if you are using a custom serializer, you must set a custom `'Content-Type'` header.

```js
reply
  .header('Content-Type', 'application/x-protobuf')
  .serializer(protoBuf.serialize)
```

Note that you don't need to use this utility inside a `handler` because Buffers, streams, and strings (unless a serializer is set) are considered to already be serialized.

```js
reply
  .header('Content-Type', 'application/x-protobuf')
  .send(protoBuf.serialize(data))
```

See [`.send()`](#send) for more information on sending different types of values.

### .sent

As the name suggests, `.sent` is a property to indicate if
a response has been sent via `reply.send()`.

In case a route handler is defined as an async function or it
returns a promise, it is possible to set `reply.sent = true`
to indicate that the automatic invocation of `reply.send()` once the
handler promise resolve should be skipped. By setting `reply.sent =
true`, an application claims full responsibility of the low-level
request and response. Moreover, hooks will not be invoked.

As an example:

```js
app.get('/', (req, reply) => {
  reply.sent = true
  reply.res.end('hello world')

  return Promise.resolve('this will be skipped')
})
```

If the handler rejects, the error will be logged.

### .send(data)
As the name suggests, `.send()` is the function that sends the payload to the end user.

#### Objects
As noted above, if you are sending JSON objects, `send` will serialize the object with [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) if you set an output schema, otherwise `JSON.stringify()` will be used.
```js
fastify.get('/json', options, function (request, reply) {
  reply.send({ hello: 'world' })
})
```

#### Strings
If you pass a string to `send` without a `Content-Type`, it will be sent as `text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a string to `send`, it will be serialized with the custom serializer if one is set, otherwise it will be sent unmodified (unless the `Content-Type` header is set to `application/json; charset=utf-8`, in which case it will be JSON-serialized like an object — see the section above).
```js
fastify.get('/json', options, function (request, reply) {
  reply.send('plain string')
})
```

#### Streams
*send* can also handle streams out of the box, internally uses [pump](https://www.npmjs.com/package/pump) to avoid leaks of file descriptors. If you are sending a stream and you have not set a `'Content-Type'` header, *send* will set it at `'application/octet-stream'`.
```js
fastify.get('/streams', function (request, reply) {
  const fs = require('fs')
  const stream = fs.createReadStream('some-file', 'utf8')
  reply.send(stream)
})
```

#### Buffers
If you are sending a buffer and you have not set a `'Content-Type'` header, *send* will set it to `'application/octet-stream'`.
```js
const fs = require('fs')
fastify.get('/streams', function (request, reply) {
  fs.readFile('some-file', (err, fileBuffer) => {
    reply.send(err || fileBuffer)
  })
})
```

#### Errors
If you pass to *send* an object that is an instance of *Error*, Fastify will automatically create an error structured as the following:
```js
{
  error: String        // the http error message
  message: String      // the user error message
  statusCode: Number   // the http status code
}
```
You can add some custom property to the Error object, such as `statusCode` and `headers`, that will be used to enhance the http response.

*Note: If you are passing an error to `send` and the statusCode is less than 400, Fastify will automatically set it at 500.*

Tip: you can simplify errors by using the [`http-errors`](https://npm.im/http-errors) module or [`fastify-sensible`](https://github.com/fastify/fastify-sensible) plugin to generate errors:

```js
fastify.get('/', function (request, reply) {
  reply.send(httpErrors.Gone())
})
```

If you want to completely customize the error handling, checkout [`setErrorHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#seterrorhandler) API.

*Note: you are responsibile for logging when customizing the error handler*

API:

```js
fastify.setErrorHandler(function (error, request, reply) {
  request.log.warn(error)
  var statusCode = error.statusCode >= 400 ? error.statusCode : 500
  reply
    .code(statusCode)
    .type('text/plain')
    .send(statusCode >= 500 ? 'Internal server error' : error.message)
})
```

The not found errors generated by the router will use the  [`setNotFoundHandler`](https://github.com/fastify/fastify/blob/master/docs/Server.md#setnotfoundhandler)

API:

```js
fastify.setNotFoundHandler(function (request, reply) {
  reply
    .code(404)
    .type('text/plain')
    .send('a custom not found')
})
```

#### Type of the final payload
The type of the sent payload (after serialization and going through any [`onSend` hooks](https://github.com/fastify/fastify/blob/master/docs/Hooks.md#the-onsend-hook)) must be one of the following types, otherwise an error will be thrown:

- `string`
- `Buffer`
- `stream`
- `undefined`
- `null`

#### Async-Await and Promises
Fastify natively handles promises and supports async-await.

*Note that in the following examples we are not using reply.send.*
```js
fastify.get('/promises', options, function (request, reply) {
  return new Promise(function (resolve) {
    setTimeout(resolve, 200, { hello: 'world' })
  })
})

fastify.get('/async-await', options, async function (request, reply) {
  var res = await new Promise(function (resolve) {
    setTimeout(resolve, 200, { hello: 'world' })
  })
  return res
})
```

Rejected promises default to a `500` HTTP status code. Reject the promise, or `throw` in an `async function`, with an object that has `statusCode` (or `status`) and `message` properties to modify the reply.

```js
fastify.get('/teapot', async function (request, reply) => {
  const err = new Error()
  err.statusCode = 418
  err.message = 'short and stout'
  throw err
})
```

If you want to know more please review [Routes#async-await](https://github.com/fastify/fastify/blob/master/docs/Routes.md#async-await).


"test": "npm run lint && npm run unit && npm run typescript",

"coverage": "npm run unit -- --cov --coverage-report=html",

"bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",

"test:report": "npm run lint && npm run unit:report && npm run typescript",

jobs:
- job: ${{ parameters.name }}
  pool:
    vmImage: ${{ parameters.vmImage }}
  strategy:
    matrix:
      node_11_x:
        node_version: 11.x
      node_10_x:
        node_version: 10.x
      node_8_x:
        node_version: 8.x
      node_6_x:
        node_version: 6.x
    maxParallel: 5
  steps:
  - task: NodeTool@0
    inputs:
      versionSpec: $(node_version)
    displayName: Install Node.js
  - bash: npm i
    displayName: Install dependencies
  - bash: npm test
    displayName: Run tests


'use strict'

const t = require('tap')
const Fastify = require('..')
const fastify = Fastify()

fastify.route({
  method: 'POST',
  path: '/jsonBody',
  handler: function (req, reply) {
    throw new Error('kaboom')
  }
})

const reqOpts = {
  method: 'POST',
  url: '/jsonBody',
  payload: {
    hello: 'world'
  }
}

process.on('uncaughtException', (err) => {
  t.equal(err.message, 'kaboom')
})

fastify.inject(reqOpts, (e, res) => {
  t.plan(1)
  t.fail('should not be called')
})


'use strict'

const t = require('tap')
const test = t.test
const sget = require('simple-get').concat
const fastify = require('..')()

const schema = {
  schema: {
    response: {
      '2xx': {
        type: 'object',
        properties: {
          hello: {
            type: 'string'
          }
        }
      }
    }
  }
}

const nullSchema = {
  schema: {
    response: {
      '2xx': {
        type: 'null'
      }
    }
  }
}

const numberSchema = {
  schema: {
    response: {
      '2xx': {
        type: 'object',
        properties: {
          hello: {
            type: 'number'
          }
        }
      }
    }
  }
}

const querySchema = {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        hello: {
          type: 'integer'
        }
      }
    }
  }
}

const paramsSchema = {
  schema: {
    params: {
      type: 'object',
      properties: {
        foo: {
          type: 'string'
        },
        test: {
          type: 'integer'
        }
      }
    }
  }
}

const headersSchema = {
  schema: {
    headers: {
      type: 'object',
      properties: {
        'x-test': {
          type: 'number'
        },
        'Y-Test': {
          type: 'number'
        }
      }
    }
  }
}

test('shorthand - get', t => {
  t.plan(1)
  try {
    fastify.get('/', schema, function (req, reply) {
      reply.code(200).send({ hello: 'world' })
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('shorthand - get (return null)', t => {
  t.plan(1)
  try {
    fastify.get('/null', nullSchema, function (req, reply) {
      reply.code(200).send(null)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('shorthand - get params', t => {
  t.plan(1)
  try {
    fastify.get('/params/:foo/:test', paramsSchema, function (req, reply) {
      reply.code(200).send(req.params)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('shorthand - get, querystring schema', t => {
  t.plan(1)
  try {
    fastify.get('/query', querySchema, function (req, reply) {
      reply.code(200).send(req.query)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('shorthand - get, headers schema', t => {
  t.plan(1)
  try {
    fastify.get('/headers', headersSchema, function (req, reply) {
      reply.code(200).send(req.headers)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('missing schema - get', t => {
  t.plan(1)
  try {
    fastify.get('/missing', function (req, reply) {
      reply.code(200).send({ hello: 'world' })
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('custom serializer - get', t => {
  t.plan(1)

  function customSerializer (data) {
    return JSON.stringify(data)
  }

  try {
    fastify.get('/custom-serializer', numberSchema, function (req, reply) {
      reply.code(200).serializer(customSerializer).send({ hello: 'world' })
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('empty response', t => {
  t.plan(1)
  try {
    fastify.get('/empty', function (req, reply) {
      reply.code(200).send()
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('send a falsy boolean', t => {
  t.plan(1)
  try {
    fastify.get('/boolean', function (req, reply) {
      reply.code(200).send(false)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

fastify.listen(0, err => {
  t.error(err)
  fastify.server.unref()

  test('shorthand - request get', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })

  test('shorthand - request get params schema', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/params/world/123'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { foo: 'world', test: 123 })
    })
  })

  test('shorthand - request get params schema error', t => {
    t.plan(3)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/params/world/string'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 400)
      t.deepEqual(JSON.parse(body), {
        error: 'Bad Request',
        message: 'params.test should be integer',
        statusCode: 400
      })
    })
  })

  test('shorthand - request get headers schema', t => {
    t.plan(4)
    sget({
      method: 'GET',
      headers: {
        'x-test': '1',
        'Y-Test': '3'
      },
      json: true,
      url: 'http://localhost:' + fastify.server.address().port + '/headers'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(body['x-test'], 1)
      t.strictEqual(body['y-test'], 3)
    })
  })

  test('shorthand - request get headers schema error', t => {
    t.plan(3)
    sget({
      method: 'GET',
      headers: {
        'x-test': 'abc'
      },
      url: 'http://localhost:' + fastify.server.address().port + '/headers'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 400)
      t.deepEqual(JSON.parse(body), {
        error: 'Bad Request',
        message: "headers['x-test'] should be number",
        statusCode: 400
      })
    })
  })

  test('shorthand - request get querystring schema', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 123 })
    })
  })

  test('shorthand - request get querystring schema error', t => {
    t.plan(3)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 400)
      t.deepEqual(JSON.parse(body), {
        error: 'Bad Request',
        message: 'querystring.hello should be integer',
        statusCode: 400
      })
    })
  })

  test('shorthand - request get missing schema', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/missing'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })

  test('shorthand - custom serializer', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/custom-serializer'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })

  test('shorthand - empty response', t => {
    t.plan(4)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/empty'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '0')
      t.deepEqual(body.toString(), '')
    })
  })

  test('shorthand - send a falsy boolean', t => {
    t.plan(3)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/boolean'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(body.toString(), 'false')
    })
  })

  test('shorthand - send null value', t => {
    t.plan(3)
    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/null'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(body.toString(), 'null')
    })
  })
})