Fastify

## Plugins
Fastify allows the user to extend its functionalities with plugins.
A plugin can be a set of routes, a server [decorator](https://github.com/fastify/fastify/blob/master/docs/Decorators.md) or whatever. The API that you will need to use one or more plugins, is `register`.

By default, `register` creates a *new scope*, this means that if you do some changes to the Fastify instance (via `decorate`), this change will not be reflected to the current context ancestors, but only to its sons. This feature allows us to achieve plugin *encapsulation* and *inheritance*, in this way we create a *direct acyclic graph* (DAG) and we will not have issues caused by cross dependencies.

You already see in the [getting started](https://github.com/fastify/fastify/blob/master/docs/Getting-Started.md#register) section how use this API, is pretty straightforward.
```
fastify.register(plugin, [options], [callback])
```
Example:
```js
fastify.register([
  require('./another-route'),
  require('./yet-another-route')
], opts, function (err) {
  if (err) throw err
})
```

#### Route Prefixing option
If you pass an option with the key `prefix` with a `string` value, Fastify will use it to prefix all the routes inside the register, for more info check [here](https://github.com/fastify/fastify/blob/master/docs/Routes.md#route-prefixing).

#### Error handling
The error handling is done by [avvio](https://github.com/mcollina/avvio#error-handling).
As general rule is highly recommended that you handle your errors in the `register`'s callback, otherwise the server will not start, and you will find the unhandled error in the `listen` callback.

### Create a plugin
Create a plugin is very easy, you just need to create a function that takes three parameters, the `fastify` instance, an options object and the next callback.
Example:
```js
module.exports = function (fastify, opts, next) {
  fastify.decorate('utility', () => {})

  fastify.get('/', handler)

  next()
}
```
You can also use `register` inside another `register`:
```js
module.exports = function (fastify, opts, next) {
  fastify.decorate('utility', () => {})

  fastify.get('/', handler)

  fastify.register(require('./other-plugin'))

  next()
}
```
Sometimes, you will need to know when the server is about to close, for example because you must close a connection to a database. To know when this is gonna happen, you can use the [`'onClose'`](https://github.com/fastify/fastify/blob/master/docs/Hooks.md#on-close) hook.

Do not forget that `register` will always create a new Fastify scope, if you don't need that, read the following section.

### Handle the scope
If you are using `register` only for extend a functionality of the server with  [`decorate`](https://github.com/fastify/fastify/blob/master/docs/Decorators.md), it is your responsibility tell Fastify to do not create a new scope, otherwise your changes will not be accessible by the user in the upper scope.

You have two ways to tell Fastify to avoid the creation of a new context:
- Use the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module
- Use the `'skip-override'` hidden property

We recommend to use the `fastify-plugin` module, because it solves this problem for you, and you can pass as parameter a version range of Fastify that your plugin support.
```js
const fp = require('fastify-plugin')

module.exports = fp(function (fastify, opts, next) {
  fastify.decorate('utility', () => {})
  next()
}, '0.x')
```
Check the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) documentation to know more about how use this module.

If you won't use the `fastify-plugin` module, you can use the `'skip-override'` hidden property, but we do not recommend it, because if in the future the Fastify API will change, it will be a your responsibility update the module, while if you use `fastify-plugin`, you can be sure about back compatibility.
```js
function yourPlugin (fastify, opts, next) {
  fastify.decorate('utility', () => {})
  next()
}
yourPlugin[Symbol.for('skip-override')] = true
module.exports = yourPlugin
```


* fastify's wrapped version of node.js IncomingMessage

query: {
[key: string]: any
},
params: {
[key: string]: any
},

'use strict'

const FindMyWay = require('find-my-way')
const avvio = require('avvio')
const http = require('http')
const https = require('https')
const pinoHttp = require('pino-http')
const Middie = require('middie')
const fastseries = require('fastseries')
var shot = null
try { shot = require('shot') } catch (e) { }

const Reply = require('./lib/reply')
const Request = require('./lib/request')
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
const buildSchema = require('./lib/validation').build
const handleRequest = require('./lib/handleRequest')
const isValidLogger = require('./lib/validation').isValidLogger
const decorator = require('./lib/decorate')
const ContentTypeParser = require('./lib/ContentTypeParser')
const Hooks = require('./lib/hooks')
const serializers = require('./lib/serializers')

function build (options) {
  options = options || {}
  if (typeof options !== 'object') {
    throw new TypeError('Options must be an object')
  }

  var logger
  if (options.logger && isValidLogger(options.logger)) {
    logger = pinoHttp({ logger: options.logger, serializers })
  } else {
    options.logger = options.logger || {}
    options.logger.level = options.logger.level || 'fatal'
    options.logger.serializers = options.logger.serializers || serializers
    logger = pinoHttp(options.logger)
  }

  const router = FindMyWay({ defaultRoute: defaultRoute })
  const middie = Middie(_runMiddlewares)
  const run = middie.run
  const map = new Map()
  const runHooks = fastseries()

  const app = avvio(fastify, {})
  // Override to allow the plugin incapsulation
  app.override = override

  var listening = false
  // true when Fastify is ready to go
  var started = false
  app.on('start', () => {
    started = true
  })

  var server
  if (options.https) {
    server = https.createServer(options.https, fastify)
  } else {
    server = http.createServer(fastify)
  }

  fastify.onClose((instance, done) => {
    if (listening) {
      instance.server.close(done)
    } else {
      done(null)
    }
  })

  if (Number(process.versions.node[0]) >= 6) {
    server.on('clientError', handleClientError)
  }

  // shorthand methods
  fastify.delete = _delete
  fastify.get = _get
  fastify.head = _head
  fastify.patch = _patch
  fastify.post = _post
  fastify.put = _put
  fastify.options = _options
  // extended route
  fastify.route = route
  fastify._RoutePrefix = new RoutePrefix()

  // expose logger instance
  fastify.logger = logger

  // hooks
  fastify.addHook = addHook
  fastify._hooks = new Hooks(fastify)

  // custom parsers
  fastify.addContentTypeParser = addContentTypeParser
  fastify.hasContentTypeParser = hasContentTypeParser
  fastify._contentTypeParser = new ContentTypeParser()

  // plugin
  fastify.register = fastify.use
  fastify.listen = listen
  fastify.server = server

  // extend server methods
  fastify.decorate = decorator.add
  fastify.hasDecorator = decorator.exist
  fastify.decorateReply = decorator.decorateReply
  fastify.decorateRequest = decorator.decorateRequest
  fastify.extendServerError = decorator.extendServerError

  fastify._Reply = Reply
  fastify._Request = Request

  // middleware support
  fastify.use = middie.use

  // exposes the routes map
  fastify[Symbol.iterator] = iterator

  // fake http injection (for testing purposes)
  fastify.inject = inject

  return fastify

  function fastify (req, res) {
    logger(req, res)

    // onRequest hook
    setImmediate(
      runHooks,
      new State(req, res),
      hookIterator,
      fastify._hooks.onRequest,
      middlewareCallback
    )
  }

  function _runMiddlewares (err, req, res) {
    if (err) {
      const reply = new Reply(req, res, null)
      reply.send(err)
      return
    }

    // preRouting hook
    setImmediate(
      runHooks,
      new State(req, res),
      hookIterator,
      fastify._hooks.preRouting,
      routeCallback
    )
  }

  function State (req, res) {
    this.req = req
    this.res = res
  }

  function hookIterator (fn, cb) {
    fn(this.req, this.res, cb)
  }

  function middlewareCallback (err, code) {
    if (err) {
      const reply = new Reply(this.req, this.res, null)
      if (code[0]) reply.code(code[0])
      reply.send(err)
      return
    }
    run(this.req, this.res)
  }

  function routeCallback (err, code) {
    if (err) {
      const reply = new Reply(this.req, this.res, null)
      if (code[0]) reply.code(code[0])
      reply.send(err)
      return
    }

    router.lookup(this.req, this.res)
  }

  function listen (port, address, cb) {
    const hasAddress = arguments.length === 3
    const _cb = (hasAddress) ? cb : address
    fastify.ready(function (err) {
      if (err) return _cb(err)
      if (hasAddress) {
        server.listen(port, address, _cb)
      } else {
        server.listen(port, _cb)
      }
      listening = true
    })
  }

  function override (instance, fn, opts) {
    if (fn[Symbol.for('skip-override')]) {
      return instance
    }

    instance = Object.create(instance)
    instance._Reply = Reply.buildReply(instance._Reply)
    instance._Request = Request.buildRequest(instance._Request)
    instance._contentTypeParser = ContentTypeParser.buildContentTypeParser(instance._contentTypeParser)
    instance._hooks = Hooks.buildHooks(instance._hooks)
    instance._RoutePrefix = buildRoutePrefix(instance._RoutePrefix, opts)

    return instance
  }

  function RoutePrefix () {
    this.prefix = ''
  }

  function buildRoutePrefix (r, opts) {
    const _RoutePrefix = Object.create(opts)
    const R = _RoutePrefix
    R.prefix = r.prefix
    if (typeof opts.prefix === 'string') {
      if (opts.prefix[0] !== '/') {
        opts.prefix = '/' + opts.prefix
      }
      R.prefix += opts.prefix
    }
    return R
  }

  // Shorthand methods
  function _delete (url, opts, handler) {
    return _route(this, 'DELETE', url, opts, handler)
  }

  function _get (url, opts, handler) {
    return _route(this, 'GET', url, opts, handler)
  }

  function _head (url, opts, handler) {
    return _route(this, 'HEAD', url, opts, handler)
  }

  function _patch (url, opts, handler) {
    return _route(this, 'PATCH', url, opts, handler)
  }

  function _post (url, opts, handler) {
    return _route(this, 'POST', url, opts, handler)
  }

  function _put (url, opts, handler) {
    return _route(this, 'PUT', url, opts, handler)
  }

  function _options (url, opts, handler) {
    return _route(this, 'OPTIONS', url, opts, handler)
  }

  function _route (self, method, url, options, handler) {
    if (!handler && typeof options === 'function') {
      handler = options
      options = {}
    }
    return route.call(self, {
      method,
      url,
      handler,
      schema: options.schema || {},
      Reply: self._Reply,
      Request: self._Request,
      contentTypeParser: self._contentTypeParser,
      preHandler: self._hooks.preHandler,
      RoutePrefix: self._RoutePrefix,
      beforeHandler: options.beforeHandler,
      config: options.config
    })
  }

  // Route management
  function route (opts) {
    const _fastify = this

    if (supportedMethods.indexOf(opts.method) === -1) {
      throw new Error(`${opts.method} method is not supported!`)
    }

    if (!opts.handler) {
      throw new Error(`Missing handler function for ${opts.method}:${opts.url} route.`)
    }

    _fastify._RoutePrefix = opts.RoutePrefix || _fastify._RoutePrefix

    _fastify.after((notHandledErr, done) => {
      const path = opts.url || opts.path
      const prefix = _fastify._RoutePrefix.prefix
      const url = prefix + (path === '/' && prefix.length > 0 ? '' : path)

      const config = opts.config || {}
      config.url = url

      const store = new Store(
        opts.schema,
        opts.handler,
        opts.Reply || _fastify._Reply,
        opts.Request || _fastify._Request,
        opts.contentTypeParser || _fastify._contentTypeParser,
        [],
        config
      )

      buildSchema(store)

      store.preHandler.push.apply(store.preHandler, (opts.preHandler || _fastify._hooks.preHandler))
      if (opts.beforeHandler) {
        opts.beforeHandler = Array.isArray(opts.beforeHandler) ? opts.beforeHandler : [opts.beforeHandler]
        store.preHandler.push.apply(store.preHandler, opts.beforeHandler)
      }

      if (map.has(url)) {
        if (map.get(url)[opts.method]) {
          return done(new Error(`${opts.method} already set for ${url}`))
        }

        map.get(url)[opts.method] = store
        router.on(opts.method, url, handleRequest, store)
      } else {
        const node = {}
        node[opts.method] = store
        map.set(url, node)
        router.on(opts.method, url, handleRequest, store)
      }
      done()
    })

    // chainable api
    return _fastify
  }

  function Store (schema, handler, Reply, Request, contentTypeParser, preHandler, config) {
    this.schema = schema
    this.handler = handler
    this.Reply = Reply
    this.Request = Request
    this.contentTypeParser = contentTypeParser
    this.preHandler = preHandler
    this.config = config
  }

  function iterator () {
    var entries = map.entries()
    var it = {}
    it.next = function () {
      var next = entries.next()

      if (next.done) {
        return {
          value: null,
          done: true
        }
      }

      var value = {}
      var methods = {}

      value[next.value[0]] = methods

      // out methods are saved Uppercase,
      // so we lowercase them for a better usability
      for (var method in next.value[1]) {
        methods[method.toLowerCase()] = next.value[1][method]
      }

      return {
        value: value,
        done: false
      }
    }
    return it
  }

  function inject (opts, cb) {
    if (!shot) throw new Error('"shot" library is not installed: "npm install shot --save-dev"')

    if (started) {
      shot.inject(this, opts, cb)
      return
    }

    this.ready(err => {
      if (err) throw err
      shot.inject(this, opts, cb)
    })
  }

  function addHook (name, fn) {
    this._hooks.add(name, fn)
    return this
  }

  function addContentTypeParser (contentType, fn) {
    this._contentTypeParser.add(contentType, fn)
    return this
  }

  function hasContentTypeParser (contentType, fn) {
    return this._contentTypeParser.hasParser(contentType)
  }

  function handleClientError (e, socket) {
    const body = JSON.stringify({
      error: http.STATUS_CODES['400'],
      message: 'Client Error',
      statusCode: 400
    })
    logger.logger.error(e, 'client error')
    socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: 'application/json'\r\n\r\n${body}`)
  }

  function defaultRoute (req, res, params) {
    const reply = new Reply(req, res, null)
    reply.code(404).send(new Error('Not found'))
  }
}

module.exports = build


'use strict'

const fastify = require('../fastify')()

fastify.use(require('cors')())
fastify.use(require('dns-prefetch-control')())
fastify.use(require('frameguard')())
fastify.use(require('hide-powered-by')())
fastify.use(require('hsts')())
fastify.use(require('ienoopen')())
fastify.use(require('x-xss-protection')())

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

fastify
  .get('/', opts, function (req, reply) {
    reply.header('Content-Type', 'application/json').code(200)
    reply.send({ hello: 'world' })
  })

fastify.listen(3000, err => {
  if (err) throw err
  console.log(`server listening on ${fastify.server.address().port}`)
})


/// <reference types="node" />
/// <reference types="pino" />

import * as http from 'http';
import * as pino from 'pino';

declare function fastify(opts?: fastify.ServerOptions): fastify.FastifyInstance;

declare namespace fastify {

  type Plugin = (instance: FastifyInstance, opts: Object, callback?: (err?: Error) => void) => void

  type Middleware = (req: http.IncomingMessage, res: http.OutgoingMessage, callback?: (err?: Error) => void) => void

  type HTTPMethod = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS';

  type FastifyMiddleware = (req: FastifyRequest, reply: FastifyReply, done: (err?: Error) => void) => void

  type RequestHandler = (req: FastifyRequest, res: FastifyReply) => void

  /**
   * fastify's wrapped version of node.js IncomingMessage
   */
  interface FastifyRequest {
    query: object,
    body: any,
    params: object,

    req: http.IncomingMessage
    log: pino.Logger
  }

  /**
   * Response object that is used to build and send a http response
   */
  interface FastifyReply {
    code: (statusCode: number) => FastifyReply
    header: (name: string, value: any) => FastifyReply
    type: (contentType: string) => FastifyReply
    redirect: (statusCode: number, url: string) => FastifyReply
    serializer: (fn: Function) => FastifyReply
    send: (payload?: string|Array<any>|Object|Error|Promise<any>|ReadableStream) => FastifyReply
    sent: boolean
  }

  interface ServerOptions {
    logger?: pino.LoggerOptions,
    https?: boolean
  }

  interface JSONSchema {
    // TODO - define/import JSONSchema types
    body?: Object
    querystring?: Object
    params?: Object
    response?: {
      [code: number]: Object
    }
  }

  /**
   * Optional configuration parameters for the route being created
   */
  interface RouteShorthandOptions {
    schema?: JSONSchema
    beforeHandler?: FastifyMiddleware
  }

  /**
   * Route configuration options such as "url" and "method"
   */
  interface RouteOptions extends RouteShorthandOptions {
    method: HTTPMethod,
    url: string,
    handler?: RequestHandler
  }

  /**
   * Represents the fastify instance created by the factory function the module exports.
   */
  interface FastifyInstance {
    server: http.Server

    /**
     * Adds a route to the server
     */
    route(opts: RouteOptions): FastifyInstance

    /**
     * Defines a GET route with the given mount path, options, and handler
     */
    get(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a GET route with the given mount path and handler
     */
    get(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a PUT route with the given mount path, options, and handler
     */
    put(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a PUT route with the given mount path and handler
     */
    put(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a PATCH route with the given mount path, options, and handler
     */
    patch(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a PATCH route with the given mount path and handler
     */
    patch(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a POST route with the given mount path, options, and handler
     */
    post(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a POST route with the given mount path and handler
     */
    post(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a HEAD route with the given mount path, options, and handler
     */
    head(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a HEAD route with the given mount path and handler
     */
    head(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a DELETE route with the given mount path, options, and handler
     */
    delete(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a DELETE route with the given mount path and handler
     */
    delete(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Defines a OPTIONS route with the given mount path, options, and handler
     */
    options(url: string, opts: RouteShorthandOptions, handler: RequestHandler): FastifyInstance

    /**
     * Defines a OPTIONS route with the given mount path and handler
     */
    options(url: string, handler: RequestHandler): FastifyInstance

    /**
     * Starts the server on the given port after all the plugins are loaded,
     * internally waits for the .ready() event. The callback is the same as the
     * Node core.
     */
    listen(port: number, hostname: string, backlog: number, callback?: (err: Error) => void): http.Server

    /**
     * Starts the server on the given port after all the plugins are loaded,
     * internally waits for the .ready() event. The callback is the same as the
     * Node core.
     */
    listen(port: number, hostname: string, callback?: (err: Error) => void): http.Server

    /**
     * Starts the server on the given port after all the plugins are loaded,
     * internally waits for the .ready() event. The callback is the same as the
     * Node core.
     */
    listen(port: number, callback?: (err: Error) => void): http.Server

    /**
     * Registers a listener function that is invoked when all the plugins have
     * been loaded. It receives an error parameter if something went wrong.
     */
    ready(readyListener?: () => void): void

    /**
     * Call this function to close the server instance and run the "onClose" callback
     */
    close(closeListener: () => void): void

    /**
     * Apply the given middleware to all incoming requests
     */
    use(middleware: Middleware): void

    /**
     * Apply the given middleware to routes matching the given path
     */
    use(path: string, middleware: Middleware): void

    /**
     * Registers a plugin or array of plugins on the server
     */
    register(plugin: Plugin|Array<Plugin>, opts?: RouteOptions, callback?: (err: Error) => void): FastifyInstance

    /**
     * Decorate this fastify instance with new properties. Throws an execption if
     * you attempt to add the same decorator name twice
     */
    decorate(name: string, decoration: any, dependencies?: Array<string>): FastifyInstance

    /**
     * Decorate reply objects with new properties. Throws an execption if
     * you attempt to add the same decorator name twice
     */
    decorateReply(name: string, decoration: any, dependencies?: Array<string>): FastifyInstance

    /**
     * Decorate request objects with new properties. Throws an execption if
     * you attempt to add the same decorator name twice
     */
    decorateResponse(name: string, decoration: any, dependencies?: Array<string>): FastifyInstance

    /**
     * Extends the standard server error. Return an object with the properties you'd
     * like added to the error
     */
    extendServerError(extendFn: () => Object): FastifyInstance

    /**
     * Determines if the given named decorator is available
     */
    hasDecorator(name: string): boolean

    /**
     * Add a hook that is triggered when a request is initially received
     */
    addHook(name: 'onRequest', hook: Middleware): FastifyInstance

    /**
     * Hook that is fired before a request is processed, but after the "onRequest"
     * hook
     */
    addHook(name: 'preHandler', hook: FastifyMiddleware)

    /**
     * Hook that is called when a response is about to be sent to a client
     */
    addHook(name: 'onResponse', hook: (res: http.OutgoingMessage, next: (err?: Error) => void) => void): FastifyInstance

    /**
     * Adds a hook that is triggered when server.close is called. Useful for closing connections
     * and performing cleanup tasks
     */
    addHook(name: 'onClose', hook: (instance: FastifyInstance, done: () => void) => void): FastifyInstance
  }
}

export = fastify;


Fastify

## Hooks

By using the hooks you can interact directly inside the lifecycle of Fastify, there are three different Hooks that you can use *(in order of execution)*:
- `'onRequest'`
- `'preRouting'`
- `'preHandler'`
- `'onClose'`

Example:
```js
fastify.addHook('onRequest', (req, res, next) => {
  // some code
  next()
})
```

Is pretty easy understand where each hook is executed, if you need a visual feedback take a look to the [lifecycle](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md) page.

If you get an error during the execution of you hook, just pass it to `next()` and Fastify will automatically close the request and send the appropriate error code to the user.

If you want to pass a custom error code to the user, just pass it as second parameter to `next()`:
```js
fastify.addHook('onRequest', (req, res, next) => {
  // some code
  next(new Error('some error'), 400)
})
```
*The error will be handled by [`Reply`](https://github.com/fastify/fastify/blob/master/docs/Reply.md#errors).*

The function signature is always the same, `request`, `response`, `next`, it changes a little bit only in the `'preHandler'` hook, where the first two arguments are [`request`](https://github.com/fastify/fastify/blob/master/docs/Request.md) and [`reply`](https://github.com/fastify/fastify/blob/master/docs/Reply.md) core Fastify objects.

**'onClose'**
The unique hook that is not inside the lifecycle is `'onClose'`, this one is triggered when you call `fastify.close()` to stop the server, and it is useful if you have some [plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins.md) that need a "shutdown" part, such as a connection to a database.
Only for this hook, the parameters of the function changes, the first one is the Fastify instance, the second one the `done` callback.
```js
fastify.addHook('onClose', (instance, done) => {
  // some code
  done()
})
```

### Scope
Talking about scope, the hooks works in a slightly different way from the Request/Reply encapsulation model. For instance, `onRequest`, `preRouting` and `onClose` are never encapsulated, not matter where you are declaring them, while the `preHandler` hook is always encapsulated if you declare it inside a `register`.

### beforeHandler
Despite the name, `beforeHandler` is not a standard hook like `preHandler`, but is a function that your register right in the route option that will be executed only in the specified route. Can be useful if you need to handle the authentication at route level instead of at hook level (`preHandler` for example.), it could also be an array of functions.
**`beforeHandler` is executed always after the `preHandler` hook.**

```js
fastify.addHook('preHandler', (request, reply, done) => {
  // your code
  done()
})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { ... },
  beforeHandler: function (request, reply, done) {
    // your code
    done()
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { ... },
  beforeHandler: [
    function first (request, reply, done) {
      // your code
      done()
    },
    function second (request, reply, done) {
      // your code
      done()
    }
  ],
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})
```


MIT License

Copyright (c) 2016-2017 Matteo Collina and the Fastify team

The Fastify team members are listed at https://github.com/fastify/fastify#team
and in the README file.

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

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

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


fastify

Fast and low overhead web framework, for Node.js

standard | snazzy && tap test/*.test.js test/*/*.test.js && tsc test/types/index.ts --target es6 --module commonjs --noEmit

tap --cov --coverage-report=html test/*.test.js test/*/*.test.js

tap test/*test.js  test/*/*.test.js --cov

node ./examples/$npm_config_usingfile & pid=$! && autocannon -c 100 -d 5 -p 10 localhost:3000/ && kill -9 $pid


.delete('/', opts, function (req, reply) {

.patch('/', opts, function (req, reply) {

// Using decorate requires casting so the compiler knows about new properties

keep keep keep keep keep replace keep keep keep keep keep

.patch('/:id', opts, function (req, reply) {

req.log.info(`incoming id is ${req.params.id}`);


'use strict'

const t = require('tap')
const test = t.test
const request = require('request')
const fastify = require('..')()
const safeStringify = require('fast-safe-stringify')

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

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'
        }
      }
    }
  }
}

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 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('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('wrong object for schema - get', t => {
  t.plan(1)
  try {
    fastify.get('/wrong-object-for-schema', numberSchema, function (req, reply) {
      // will send { hello: null }
      reply.code(200).send({ hello: 'world' })
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

test('custom serializer - get', t => {
  t.plan(1)
  try {
    fastify.get('/custom-serializer', numberSchema, function (req, reply) {
      reply.code(200).serializer(safeStringify).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()
  }
})

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

  test('shorthand - request get', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: '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: JSON.stringify([{
          keyword: 'type',
          dataPath: '.test',
          schemaPath: '#/properties/test/type',
          params: { type: 'integer' },
          message: 'should be integer'
        }]),
        statusCode: 400
      })
    })
  })

  test('shorthand - request get querystring schema', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: '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: JSON.stringify([{
          keyword: 'type',
          dataPath: '.hello',
          schemaPath: '#/properties/hello/type',
          params: { type: 'integer' },
          message: 'should be integer'
        }]),
        statusCode: 400
      })
    })
  })

  test('shorthand - request get missing schema', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: '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 - request get missing schema', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: 'http://localhost:' + fastify.server.address().port + '/wrong-object-for-schema'
    }, (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: null })
    })
  })

  test('shorthand - custom serializer', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: 'http://localhost:' + fastify.server.address().port + '/empty'
    }, (err, response, body) => {
      console.log(body)
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '0')
      t.deepEqual(body, '')
    })
  })
})


'use strict'

const fastJsonStringify = require('fast-json-stringify')
const fastSafeStringify = require('fast-safe-stringify')
const Ajv = require('ajv')
const ajv = new Ajv({ coerceTypes: true })

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

const schemas = require('./schemas.json')
const inputSchemaError = fastJsonStringify(schemas.inputSchemaError)

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 (opts) {
  if (!opts.schema) {
    return
  }

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

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

  if (opts.schema.querystring) {
    // querystring will always be an object, allow schema def to skip this
    if (!opts.schema.querystring.type || !opts.schema.querystring.properties) {
      opts.schema.querystring = {
        type: 'object',
        properties: opts.schema.querystring
      }
    }

    opts[querystringSchema] = ajv.compile(opts.schema.querystring)
  }

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

function validate (handle, params, body, query) {
  if (handle[paramsSchema] && !handle[paramsSchema](params)) {
    return inputSchemaError(handle[paramsSchema].errors)
  }

  if (handle[bodySchema] && !handle[bodySchema](body)) {
    return inputSchemaError(handle[bodySchema].errors)
  }

  if (handle[querystringSchema] && !handle[querystringSchema](query)) {
    return inputSchemaError(handle[querystringSchema].errors)
  }
  return true
}

function serialize (handle, data, statusCode) {
  var responseSchemaDef = handle[responseSchema]
  if (!responseSchemaDef) {
    return fastSafeStringify(data)
  }
  if (responseSchemaDef[statusCode]) {
    return responseSchemaDef[statusCode](data)
  }
  var fallbackStatusCode = (statusCode + '')[0] + 'xx'
  if (responseSchemaDef[fallbackStatusCode]) {
    return responseSchemaDef[fallbackStatusCode](data)
  }
  return fastSafeStringify(data)
}

function isValidLogger (logger) {
  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
}

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


match = err.detail.match(/Key \((.*?)\)=\((.*?)\) already exists/);

match = err.message.match(/violates foreign key constraint \"(.+?)\" on table \"(.+?)\"/);

'use strict';

/**
 * Sequelize provides a host of custom error classes, to allow you to do easier debugging. All of these errors are exposed on the sequelize object and the sequelize constructor.
 * All sequelize errors inherit from the base JS error object.
 *
 * @fileOverview The Error Objects produced by Sequelize.
 * @class Errors
 */

var util = require('util');
var error = module.exports = {};

/**
 * The Base Error all Sequelize Errors inherit from.
 *
 * @constructor
 * @alias Error
 */
error.BaseError = function() {
  var tmp = Error.apply(this, arguments);
  tmp.name = this.name = 'SequelizeBaseError';

  Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
  this.message = tmp.message;
};
util.inherits(error.BaseError, Error);

/**
 * Validation Error. Thrown when the sequelize validation has failed. The error contains an `errors` property,
 * which is an array with 1 or more ValidationErrorItems, one for each validation that failed.
 *
 * @param {string} message Error message
 * @param {Array} [errors] Array of ValidationErrorItem objects describing the validation errors
 *
 * @extends BaseError
 * @constructor
 */
error.ValidationError = function(message, errors) {
  error.BaseError.apply(this, arguments);
  this.name = 'SequelizeValidationError';
  this.errors = errors || [];
};
util.inherits(error.ValidationError, error.BaseError);

/**
 * Gets all validation error items for the path / field specified.
 *
 * @param {string} path The path to be checked for error items
 * @returns {Array} Validation error items for the specified path
 */
error.ValidationError.prototype.get = function(path) {
  return this.errors.reduce(function(reduced, error) {
    if (error.path === path) {
      reduced.push(error);
    }
    return reduced;
  }, []);
};

/**
 * An array of ValidationErrorItems
 * @property errors
 * @name errors
 */
error.ValidationError.prototype.errors;

/**
 * A base class for all database related errors.
 * @extends BaseError
 * @constructor
 */
error.DatabaseError = function (parent) {
  error.BaseError.apply(this, arguments);
  this.name = 'SequelizeDatabaseError';

  this.parent = parent;
  this.sql = parent.sql;
};
util.inherits(error.DatabaseError, error.BaseError);

/**
 * The database specific error which triggered this one
 * @property parent
 * @name parent
 */
error.DatabaseError.prototype.parent;
/**
 * The SQL that triggered the error
 * @property sql
 * @name sql
 */
error.DatabaseError.prototype.sql;

/**
 * Thrown when a database query times out because of a deadlock
 * @extends DatabaseError
 * @constructor
 */
error.TimeoutError = function (parent) {
  error.DatabaseError.call(this, parent);
  this.name = 'SequelizeTimeoutError';
};
util.inherits(error.TimeoutError, error.BaseError);

/**
 * Thrown when a unique constraint is violated in the database
 * @extends DatabaseError
 * @constructor
 */
error.UniqueConstraintError = function (options) {
  options = options || {};
  options.parent = options.parent || { sql: '' };

  error.DatabaseError.call(this, options.parent);
  this.name = 'SequelizeUniqueConstraintError';

  this.message = options.message;
  this.fields = options.fields;
  this.value = options.value;
  this.index = options.index;
};
util.inherits(error.UniqueConstraintError, error.DatabaseError);

/**
 * Thrown when a foreign key constraint is violated in the database
 * @extends DatabaseError
 * @constructor
 */
error.ForeignKeyConstraintError = function (options) {
  options = options || {};
  options.parent = options.parent || { sql: '' };

  error.DatabaseError.call(this, options.parent);
  this.name = 'SequelizeForeignKeyConstraintError';

  this.message = options.message;
  this.fields = options.fields;
  this.value = options.value;
  this.index = options.index;
};
util.inherits(error.ForeignKeyConstraintError, error.DatabaseError);

/**
 * The message from the DB.
 * @property message
 * @name message
 */
error.DatabaseError.prototype.message;
/**
 * The fields of the unique constraint
 * @property fields
 * @name fields
 */
error.DatabaseError.prototype.fields;
/**
 * The value(s) which triggered the error
 * @property value
 * @name value
 */
error.DatabaseError.prototype.value;
/**
 * The name of the index that triggered the error
 * @property index
 * @name index
 */
error.DatabaseError.prototype.index;

/**
 * Validation Error Item
 * Instances of this class are included in the `ValidationError.errors` property.
 *
 * @param {string} message An error message
 * @param {string} type The type of the validation error
 * @param {string} path The field that triggered the validation error
 * @param {string} value The value that generated the error
 * @constructor
 */
error.ValidationErrorItem = function(message, type, path, value) {
  this.message = message || '';
  this.type = type || null;
  this.path = path || null;
  this.value = value || null;
};


'use strict';

var moment = require('moment')
  , isArrayBufferView
  , SqlString = exports;

if (typeof ArrayBufferView === 'function') {
  isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView); };
} else {
  var arrayBufferViews = [
    Int8Array, Uint8Array, Int16Array, Uint16Array,
    Int32Array, Uint32Array, Float32Array, Float64Array
  ];
  isArrayBufferView = function(object) {
    for (var i = 0; i < 8; i++) {
      if (object instanceof arrayBufferViews[i]) {
        return true;
      }
    }
    return false;
  };
}

SqlString.escapeId = function(val, forbidQualified) {
  if (forbidQualified) {
    return '`' + val.replace(/`/g, '``') + '`';
  }
  return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
};

SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
  if (arguments.length === 1 && typeof arguments[0] === 'object') {
    val = val.val || val.value || null;
    stringifyObjects = val.stringifyObjects || val.objects || undefined;
    timeZone = val.timeZone || val.zone || null;
    dialect = val.dialect || null;
    field = val.field || null;
  }
  else if (arguments.length < 3 && typeof arguments[1] === 'object') {
    timeZone = stringifyObjects.timeZone || stringifyObjects.zone || null;
    dialect = stringifyObjects.dialect || null;
    field = stringifyObjects.field || null;
  }

  if (val === undefined || val === null) {
    return 'NULL';
  }

  switch (typeof val) {
  case 'boolean':
    // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
    // for us. Postgres actually has a boolean type with true/false literals,
    // but sequelize doesn't use it yet.
    return dialect === 'sqlite' ? +!!val : ('' + !!val);
  case 'number':
    return val + '';
  }

  if (val instanceof Date) {
    val = SqlString.dateToString(val, timeZone || 'Z', dialect);
  }

  if (Buffer.isBuffer(val)) {
    return SqlString.bufferToString(val, dialect);
  }
  if (Array.isArray(val) || isArrayBufferView(val)) {
    return SqlString.arrayToList(val, timeZone, dialect, field);
  }

  if (typeof val === 'object') {
    if (stringifyObjects) {
      val = val.toString();
    } else {
      return SqlString.objectToValues(val, timeZone);
    }
  }

  if (dialect === 'postgres' || dialect === 'sqlite') {
    // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
    // http://stackoverflow.com/q/603572/130598
    val = val.replace(/'/g, "''");
  } else {
    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch (s) {
        case '\0': return '\\0';
        case '\n': return '\\n';
        case '\r': return '\\r';
        case '\b': return '\\b';
        case '\t': return '\\t';
        case '\x1a': return '\\Z';
        default: return '\\' + s;
      }
    });
  }
  return "'" + val + "'";
};

SqlString.arrayToList = function(array, timeZone, dialect, field) {
  var valstr, i;
  if (dialect === 'postgres') {
    valstr = '';
    if (array.map) {
      valstr = array.map(function(v) {
        return SqlString.escape(v, true, timeZone, dialect, field);
      }).join(',');
    } else {
      for (i = 0; i < array.length; i++) {
        valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ',';
      }
      valstr = valstr.slice(0, -1);
    }
    var ret = 'ARRAY[' + valstr + ']';
    if (!!field && !!field.type) {
      ret += '::' + field.type.replace(/\(\d+\)/g, '');
    }
    return ret;
  } else {
    if (array.map) {
      return array.map(function(v) {
        if (Array.isArray(v)) {
          return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')';
        }
        return SqlString.escape(v, true, timeZone, dialect);
      }).join(', ');
    } else {
      valstr = '';
      for (i = 0; i < array.length; i++) {
        valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', ';
      }
      return valstr.slice(0, -2);
    }
  }
};

SqlString.format = function(sql, values, timeZone, dialect) {
  values = [].concat(values);

  return sql.replace(/\?/g, function(match) {
    if (!values.length) {
      return match;
    }

    return SqlString.escape(values.shift(), false, timeZone, dialect);
  });
};

SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
  return sql.replace(/\:+(?!\d)(\w+)/g, function(value, key) {
    if ('postgres' === dialect && '::' === value.slice(0, 2)) {
      return value;
    }

    if (values.hasOwnProperty(key)) {
      return SqlString.escape(values[key], false, timeZone, dialect);
    } else {
      throw new Error('Named parameter "' + value + '" has no value in the given object.');
    }
  });
};

SqlString.dateToString = function(date, timeZone, dialect) {
  date = moment(date).zone(timeZone);

  if (dialect === 'mysql' || dialect === 'mariadb') {
    return date.format('YYYY-MM-DD HH:mm:ss');
  } else {
    // ZZ here means current timezone, _not_ UTC
    return date.format('YYYY-MM-DD HH:mm:ss.SSS Z');
  }
};

SqlString.bufferToString = function(buffer, dialect) {
  var hex = buffer.toString('hex');

  if (dialect === 'postgres') {
    // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
    return "E'\\\\x" + hex + "'";
  }
  return "X'" + hex + "'";
};

SqlString.objectToValues = function(object, timeZone) {
  var values = [];
  for (var key in object) {
    var value = object[key];
    if (typeof value === 'function') {
      continue;
    }

    values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone));
  }

  return values.join(', ');
};

function zeroPad(number) {
  return (number < 10) ? '0' + number : number;
}


/* jshint camelcase: false */
/* jshint expr: true */
var chai      = require('chai')
  , Sequelize = require('../../index')
  , expect    = chai.expect
  , Support   = require(__dirname + '/../support')
  , DataTypes = require(__dirname + "/../../lib/data-types")
  , dialect   = Support.getTestDialect()
  , datetime  = require('chai-datetime')

chai.use(datetime)
chai.config.includeStack = true

describe(Support.getTestDialectTeaser("DAOFactory"), function () {
  beforeEach(function(done) {
    this.User = this.sequelize.define('User', {
      username:     DataTypes.STRING,
      secretValue:  DataTypes.STRING,
      data:         DataTypes.STRING,
      intVal:       DataTypes.INTEGER,
      theDate:      DataTypes.DATE,
      aBool:        DataTypes.BOOLEAN
    })

    this.User.sync({ force: true }).success(function() {
      done()
    })
  })

  ;(['or', 'and']).forEach(function(method) {
    var word = method.toUpperCase()

    describe('Sequelize.' + method, function() {
      it('can handle plain strings', function(done) {
        this.User.find({
          where: Sequelize[method]( "1=1", "2=2" )
        }).on('sql', function(sql) {
          expect(sql).to.contain("WHERE (1=1 " + word + " 2=2) LIMIT 1")
          done()
        })
      })

      it('can handle arrays', function(done) {
        this.User.find({
          where: Sequelize[method]( ["1=?", 1], ["2=?", 2] )
        }).on('sql', function(sql) {
          expect(sql).to.contain("WHERE (1=1 " + word + " 2=2) LIMIT 1")
          done()
        })
      })

      it('can handle objects', function(done) {
        this.User.find({
          where: Sequelize[method]( { username: "foo", intVal: 2 }, { secretValue: 'bar' } )
        }).on('sql', function(sql) {
          var expectation = ({
            mysql: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')",
            sqlite: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')",
            postgres: 'WHERE ("User"."username"=\'foo\' AND "User"."intVal"=2 ' + word + ' "User"."secretValue"=\'bar\')',
            mariadb: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')"
          })[Support.getTestDialect()]

          if (!expectation) {
            console.log(sql)
            throw new Error('Undefined expectation for ' + Support.getTestDialect())
          }

          expect(sql).to.contain(expectation)

          done()
        })
      })

      it('can handle numbers', function(done) {
        this.User.find({
          where: Sequelize[method]( 1, 2 )
        }).on('sql', function(sql) {
          var expectation = ({
            mysql: "WHERE (`User`.`id`=1 " + word + " `User`.`id`=2)",
            sqlite: "WHERE (`User`.`id`=1 " + word + " `User`.`id`=2)",
            postgres: 'WHERE ("User"."id"=1 ' + word + ' "User"."id"=2)',
            mariadb: "WHERE (`User`.`id`=1 " + word + " `User`.`id`=2)"
          })[Support.getTestDialect()]

          if (!expectation) {
            console.log(sql)
            throw new Error('Undefined expectation for ' + Support.getTestDialect())
          }

          expect(sql).to.contain(expectation)

          done()
        })
      })
    })
  })

  describe('Combinations of Sequelize.and and Sequelize.or', function() {
    it('allows nesting of Sequelize.or', function(done) {
      this.User.find({
        where: Sequelize.and( Sequelize.or("1=1", "2=2"), Sequelize.or("3=3", "4=4") )
      }).on('sql', function(sql) {
        expect(sql).to.contain("WHERE ((1=1 OR 2=2) AND (3=3 OR 4=4)) LIMIT 1")
        done()
      })
    })

    it('allows nesting of Sequelize.or using object notation', function(done) {
      this.User.find({
        where: Sequelize.and( Sequelize.or({username: {eq: "foo"}}, {username: {eq:"bar"}}),
                              Sequelize.or({id: {eq: 1}}, {id: {eq:4}}) )
      }).on('sql', function(sql) {
        var expectation = ({
          mysql: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1",
          sqlite: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1",
          postgres: 'WHERE (("User"."username" = \'foo\' OR "User"."username" = \'bar\') AND ("User"."id" = 1 OR "User"."id" = 4)) LIMIT 1',
          mariadb: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1"
        })[Support.getTestDialect()]

        if (!expectation) {
          console.log(sql)
          throw new Error('Undefined expectation for ' + Support.getTestDialect())
        }

        expect(sql).to.contain(expectation)
        done()
      })
    })

    it('allows nesting of Sequelize.and', function(done) {
      this.User.find({
        where: Sequelize.or( Sequelize.and("1=1", "2=2"), Sequelize.and("3=3", "4=4") )
      }).on('sql', function(sql) {
        expect(sql).to.contain("WHERE ((1=1 AND 2=2) OR (3=3 AND 4=4)) LIMIT 1")
        done()
      })
    })

    it('allows nesting of Sequelize.and using object notation', function(done) {
      this.User.find({
        where: Sequelize.or( Sequelize.and({username: {eq: "foo"}}, {username: {eq:"bar"}}),
                              Sequelize.and({id: {eq: 1}}, {id: {eq:4}}) )
      }).on('sql', function(sql) {
        var expectation = ({
          mysql: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1",
          sqlite: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1",
          postgres: 'WHERE (("User"."username" = \'foo\' AND "User"."username" = \'bar\') OR ("User"."id" = 1 AND "User"."id" = 4)) LIMIT 1',
          mariadb: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1"
        })[Support.getTestDialect()]

        if (!expectation) {
          console.log(sql)
          throw new Error('Undefined expectation for ' + Support.getTestDialect())
        }

        expect(sql).to.contain(expectation)
        done()
      })
    })

    if (dialect !== 'postgres') {
      it('still allows simple arrays lookups', function (done) {
        this.User.find({
          where: ["id IN (?) OR id IN (?)", [1, 2], [3, 4]]
        }).on('sql', function(sql) {
          expect(sql).to.contain("id IN (1, 2) OR id IN (3, 4)")
          done()
        })
      })
    }

    ;(['find', 'findAll']).forEach(function(finderMethod) {
      it('correctly handles complex combinations', function(done) {
        this.User[finderMethod]({
          where: [
            42, "2=2", ["1=?", 1], { username: "foo" },
            Sequelize.or(
              42, "2=2", ["1=?", 1], { username: "foo" },
              Sequelize.and( 42, "2=2", ["1=?", 1], { username: "foo" } ),
              Sequelize.or( 42, "2=2", ["1=?", 1], { username: "foo" } )
            ),
            Sequelize.and(
              42, "2=2", ["1=?", 1], { username: "foo" },
              Sequelize.or( 42, "2=2", ["1=?", 1], { username: "foo" } ),
              Sequelize.and( 42, "2=2", ["1=?", 1], { username: "foo" } )
            )
          ]
        }).on('sql', function(sql) {
          if (Support.getTestDialect() === 'postgres') {
            expect(sql).to.contain(
              'WHERE (' + [
                '"User"."id"=42 AND 2=2 AND 1=1 AND "User"."username"=\'foo\' AND ',
                  '(',
                    '"User"."id"=42 OR 2=2 OR 1=1 OR "User"."username"=\'foo\' OR ',
                    '("User"."id"=42 AND 2=2 AND 1=1 AND "User"."username"=\'foo\') OR ',
                    '("User"."id"=42 OR 2=2 OR 1=1 OR "User"."username"=\'foo\')',
                  ') AND ',
                  '(',
                    '"User"."id"=42 AND 2=2 AND 1=1 AND "User"."username"=\'foo\' AND ',
                    '("User"."id"=42 OR 2=2 OR 1=1 OR "User"."username"=\'foo\') AND ',
                    '("User"."id"=42 AND 2=2 AND 1=1 AND "User"."username"=\'foo\')',
                  ')'
                ].join("") +
              ')'
            )
          } else {
            expect(sql).to.contain(
              "WHERE (" + [
                "`User`.`id`=42 AND 2=2 AND 1=1 AND `User`.`username`='foo' AND ",
                  "(",
                    "`User`.`id`=42 OR 2=2 OR 1=1 OR `User`.`username`='foo' OR ",
                    "(`User`.`id`=42 AND 2=2 AND 1=1 AND `User`.`username`='foo') OR ",
                    "(`User`.`id`=42 OR 2=2 OR 1=1 OR `User`.`username`='foo')",
                  ") AND ",
                  "(",
                    "`User`.`id`=42 AND 2=2 AND 1=1 AND `User`.`username`='foo' AND ",
                    "(`User`.`id`=42 OR 2=2 OR 1=1 OR `User`.`username`='foo') AND ",
                    "(`User`.`id`=42 AND 2=2 AND 1=1 AND `User`.`username`='foo')",
                  ")"
                ].join("") +
              ")"
            )
          }

          done()
        })
      })
    })
  })
})