'use strict'

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

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

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

  test('route - get', t => {
    t.plan(3)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: '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)
    request({
      method: 'GET',
      uri: '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' })
    })

    request({
      method: 'DELETE',
      uri: '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)

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

    const reqOpts = {
      method: 'GET',
      uri: 'http://localhost:' + fastify.server.address().port + '/path'
    }
    request(reqOpts, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})


t.equal(line.msg, 'kaboom', 'message is set')

t.equal(line.req.method, 'GET', 'method is get')

t.equal(line.msg, 'incoming request', 'message is set')

Fastify

## Server Methods

#### server
`fastify.server`: The Node core [server](https://nodejs.org/api/http.html#http_class_http_server) object

#### ready
Function called when all the plugins has been loaded.
It takes an error parameter if something went wrong.
```js
fastify.ready(err => {
  if (err) throw err
})
```

#### listen
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.
```js
fastify.listen(3000, err => {
  if (err) throw err
})
```

Specifying an address is also supported:

```js
fastify.listen(3000, '127.0.0.1', err => {
  if (err) throw err
})
```

#### route
Method to add routes to the server, it also have shorthands functions, check [here](https://github.com/fastify/fastify/blob/master/docs/Routes.md).

#### routes iterator
The Fastify instance is an Iterable object with all the registered routes.
The route properties are the same the developer has declared [here](https://github.com/fastify/fastify/blob/master/docs/Routes.md).
```js
fastify.get('/route', opts, handler)

fastify.ready(() => {
  for (var route of fastify) {
    console.log(route)
    /* will output:
    {
      '/route': {
        get: {
          method: String,
          ulr: String,
          schema: Object,
          handler: Function,
          Request: Function,
          Reply: Function
        }
      }
    }
    */
  }
})
```

#### close
`fastify.close(callback)`: call this function to close the server instance and run the [`'onClose'`](https://github.com/fastify/fastify/blob/master/docs/Hooks.md#on-close) hook.

#### decorate*
Function useful if you need to decorate the fastify instance, Reply or Request, check [here](https://github.com/fastify/fastify/blob/master/docs/Decorators.md).

#### register
Fastify allows the user to extend its functionalities with plugins.
A plugin can be a set of routes, a server decorator or whatever, check [here](https://github.com/fastify/fastify/blob/master/docs/Plugins.md).

#### use
Function to add middlewares to Fastify, check  [here](https://github.com/fastify/fastify/blob/master/docs/Middlewares.md).

#### addHook
Function to add a specific hook in the lifecycle of Fastify, check  [here](https://github.com/fastify/fastify/blob/master/docs/Hooks.md).

#### logger
The logger instance, check  [here](https://github.com/fastify/fastify/blob/master/docs/Logging.md).

#### inject
Fake http injection (for testing purposes)  [here](https://github.com/fastify/fastify/blob/master/docs/Testing.md#inject).

#### setSchemaCompiler
Set the schema compiler for all routes  [here](https://github.com/fastify/fastify/blob/master/docs/Validation-And-Serialize.md#schema-compiler).


'use strict'

/**
 * Code imported from `pino-http`
 * Repo: https://github.com/pinojs/pino-http
 * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
 */

const pino = require('pino')

function createLogger (opts, stream) {
  stream = stream || opts.stream
  delete opts.stream

  var prevLogger = opts.logger
  var prevGenReqId = opts.genReqId
  var logger = null

  if (prevLogger) {
    opts.logger = undefined
    opts.genReqId = undefined
    logger = prevLogger.child(opts)
    opts.logger = prevLogger
    opts.genReqId = prevGenReqId
  } else {
    logger = pino(opts, stream)
  }

  return logger
}

function reqIdGenFactory () {
  var maxInt = 2147483647
  var nextReqId = 0
  return function genReqId () {
    return (nextReqId = (nextReqId + 1) & maxInt)
  }
}

const serializers = {
  req: function asReqValue (req) {
    return {
      id: req.id,
      method: req.method,
      url: req.url,
      remoteAddress: req.connection.remoteAddress,
      remotePort: req.connection.remotePort
    }
  },
  err: pino.stdSerializers.err,
  res: function asResValue (res) {
    return {
      statusCode: res.statusCode
    }
  }
}

function OnResponseState (err, res) {
  this.err = err
  this.res = res
}

function onResponseIterator (fn, cb) {
  fn(this.res, cb)
}

function now () {
  var ts = process.hrtime()
  return (ts[0] * 1e3) + (ts[1] / 1e6)
}

function onResponseCallback (err, res) {
  var responseTime = now() - res._startTime

  if (err) {
    res.log.error({
      res,
      err,
      responseTime
    }, 'request errored')
    return
  }

  res.log.info({
    res,
    responseTime
  }, 'request completed')
}

module.exports = {
  createLogger,
  reqIdGenFactory,
  serializers,
  now,
  OnResponseState,
  onResponseIterator,
  onResponseCallback
}


t.equal(line.res.statusCode, 500, 'statusCode is 500')

test('can use external logger instance', t => {

t.equal(line.msg, 'kaboom', 'message is set')

'use strict'

const FindMyWay = require('find-my-way')
const avvio = require('avvio')
const Ajv = require('ajv')
const http = require('http')
const https = require('https')
const Middie = require('middie')
const runHooks = 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 schemaCompiler = require('./lib/validation').schemaCompiler
const decorator = require('./lib/decorate')
const ContentTypeParser = require('./lib/ContentTypeParser')
const Hooks = require('./lib/hooks')
const loggerUtils = require('./lib/logger')

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

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

  const ajv = new Ajv(Object.assign({ coerceTypes: true }, options.ajv))

  const router = FindMyWay({ defaultRoute: defaultRoute })
  const map = new Map()

  // logger utils
  const customGenReqId = options.logger ? options.logger.genReqId : null
  const genReqId = customGenReqId || loggerUtils.reqIdGenFactory()
  const now = loggerUtils.now
  const OnResponseState = loggerUtils.OnResponseState
  const onResponseIterator = loggerUtils.onResponseIterator
  const onResponseCallback = loggerUtils.onResponseCallback

  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
  fastify.all = _all
  // extended route
  fastify.route = route
  fastify._RoutePrefix = new RoutePrefix()

  // expose logger instance
  fastify.logger = logger

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

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

  fastify.setSchemaCompiler = setSchemaCompiler
  fastify._schemaCompiler = schemaCompiler.bind({ ajv: ajv })

  // 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 = use
  fastify._middie = Middie(onRunMiddlewares)
  fastify._middlewares = []

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

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

  return fastify

  function fastify (req, res) {
    req.id = genReqId()
    req.log = res.log = logger.child({ req: req })

    res._startTime = now()
    res._context = null
    res.on('finish', onResFinished)
    res.on('error', onResFinished)

    router.lookup(req, res)
  }

  function onResFinished (err) {
    this.removeListener('finish', onResFinished)
    this.removeListener('error', onResFinished)

    var ctx = this._context

    if (ctx !== null && ctx.onResponse.length > 0) {
      // deferring this with setImmediate will
      // slow us by 10%
      runHooks(new OnResponseState(err, this),
        onResponseIterator,
        ctx.onResponse,
        wrapOnResponseCallback)
    } else {
      onResponseCallback(err, this)
    }
  }

  function wrapOnResponseCallback (err) {
    onResponseCallback(this.err || err, 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 (listening) {
        return _cb(new Error('Fastify is already listening'))
      }

      server.on('error', wrap)
      if (hasAddress) {
        server.listen(port, address, wrap)
      } else {
        server.listen(port, wrap)
      }
      listening = true
    })

    function wrap (err) {
      server.removeListener('error', wrap)
      if (_cb) {
        _cb(err)
      } else if (err) {
        // this will crash the process
        // it will go to 'uncaughtException'
        throw err
      }
    }
  }

  function startHooks (req, res, params, store) {
    res._context = store
    runHooks(
      new State(req, res, params, store),
      hookIterator,
      store.onRequest,
      middlewareCallback
    )
  }

  function middlewareCallback (err) {
    if (err) {
      const reply = new Reply(this.req, this.res, this.store)
      reply.send(err)
      return
    }
    this.store._middie.run(this.req, this.res, this)
  }

  function onRunMiddlewares (err, req, res, ctx) {
    if (err) {
      const reply = new Reply(req, res, ctx.store)
      reply.send(err)
      return
    }

    handleRequest(req, res, ctx.params, ctx.store)
  }

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

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

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

    const middlewares = Object.assign([], old._middlewares)
    const instance = Object.create(old)
    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)
    instance._middlewares = []
    instance._middie = Middie(onRunMiddlewares)

    for (var i = 0; i < middlewares.length; i++) {
      instance.use.apply(instance, middlewares[i])
    }

    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 _all (url, opts, handler) {
    return _route(this, supportedMethods, 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,
      onRequest: self._hooks.onRequest,
      preHandler: self._hooks.preHandler,
      RoutePrefix: self._RoutePrefix,
      beforeHandler: options.beforeHandler,
      onResponse: options.onResponse,
      config: options.config,
      middie: self._middie,
      schemaCompiler: options.schemaCompiler
    })
  }

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

    if (Array.isArray(opts.method)) {
      for (var i = 0; i < opts.method.length; i++) {
        if (supportedMethods.indexOf(opts.method[i]) === -1) {
          throw new Error(`${opts.method[i]} method is not supported!`)
        }
      }
    } else {
      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,
        opts.onRequest || _fastify._hooks.onRequest,
        [],
        opts.onResponse || _fastify._hooks.onResponse,
        config,
        opts.middie || _fastify._middie
      )

      buildSchema(store, opts.schemaCompiler || _fastify._schemaCompiler)

      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}`))
        }

        if (Array.isArray(opts.method)) {
          for (i = 0; i < opts.method.length; i++) {
            map.get(url)[opts.method[i]] = store
          }
        } else {
          map.get(url)[opts.method] = store
        }
        router.on(opts.method, url, startHooks, store)
      } else {
        const node = {}
        if (Array.isArray(opts.method)) {
          for (i = 0; i < opts.method.length; i++) {
            node[opts.method[i]] = store
          }
        } else {
          node[opts.method] = store
        }
        map.set(url, node)
        router.on(opts.method, url, startHooks, store)
      }
      done()
    })

    // chainable api
    return _fastify
  }

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

  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 use (url, fn) {
    if (typeof url === 'string') {
      const prefix = this._RoutePrefix.prefix
      url = prefix + (url === '/' && prefix.length > 0 ? '' : url)
    }
    this._middlewares.push([url, fn])
    this._middie.use(url, fn)
    return this
  }

  function addHook (name, fn) {
    if (name === 'onClose') {
      this.onClose(fn)
    } else {
      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.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'))
  }

  function setSchemaCompiler (schemaCompiler) {
    this._schemaCompiler = schemaCompiler
    return this
  }
}

module.exports = build


'use strict'

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

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

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

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

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

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

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

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

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

  test('shorthand - request delete', t => {
    t.plan(4)
    request({
      method: 'DELETE',
      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 delete params schema', t => {
    t.plan(4)
    request({
      method: 'DELETE',
      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 delete params schema error', t => {
    t.plan(3)
    request({
      method: 'DELETE',
      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 delete headers schema', t => {
    t.plan(4)
    request({
      method: 'DELETE',
      headers: {
        'x-test': 1
      },
      uri: 'http://localhost:' + fastify.server.address().port + '/headers'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.strictEqual(JSON.parse(body)['x-test'], 1)
    })
  })

  test('shorthand - request delete headers schema error', t => {
    t.plan(3)
    request({
      method: 'DELETE',
      headers: {
        'x-test': 'abc'
      },
      uri: '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: JSON.stringify([{
          keyword: 'type',
          dataPath: '[\'x-test\']',
          schemaPath: '#/properties/x-test/type',
          params: { type: 'number' },
          message: 'should be number'
        }]),
        statusCode: 400
      })
    })
  })

  test('shorthand - request delete querystring schema', t => {
    t.plan(4)
    request({
      method: 'DELETE',
      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 delete querystring schema error', t => {
    t.plan(3)
    request({
      method: 'DELETE',
      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 delete missing schema', t => {
    t.plan(4)
    request({
      method: 'DELETE',
      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' })
    })
  })
})


'use strict'

const t = require('tap')
const test = t.test
const request = require('request')
const fs = require('fs')
const path = require('path')
const Fastify = require('../..')

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

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

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

  test('https get request', t => {
    t.plan(4)
    request({
      method: 'GET',
      uri: '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' })
    })
  })
})


Fastify

# The hitchhiker's guide to plugins
First of all, `DON'T PANIC`!

Fastify has been built since the beginning to be an extremely modular system, we built a powerful api that allows you to add methods and utilities to Fastify by creating a namespace, we built a system that creates an encapsulation model that allows you to split you application in multiple microservices at any moment, without the need to refactor the entire application.

**Table of contents**
- [Register](#register)
- [Decorators](#decorators)
- [Hooks](#hooks)
- [Middlewares](#middlewares)
- [How to handle encapsulation and distribution](#distribution)
- [Let's start!](#start)

## Register
As in JavaScript everything is an object, in Fastify everything is a plugin.
Your routes, your utilities and so on are all plugins. And to add a new plugin, whatever its functionality is, in Fastify you have a nice and unique api to use: [`register`](https://github.com/fastify/fastify/blob/master/docs/Plugins.md).
```js
fastify.register(
  require('./my-plugin'),
  { options },
  callback
)
```
`register` creates for you a new Fastify context, this means that if you do any change to the Fastify instance, that change(s) will not be reflected into the context's ancestors. In other words, encapsulation!

*Why encapsulation is important?*
Well, let's say you are creating a new disruptive startup, what do you do? You create an api server with all your stuff, everything in the same place, a monolith!
Ok, you are growing very fast and you want to change your architecture and try microservices. Usually this implies an huge amount of work, because of cross dependencies and the lack of separation of concerns.
Fastify helps you a lot in this direction, because thanks to the encapsulation model it will completely avoid cross dependencies, and will help you structure your code in cohesive blocks.

*Let's return to how to use correctly `register`.*
As you probably know, the required plugins must expose a single function with the following signature
```js
module.exports = function (fastify, options, next) {}
```
Where `fastify` is (pretty obvious) the encapsulated Fastify instance, `options` is the options object and `next` is the function you **must** call when you plugin is ready.

The Fastify's plugin model is fully reentrant and graph-based, it handles without any kind of problem asynchronous code and it guarantees the load order of the plugins, even the close order! *How?* Glad you asked, checkout [`avvio`](https://github.com/mcollina/avvio)!

Inside a plugin you can do whatever you want, register routes, utilities (we'll see this in a moment) and do nested registers, just remember to call `next` when everything is set up!
```js
module.exports = function (fastify, options, next) {
  fastify.get('/plugin', (req, reply) => {
    reply.send({ hello: 'world' })
  })

  next()
}
```

Well, now you know how to use the `register` api and how it works, but how add new functionalities to fastify and even better, share them with other developers?

## Decorators
Okay, let's say that you wrote an utility that is so good that you decided to make it available along all your code. How would you do it? Probably something like the following:
```js
// your-awesome-utility.js
module.exports = function (a, b) {
  return a + b
}
```
```js
const util = require('./your-awesome-utility')
console.log(util('that is ', ' awesome'))
```
And now you will import your utility in every file you need it. (and don't forget that probably you will need it also in your test).

Fastify offers you a way nicer and elegant way to do this, *decorators*.
Create a decorator is extremely easy, just use the [`decorate`](https://github.com/fastify/fastify/blob/master/docs/Decorators.md) api:
```js
fastify.decorate('util', (a, b) => a + b)
```
Now you can access your utility just by doing `fastify.util` whenever you need it, even inside your test.
And here's starts the magic; do you remember that few lines above we talked about encapsulation? Well, use `register` and `decorate` in conjunction enable exactly that, let me show you an example to clarify this:
```js
fastify.register((instance, opts, next) => {
  instance.decorate('util', (a, b) => a + b)
  console.log(instance.util('that is ', ' awesome'))

  next()
})

fastify.register((instance, opts, next) => {
  console.log(instance.util('that is ', ' awesome')) // this will throw an error

  next()
})
```
Inside the second register call `instance.util` will throw an error, because `util` exist only inside the first register context.
Let's step back for a moment and get deepen on this: when using the `register` api you will create a new context every time and this avoid situations like the one mentioned few line above. But pay attention, the encapsulation works only for the ancestors and the brothers, but not for the sons.
```js
fastify.register((instance, opts, next) => {
  instance.decorate('util', (a, b) => a + b)
  console.log(instance.util('that is ', ' awesome'))

  fastify.register((instance, opts, next) => {
    console.log(instance.util('that is ', ' awesome')) // this will not throw an error
    next()
  }, next)
})

fastify.register((instance, opts, next) => {
  console.log(instance.util('that is ', ' awesome')) // this will throw an error

  next()
})
```
*Take home message: if you need that an utility is available in every part of your application, pay attention that is declared at the root scope of your application. Otherwise you can use `fastify-plugin` utility as described [here](#distribution).*

`decorate` is not the unique api that you can use to extend the server functionalities, you can also use `decorateRequest` and `decorateReply`.

*`decorateRequest` and `decorateReply`? Why do we need them if we already have `decorate`?*
Good question, we added them to make Fastify more developer-friendly. Let's see an example:
```js
fastify.decorate('html', payload => {
  return generateHtml(payload)
})

fastify.get('/html', (req, reply) => {
  reply
    .type('text/html')
    .send(fastify.html({ hello: 'world' }))
})
```
It works, but it can be way better!
```js
fastify.decorateReply('html', function (payload) {
  this.type('text/html') // this is the 'Reply' object
  this.send(generateHtml(payload))
})

fastify.get('/html', (req, reply) => {
  reply.html({ hello: 'world' })
})
```

And in the same way you can do this for the `request` object:
```js
fastify.decorate('getHeader', (req, header) => {
  return req.headers[header]
})

fastify.addHook('preHandler', (req, reply, done) => {
  req.isHappy = fastify.getHeader(req.req, 'happy')
  done()
})

fastify.get('/happiness', (req, reply) => {
  reply.send({ happy: req.isHappy })
})
```
Again, it works, but it can be way better!
```js
fastify.decorateRequest('setHeader', function (header) {
  this.isHappy = this.req.headers[header]
})

fastify.decorateRequest('isHappy', false) // this will be added to the Request object prototype, yay speed!

fastify.addHook('preHandler', (req, reply, done) => {
  req.setHeader('happy')
  done()
})

fastify.get('/happiness', (req, reply) => {
  reply.send({ happy: req.isHappy })
})
```

We've seen how extend server functionalities and how handle the encapsulation system, but what if you need to add a function that must be executed every time that the server "[emits](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md)" an event?

## Hooks
You just built an amazing utility, but now you need to execute that for every request, this is what you will likely do:
```js
fastify.decorate('util', (req, key, value) => { req.key = value })

fastify.get('/plugin1', (req, reply) => {
  fastify.util(req, 'timestamp', new Date())
  reply.send(req)
})

fastify.get('/plugin2', (req, reply) => {
  fastify.util(req, 'timestamp', new Date())
  reply.send(req)
})
```
I think we all agree that this is terrible. Code repeat, awful readability and it cannot scale.

So what can you do to avoid this annoying issue? Yes, you are right, use an [hook](https://github.com/fastify/fastify/blob/master/docs/Hooks.md)!
```js
fastify.decorate('util', (req, key, value) => { req.key = value })

fastify.addHook('preHandler', (req, reply, done) => {
  fastify.util(req, 'timestamp', new Date())
  done()
})

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

fastify.get('/plugin2', (req, reply) => {
  reply.send(req)
})
```
Now for every request you will run your utility, it is obvious that you can register as many hooks as you need.
It can happen that you want that a hook must be executed just for a subset of routes, how can you do that?  Yep, encapsulation!

```js
fastify.register((instance, opts, next) => {
  instance.decorate('util', (req, key, value) => { req.key = value })

  instance.addHook('preHandler', (req, reply, done) => {
    instance.util(req, 'timestamp', new Date())
    done()
  })

  instance.get('/plugin1', (req, reply) => {
    reply.send(req)
  })

  next()
})

fastify.get('/plugin2', (req, reply) => {
  reply.send(req)
})
```
Now your hook will run just for the first route!

As you probably noticed at this time, `request` and `reply` are not the standard Nodejs *request* and *response* objects, but Fastify's objects.
Let's say that you are arriving from a framework like Express or Restify, and you already have some Middleware that do exactly what you need, and you don't want to redo al the work.

## Middlewares
Fastify [supports](https://github.com/fastify/fastify/blob/master/docs/Middlewares.md) out of the box Express/Restify/Connect middlewares, this means that you can just drop-in your old code and it will work! *(faster, by the way)*
How we can do that? Checkout our middlewares engine, [middie](https://github.com/fastify/middie).
```js
const yourMiddleware = require('your-middleware')
fastify.use(yourMiddleware)
```

## How to handle encapsulation and distribution
Perfect, now you know (almost) all the tools that you can use to extend Fastify. But probably there is something you noted when trying out your code.
How can you distribute your code?

The preferred way to distribute an utility is to wrap all your code inside a `register`, in this way your plugin can support an asynchronous bootstrap *(since `decorate` is a synchronous api)*, in the case of a database connection for example.

*Wait, what? Don't you told me that `register` creates and encapsulation and what I create inside there will not be available outside?*
Yes, I told that. But what I didn't told you, is that you can tell to Fastify to avoid this behavior, with the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module.
```js
const fp = require('fastify-plugin')
const dbClient = require('db-client')

function dbPlugin (fastify, opts, next) {
  dbClient.connect(opts.url, (err, conn) => {
    fastify.decorate('db', conn)
    next()
  })
}

module.exports = fp(dbPlugin)
```
You can also tell to `fastify-plugin` to check the installed version of Fastify, in case of you need a specific api.

## Let's start!
Awesome, now you know everything you need to know about Fastify and its plugin system to start build your first plugin, and please if you do, tell us! We will add it to the [*ecosystem*](https://github.com/fastify/fastify#ecosystem) section of our documentation!

If you want to see some real world example, checkout:
- [`point-of-view`](https://github.com/fastify/point-of-view)
Templates rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.
- [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb)
Fastify MongoDB connection plugin, with this you can share the same MongoDb connection pool in every part of your server.
- [`fastify-multipart`](https://github.com/fastify/fastify-multipart)
Multipart support for Fastify
- [`fastify-helmet`](https://github.com/fastify/fastify-helmet) Important security headers for Fastify

*Do you feel it's missing something here? Let us know! :)*


keep keep keep keep replace keep keep keep keep keep

const lines = ['incoming request', 'log success', 'request completed']

'use strict'

const t = require('tap')
const test = t.test
const http = require('http')
const split = require('split2')
const Fastify = require('..')
const pino = require('pino')

test('test log stream', t => {
  t.plan(7)
  var fastify = null
  var stream = split(JSON.parse)
  try {
    fastify = Fastify({
      logger: {
        stream: stream,
        level: 'info'
      }
    })
  } catch (e) {
    t.fail()
  }

  fastify.get('/', function (req, reply) {
    t.ok(req.log)
    reply.send({ hello: 'world' })
  })

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

    http.get('http://localhost:' + fastify.server.address().port)
    stream.on('data', line => {
      t.ok(line.req, 'req is defined')
      t.ok(line.res, 'res is defined')
      t.equal(line.msg, 'request completed', 'message is set')
      t.equal(line.req.method, 'GET', 'method is get')
      t.equal(line.res.statusCode, 200, 'statusCode is 200')
    })
  })
})

test('test error log stream', t => {
  t.plan(7)
  var fastify = null
  var stream = split(JSON.parse)
  try {
    fastify = Fastify({
      logger: {
        stream: stream,
        level: 'info'
      }
    })
  } catch (e) {
    t.fail()
  }

  fastify.get('/error', function (req, reply) {
    t.ok(req.log)
    reply.send(new Error('kaboom'))
  })

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

    http.get('http://localhost:' + fastify.server.address().port + '/error')
    stream.once('data', line => {
      t.ok(line.req, 'req is defined')
      t.ok(line.res, 'res is defined')
      t.equal(line.msg, 'kaboom', 'message is set')
      t.equal(line.req.method, 'GET', 'method is get')
      t.equal(line.res.statusCode, 500, 'statusCode is 500')
    })
  })
})

test('can use external logger instance', t => {
  const lines = ['log success', 'request completed']
  t.plan(lines.length + 2)

  const splitStream = split(JSON.parse)
  splitStream.on('data', (line) => {
    t.is(line.msg, lines.shift())
  })

  const logger = require('pino')(splitStream)

  const localFastify = Fastify({logger: logger})

  localFastify.get('/foo', function (req, reply) {
    t.ok(req.log)
    req.log.info('log success')
    reply.send({ hello: 'world' })
  })

  localFastify.listen(0, err => {
    t.error(err)
    http.get('http://localhost:' + localFastify.server.address().port + '/foo', (res) => {
      res.resume()
      res.on('end', () => {
        localFastify.server.close()
      })
    })
  })
})

test('expose the logger', t => {
  t.plan(2)
  var fastify = null
  var stream = split(JSON.parse)
  try {
    fastify = Fastify({
      logger: {
        stream: stream,
        level: 'info'
      }
    })
  } catch (e) {
    t.fail()
  }

  t.ok(fastify.logger)
  t.is(typeof fastify.logger, 'object')
})

test('The logger should accept a custom genReqId function', t => {
  t.plan(3)

  const fastify = Fastify({
    logger: {
      genReqId: function () {
        return 'a'
      }
    }
  })

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

  fastify.listen(0, err => {
    t.error(err)
    fastify.inject({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, res => {
      const payload = JSON.parse(res.payload)
      t.equal(payload.id, 'a')
      fastify.close()
    })
  })
})

test('reply.send logs an error if called twice in a row', t => {
  const lines = ['Reply already sent', 'Reply already sent', 'request completed']
  t.plan(lines.length + 1)

  const splitStream = split(JSON.parse)
  splitStream.on('data', (line) => {
    t.is(line.msg, lines.shift())
  })

  const logger = pino(splitStream)

  const fastify = Fastify({
    logger
  })

  fastify.get('/', (req, reply) => {
    reply.send({ hello: 'world' })
    reply.send({ hello: 'world2' })
    reply.send({ hello: 'world3' })
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, res => {
    const payload = JSON.parse(res.payload)
    t.deepEqual(payload, { hello: 'world' })
  })
})


'use strict'

const request = require('request')
const Ajv = require('ajv')
const Joi = require('joi')

module.exports.payloadMethod = function (method, t) {
  const test = t.test
  const fastify = require('..')()
  const upMethod = method.toUpperCase()
  const loMethod = method.toLowerCase()

  const opts = {
    schema: {
      body: {
        type: 'object',
        properties: {
          hello: {
            type: 'integer'
          }
        }
      }
    }
  }

  const ajv = new Ajv({ coerceTypes: true, removeAdditional: true })
  const optsWithCustomValidator = {
    schema: {
      body: {
        type: 'object',
        properties: {
          hello: {
            type: 'integer'
          }
        },
        additionalProperties: false
      }
    },
    schemaCompiler: function (schema) {
      return ajv.compile(schema)
    }
  }

  const optsWithJoiValidator = {
    schema: {
      body: Joi.object().keys({
        hello: Joi.string().required()
      }).required()
    },
    schemaCompiler: function (schema) {
      return schema.validate.bind(schema)
    }
  }

  test(`${upMethod} can be created`, t => {
    t.plan(1)
    try {
      fastify[loMethod]('/', opts, function (req, reply) {
        reply.send(req.body)
      })
      fastify[loMethod]('/custom', optsWithCustomValidator, function (req, reply) {
        reply.send(req.body)
      })
      fastify[loMethod]('/joi', optsWithJoiValidator, function (req, reply) {
        reply.send(req.body)
      })

      fastify.register(function (fastify2, opts, next) {
        fastify2.setSchemaCompiler(function schema (schema) {
          return body => ({ error: new Error('From custom schema compiler!') })
        })
        const withInstanceCustomCompiler = {
          schema: {
            body: {
              type: 'object',
              properties: { },
              additionalProperties: false
            }
          }
        }
        fastify2[loMethod]('/plugin', withInstanceCustomCompiler, (req, reply) => reply.send({hello: 'never here!'}))

        const optsWithCustomValidator2 = {
          schema: {
            body: {
              type: 'object',
              properties: { },
              additionalProperties: false
            }
          },
          schemaCompiler: function (schema) {
            return function (body) {
              return { error: new Error('Always fail!') }
            }
          }
        }
        fastify2[loMethod]('/plugin/custom', optsWithCustomValidator2, (req, reply) => reply.send({hello: 'never here!'}))

        next()
      })
      t.pass()
    } catch (e) {
      t.fail()
    }
  })

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

    fastify.server.unref()

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

    test(`${upMethod} - 400 on bad parameters`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port,
        body: {
          hello: 'world'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
        t.deepEqual(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(`${upMethod} - input-validation coerce`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port,
        body: {
          hello: '42'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 42 })
      })
    })

    test(`${upMethod} - input-validation custom schema compiler`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port + '/custom',
        body: {
          hello: '42',
          world: 55
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 42 })
      })
    })

    test(`${upMethod} - input-validation joi schema compiler ok`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port + '/joi',
        body: {
          hello: '42'
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 200)
        t.deepEqual(body, { hello: 42 })
      })
    })

    test(`${upMethod} - input-validation joi schema compiler ko`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port + '/joi',
        body: {
          hello: 44
        },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
        t.deepEqual(body, {
          error: 'Bad Request',
          message: 'child "hello" fails because ["hello" must be a string]',
          statusCode: 400
        })
      })
    })

    test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port + '/plugin',
        body: { },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
        t.deepEqual(body, {
          error: 'Bad Request',
          message: 'From custom schema compiler!',
          statusCode: '400'
        })
      })
    })

    test(`${upMethod} - input-validation custom schema compiler encapsulated`, t => {
      t.plan(3)
      request({
        method: upMethod,
        uri: 'http://localhost:' + fastify.server.address().port + '/plugin/custom',
        body: { },
        json: true
      }, (err, response, body) => {
        t.error(err)
        t.strictEqual(response.statusCode, 400)
        t.deepEqual(body, {
          error: 'Bad Request',
          message: 'Always fail!',
          statusCode: '400'
        })
      })
    })
  })
}


'use strict'

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

test('beforeHandler', t => {
  t.plan(1)
  const fastify = Fastify()

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

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { beforeHandler: true, hello: 'world' })
  })
})

test('beforeHandler should be called after preHandler hook', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.addHook('preHandler', (req, reply, next) => {
    req.body.check = 'a'
    next()
  })

  fastify.post('/', {
    beforeHandler: (req, reply, done) => {
      req.body.check += 'b'
      done()
    }
  }, (req, reply) => {
    reply.send(req.body)
  })

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { check: 'ab', hello: 'world' })
  })
})

test('beforeHandler should be unique per route', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.post('/', {
    beforeHandler: (req, reply, done) => {
      req.body.hello = 'earth'
      done()
    }
  }, (req, reply) => {
    reply.send(req.body)
  })

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

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { hello: 'earth' })
  })

  fastify.inject({
    method: 'POST',
    url: '/no',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { hello: 'world' })
  })
})

test('beforeHandler should handle errors', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.post('/', {
    beforeHandler: (req, reply, done) => {
      done(new Error('kaboom'))
    }
  }, (req, reply) => {
    reply.send(req.body)
  })

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.equal(res.statusCode, 500)
    t.deepEqual(payload, {
      message: 'kaboom',
      error: 'Internal Server Error',
      statusCode: 500
    })
  })
})

test('beforeHandler should handle errors with custom status code', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.post('/', {
    beforeHandler: (req, reply, done) => {
      reply.code(401)
      done(new Error('go away'))
    }
  }, (req, reply) => {
    reply.send(req.body)
  })

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.equal(res.statusCode, 401)
    t.deepEqual(payload, {
      message: 'go away',
      error: 'Unauthorized',
      statusCode: 401
    })
  })
})

test('beforeHandler could accept an array of functions', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.post('/', {
    beforeHandler: [
      (req, reply, done) => {
        req.body.beforeHandler = 'a'
        done()
      },
      (req, reply, done) => {
        req.body.beforeHandler += 'b'
        done()
      }
    ]
  }, (req, reply) => {
    reply.send(req.body)
  })

  fastify.inject({
    method: 'POST',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { beforeHandler: 'ab', hello: 'world' })
  })
})

test('beforeHandler does not interfere with preHandler', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.addHook('preHandler', (req, reply, next) => {
    req.body.check = 'a'
    next()
  })

  fastify.post('/', {
    beforeHandler: (req, reply, done) => {
      req.body.check += 'b'
      done()
    }
  }, (req, reply) => {
    reply.send(req.body)
  })

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

  fastify.inject({
    method: 'post',
    url: '/',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { check: 'ab', hello: 'world' })
  })

  fastify.inject({
    method: 'post',
    url: '/no',
    payload: { hello: 'world' }
  }, res => {
    var payload = JSON.parse(res.payload)
    t.deepEqual(payload, { check: 'a', hello: 'world' })
  })
})


environment:
  matrix:
    - nodejs_version: 4
      platform: x64
      msvs_toolset: 12
    - nodejs_version: 6
      platform: x64
      msvs_toolset: 12
    - nodejs_version: 7
      platform: x64
      msvs_toolset: 12
    - nodejs_version: 8
      platform: x64
      msvs_toolset: 12

# Install scripts. (runs after repo cloning)
install:
  # Get the latest stable version of Node.js or io.js
  - ps: Install-Product node $env:nodejs_version
  # install modules
  - npm install

# Post-install test scripts.
test_script:
  # Output useful info for debugging.
  - node --version
  - npm --version
  # run tests
  - npm test

os: Visual Studio 2015

build: OFF


test('reply.send logs an error if called twice in a row', t => {

const lines = ['Reply already sent', 'Reply already sent', 'request completed']

keep keep keep keep keep replace keep keep keep keep keep

const lines = ['incoming request', 'Reply already sent', 'Reply already sent', 'request completed']


Fastify

## Lifecycle
Following the schema of the internal lifecycle of Fastify.
On the right branch of every section there is the next phase of the lifecycle, on the left branch there is the corresponding error code that will be generated if the parent throws an error *(note that all the errors are automatically handled by Fastify)*.
```
Incoming Request
  │
  └─▶ Instance Logger
        │
        └─▶ Routing
             │
       404 ◀─┴─▶ onRequest Hook
                  │
        4**/5** ◀─┴─▶ run Middlewares
                        │
              4**/5** ◀─┴─▶ Parsing
                             │
                       415 ◀─┴─▶ Validation
                                   │
                             400 ◀─┴─▶ preHandler Hook
                                         │
                               4**/5** ◀─┴─▶ beforeHandler
                                               │
                                     4**/5** ◀─┴─▶ User Handler
                                                     │
                                                     └─▶ Reply
                                                          │ │
                                                          │ └─▶ Outgoing Response
                                                          │
                                                          └─▶ onResponse Hook
```


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).
Be aware that if you use [`fastify-plugin`](https://github.com/fastify/fastify-plugin) this option won't work.

#### 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
```


// if the `sourceset` `src` was an empty string

// wait for a `loadstart` to update the cache to `currentSrc`.

// If a sourceset happens before a `loadstart`, we reset the state

// as this function will be called again.

/* eslint-env qunit */
import window from 'global/window';
import EventTarget from '../../../src/js/event-target.js';
import TrackBaseline from './track-baseline';
import TechFaker from '../tech/tech-faker';
import TextTrack from '../../../src/js/tracks/text-track.js';
import TestHelpers from '../test-helpers.js';
import sinon from 'sinon';
import log from '../../../src/js/utils/log.js';

QUnit.module('Text Track', {
  beforeEach() {
    this.tech = new TechFaker();
  },
  afterEach() {
    this.tech.dispose();
    this.tech = null;
  }
});

// do baseline track testing
TrackBaseline(TextTrack, {
  id: '1',
  kind: 'subtitles',
  mode: 'disabled',
  label: 'English',
  language: 'en'
  // tech is added in baseline
  // tech: new TechFaker()
});

QUnit.test('requires a tech', function(assert) {
  const error = new Error('A tech was not provided.');

  assert.throws(() => new TextTrack({}), error, 'a tech is required');
  assert.throws(() => new TextTrack({tech: null}), error, 'a tech is required');
});

QUnit.test('can create a TextTrack with a mode property', function(assert) {
  const mode = 'disabled';
  const tt = new TextTrack({
    mode,
    tech: this.tech
  });

  assert.equal(tt.mode, mode, 'we have a mode');
});

QUnit.test('defaults when items not provided', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });

  assert.equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
  assert.equal(tt.mode, 'disabled', 'mode defaulted to disabled');
  assert.equal(tt.label, '', 'label defaults to empty string');
  assert.equal(tt.language, '', 'language defaults to empty string');
});

QUnit.test('kind can only be one of several options, defaults to subtitles', function(assert) {
  let tt = new TextTrack({
    tech: this.tech,
    kind: 'foo'
  });

  assert.equal(tt.kind, 'subtitles', 'the kind is set to subtitles, not foo');
  assert.notEqual(tt.kind, 'foo', 'the kind is set to subtitles, not foo');

  tt = new TextTrack({
    tech: this.tech,
    kind: 'subtitles'
  });

  assert.equal(tt.kind, 'subtitles', 'the kind is set to subtitles');

  tt = new TextTrack({
    tech: this.tech,
    kind: 'captions'
  });

  assert.equal(tt.kind, 'captions', 'the kind is set to captions');

  tt = new TextTrack({
    tech: this.tech,
    kind: 'descriptions'
  });

  assert.equal(tt.kind, 'descriptions', 'the kind is set to descriptions');

  tt = new TextTrack({
    tech: this.tech,
    kind: 'chapters'
  });

  assert.equal(tt.kind, 'chapters', 'the kind is set to chapters');

  tt = new TextTrack({
    tech: this.tech,
    kind: 'metadata'
  });

  assert.equal(tt.kind, 'metadata', 'the kind is set to metadata');
});

QUnit.test('mode can only be one of several options, defaults to disabled', function(assert) {
  let tt = new TextTrack({
    tech: this.tech,
    mode: 'foo'
  });

  assert.equal(tt.mode, 'disabled', 'the mode is set to disabled, not foo');
  assert.notEqual(tt.mode, 'foo', 'the mode is set to disabld, not foo');

  tt = new TextTrack({
    tech: this.tech,
    mode: 'disabled'
  });

  assert.equal(tt.mode, 'disabled', 'the mode is set to disabled');

  tt = new TextTrack({
    tech: this.tech,
    mode: 'hidden'
  });

  assert.equal(tt.mode, 'hidden', 'the mode is set to hidden');

  tt = new TextTrack({
    tech: this.tech,
    mode: 'showing'
  });

  assert.equal(tt.mode, 'showing', 'the mode is set to showing');
});

QUnit.test('cue and activeCues are read only', function(assert) {
  const mode = 'disabled';
  const tt = new TextTrack({
    mode,
    tech: this.tech
  });

  tt.cues = 'foo';
  tt.activeCues = 'bar';

  assert.notEqual(tt.cues, 'foo', 'cues is still original value');
  assert.notEqual(tt.activeCues, 'bar', 'activeCues is still original value');
});

QUnit.test('mode can only be set to a few options', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });

  tt.mode = 'foo';

  assert.notEqual(tt.mode, 'foo', 'the mode is still the old value, disabled');
  assert.equal(tt.mode, 'disabled', 'still on the default mode, disabled');

  tt.mode = 'hidden';
  assert.equal(tt.mode, 'hidden', 'mode set to hidden');

  tt.mode = 'bar';
  assert.notEqual(tt.mode, 'bar', 'the mode is still the old value, hidden');
  assert.equal(tt.mode, 'hidden', 'still on the previous mode, hidden');

  tt.mode = 'showing';
  assert.equal(tt.mode, 'showing', 'mode set to showing');

  tt.mode = 'baz';
  assert.notEqual(tt.mode, 'baz', 'the mode is still the old value, showing');
  assert.equal(tt.mode, 'showing', 'still on the previous mode, showing');
});

QUnit.test('cues and activeCues return a TextTrackCueList', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });

  assert.ok(tt.cues.getCueById, 'cues are a TextTrackCueList');
  assert.ok(tt.activeCues.getCueById, 'activeCues are a TextTrackCueList');
});

QUnit.test('cues can be added and removed from a TextTrack', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });
  const cues = tt.cues;

  assert.equal(cues.length, 0, 'start with zero cues');

  tt.addCue({id: '1'});

  assert.equal(cues.length, 1, 'we have one cue');

  tt.removeCue(cues.getCueById('1'));

  assert.equal(cues.length, 0, 'we have removed our one cue');

  tt.addCue({id: '1'});
  tt.addCue({id: '2'});
  tt.addCue({id: '3'});

  assert.equal(cues.length, 3, 'we now have 3 cues');
});

QUnit.test('original cue can be used to remove cue from cues list', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });
  const Cue = window.VTTCue ||
              window.vttjs && window.vttjs.VTTCue ||
              window.TextTrackCue;

  const cue1 = new Cue(0, 1, 'some-cue');

  assert.equal(tt.cues.length, 0, 'start with zero cues');
  tt.addCue(cue1);
  assert.equal(tt.cues.length, 1, 'we have one cue');

  tt.removeCue(cue1);
  assert.equal(tt.cues.length, 0, 'we have removed cue1');
});

QUnit.test('can only remove one cue at a time', function(assert) {
  const tt = new TextTrack({
    tech: this.tech
  });
  const Cue = window.VTTCue ||
              window.vttjs && window.vttjs.VTTCue ||
              window.TextTrackCue;

  const cue1 = new Cue(0, 1, 'some-cue');

  assert.equal(tt.cues.length, 0, 'start with zero cues');
  tt.addCue(cue1);
  tt.addCue(cue1);
  assert.equal(tt.cues.length, 2, 'we have two cues');

  tt.removeCue(cue1);
  assert.equal(tt.cues.length, 1, 'we have removed one instance of cue1');

  tt.removeCue(cue1);
  assert.equal(tt.cues.length, 0, 'we have removed the other instance of cue1');
});

QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
  const done = assert.async();
  const player = TestHelpers.makePlayer({techfaker: {autoReady: false}});
  let changes = 0;
  const tt = new TextTrack({
    tech: player.tech_,
    mode: 'showing'
  });
  const cuechangeHandler = function() {
    changes++;
  };

  tt.addCue({
    id: '1',
    startTime: 0,
    endTime: 5
  });

  tt.oncuechange = cuechangeHandler;
  tt.addEventListener('cuechange', cuechangeHandler);

  player.tech_.currentTime = function() {
    return 0;
  };

  player.tech_.trigger('timeupdate');
  assert.equal(changes, 0, 'a cuechange event is not triggered');

  player.tech_.on('ready', function() {
    player.tech_.currentTime = function() {
      return 0.2;
    };

    player.tech_.trigger('timeupdate');

    assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');

    tt.off();
    player.dispose();
    done();
  });
  player.tech_.triggerReady();
});

QUnit.test('fires cuechange when cues become active and inactive', function(assert) {
  const player = TestHelpers.makePlayer();
  let changes = 0;
  const tt = new TextTrack({
    tech: player.tech_,
    mode: 'showing'
  });
  const cuechangeHandler = function() {
    changes++;
  };

  tt.addCue({
    id: '1',
    startTime: 1,
    endTime: 5
  });

  tt.oncuechange = cuechangeHandler;
  tt.addEventListener('cuechange', cuechangeHandler);

  player.tech_.currentTime = function() {
    return 2;
  };

  player.tech_.trigger('timeupdate');

  assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');

  player.tech_.currentTime = function() {
    return 7;
  };

  player.tech_.trigger('timeupdate');

  assert.equal(changes, 4, 'a cuechange event trigger addEventListener and oncuechange');

  tt.off();
  player.dispose();
});

QUnit.test('enabled and disabled cuechange handler when changing mode to hidden', function(assert) {
  const player = TestHelpers.makePlayer();
  let changes = 0;
  const tt = new TextTrack({
    tech: player.tech_
  });
  const cuechangeHandler = function() {
    changes++;
  };

  tt.mode = 'hidden';

  tt.addCue({
    id: '1',
    startTime: 1,
    endTime: 5
  });

  tt.addEventListener('cuechange', cuechangeHandler);

  player.tech_.currentTime = function() {
    return 2;
  };
  player.tech_.trigger('timeupdate');

  assert.equal(changes, 1, 'a cuechange event trigger');

  changes = 0;
  tt.mode = 'disabled';

  player.tech_.currentTime = function() {
    return 7;
  };
  player.tech_.trigger('timeupdate');

  assert.equal(changes, 0, 'NO cuechange event trigger');

  tt.off();
  player.dispose();
});

QUnit.test('enabled and disabled cuechange handler when changing mode to showing', function(assert) {
  const player = TestHelpers.makePlayer();
  let changes = 0;
  const tt = new TextTrack({
    tech: player.tech_
  });
  const cuechangeHandler = function() {
    changes++;
  };

  tt.mode = 'showing';

  tt.addCue({
    id: '1',
    startTime: 1,
    endTime: 5
  });

  tt.addEventListener('cuechange', cuechangeHandler);

  player.tech_.currentTime = function() {
    return 2;
  };
  player.tech_.trigger('timeupdate');

  assert.equal(changes, 1, 'a cuechange event trigger');

  changes = 0;
  tt.mode = 'disabled';

  player.tech_.currentTime = function() {
    return 7;
  };
  player.tech_.trigger('timeupdate');

  assert.equal(changes, 0, 'NO cuechange event trigger');

  tt.off();
  player.dispose();
});

QUnit.test('tracks are parsed if vttjs is loaded', function(assert) {
  const clock = sinon.useFakeTimers();
  const oldVTT = window.WebVTT;
  let parserCreated = false;
  const reqs = [];

  window.xhr.onCreate = function(req) {
    reqs.push(req);
  };

  window.WebVTT = () => {};
  window.WebVTT.StringDecoder = () => {};
  window.WebVTT.Parser = () => {
    parserCreated = true;
    return {
      oncue() {},
      onparsingerror() {},
      onflush() {},
      parse() {},
      flush() {}
    };
  };

  const tt = new TextTrack({
    tech: this.tech,
    src: 'http://example.com'
  });

  reqs.pop().respond(200, null, 'WEBVTT\n');

  assert.ok(parserCreated, 'WebVTT is loaded, so we can just parse');

  clock.restore();
  tt.off();
  window.WebVTT = oldVTT;
});

QUnit.test('tracks are parsed once vttjs is loaded', function(assert) {
  const clock = sinon.useFakeTimers();
  const oldVTT = window.WebVTT;
  let parserCreated = false;
  const reqs = [];

  window.xhr.onCreate = function(req) {
    reqs.push(req);
  };

  window.WebVTT = true;

  const testTech = new EventTarget();

  testTech.textTracks = () => {};
  testTech.currentTime = () => {};

  const tt = new TextTrack({
    tech: testTech,
    src: 'http://example.com'
  });

  reqs.pop().respond(200, null, 'WEBVTT\n');

  assert.ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet');

  clock.tick(100);
  assert.ok(!parserCreated, 'WebVTT still not loaded, do not try to parse yet');

  window.WebVTT = () => {};
  window.WebVTT.StringDecoder = () => {};
  window.WebVTT.Parser = () => {
    parserCreated = true;
    return {
      oncue() {},
      onparsingerror() {},
      onflush() {},
      parse() {},
      flush() {}
    };
  };

  testTech.trigger('vttjsloaded');
  assert.ok(parserCreated, 'WebVTT is loaded, so we can parse now');

  clock.restore();
  tt.off();
  testTech.off();
  window.WebVTT = oldVTT;
});

QUnit.test('stops processing if vttjs loading errored out', function(assert) {
  const clock = sinon.useFakeTimers();
  const errorSpy = sinon.spy();
  const oldVTT = window.WebVTT;
  const oldLogError = log.error;
  const parserCreated = false;
  const reqs = [];

  window.xhr.onCreate = function(req) {
    reqs.push(req);
  };

  log.error = errorSpy;

  window.WebVTT = true;

  const testTech = new EventTarget();

  testTech.textTracks = () => {};
  testTech.currentTime = () => {};

  sinon.stub(testTech, 'off');
  testTech.off.withArgs('vttjsloaded');

  const tt = new TextTrack({
    tech: testTech,
    src: 'http://example.com'
  });

  reqs.pop().respond(200, null, 'WEBVTT\n');

  assert.ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet');

  testTech.trigger('vttjserror');
  const offSpyCall = testTech.off.getCall(0);

  assert.ok(errorSpy.called, 'vttjs failed to load, so log.error was called');
  if (errorSpy.called) {
    assert.ok(
      /^vttjs failed to load, stopping trying to process/.test(errorSpy.getCall(0).args[0]),
      'log.error was called with the expected message'
    );
  }
  assert.ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet');
  assert.ok(offSpyCall, 'tech.off was called');

  clock.restore();
  window.WebVTT = oldVTT;
  tt.off();
  testTech.off.restore();
  testTech.off();
  log.error = oldLogError;
});


<!DOCTYPE html>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link rel="stylesheet" type="text/css" href="../css/guides.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700,700italic,600,600italic' rel='stylesheet' type='text/css'><link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.8.0/styles/solarized_light.min.css" />

Text Tracks

Text Tracks are a function of HTML5 video for providing time triggered text to the viewer. Video.js makes tracks work across all browsers. There are currently five types of tracks:

Subtitles: Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.

Captions: Transcription of the dialogue, sound effects, musical cues, and other audio information for when the viewer is deaf/hard of hearing, or the video is muted. Captions are also shown over the video.

Chapters: Chapter titles that are used to create navigation within the video. Typically they're in the form of a list of chapters that the viewer can click on to go to a specific chapter.

Descriptions: Text descriptions of what's happening in the video for when the video portion isn't available, because the viewer is blind, not using a screen, or driving and about to crash because they're trying to enjoy a video while driving. Descriptions are read by a screen reader or turned into a separate audio track.

Metadata: Tracks that have data meant for javascript to parse and do something with. These aren't shown to the user.

Creating the Text File

Timed text requires a text file in WebVTT format. This format defines a list of "cues" that have a start time, and end time, and text to display. Microsoft has a builder that can help you get started on the file.

When creating captions, there's also additional [caption formatting techniques] (http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML#style) that would be good to use, like brackets around sound effects: [ sound effect ]. If you'd like a more in depth style guide for captioning, you can reference the Captioning Key, but keep in mind not all features are supported by WebVTT or (more likely) the Video.js WebVTT implementation.

Adding to Video.js

Once you have your WebVTT file created, you can add it to Video.js using the track tag. Put your track tag after all the source elements, and before any fallback content.

<video id="example_video_1" class="video-js"
  controls preload="auto" width="640" height="264"
  data-setup='{"example_option":true}'>
 <source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
 <source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
 <source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />

 <track kind="captions" src="http://example.com/path/to/captions.vtt" srclang="en" label="English" default>

</video>

You can also add tracks programatically.

Subtitles from Another Domain

Because we're pulling in the text track file via Javascript, the same-origin policy applies. If you'd like to have a player served from one domain,
but the text track served from another, you'll need to enable CORS in order to do so.
In addition to enabling CORS on the server serving the text tracks, you will need to add the crossorigin attribute to the video element itself. This attribute has two values anonymous and use-credentials. Most users will want to use anonymous with cross-origin tracks.
It can be added to the video element like so:

<video class="video-js" crossorigin="anonymous">
  <source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
  <track src="http://example.com/oceans.vtt" kind="captions" srclang="en" label="English">
</video>

One thing to be aware of is that in this case the video files themselves will also needs CORS headers applied to it. This is because some browsers apply the crossorigin attribute to the video source itself and not just the tracks and is considered a security concern by the spec.

Track Attributes

Additional settings for track tags.

kind

One of the five track types listed above. Kind defaults to subtitles if no kind is included.

label

The label for the track that will be show to the user, for example in a menu that list the different languages available for subtitles.

default

The default attribute can be used to have a track default to showing. Otherwise the viewer would need to select their language from the captions or subtitles menu.
NOTE: For chapters, default is required if you want the chapters menu to show.

srclang

The two-letter code (valid BCP 47 language tag) for the language of the text track, for example "en" for English. A list of language codes is available here.

Interacting with Text Tracks

Showing tracks programmatically

Some of you would want to turn captions on and off programmatically rather than just forcing the user to do so themselves. This can be easily achieved by modifying the mode of the text tracks.
The mode can be one of three values disabled, hidden, and showing.
When a text track's mode is disabled, the track does not show on screen as the video is playing.
When the mode is set to showing, the track is visible to the viewer and updates while the video is playing.
You can change of a particular track like so:

let tracks = player.textTracks();

for (let i = 0; i < tracks.length; i++) {
  let track = tracks[i];

  // find the captions track that's in english
  if (track.kind === 'captions' && track.language === 'en') {
    track.mode = 'showing';
  }
}

Doing something when a cue becomes active

Above, we mentioned that mode can also be hidden, what this means is that the track will update
as the video is playing but it won't be visible to the viewer. This is most useful for metadata text tracks.
One usecase for metadata text tracks is to have something happen when their cues become active, to do so, you listen to the cuechange event on the track. These events fire when the mode is showing as well.
Here's an example:

let tracks = player.textTracks();
let metadataTrack;

for (let i = 0; i < tracks.length; i++) {
  let track = tracks[i];

  // find the metadata track that's labeled ads
  if (track.kind === 'captions' && track.label === 'ads') {
    track.mode = 'hidden';
    // store it for usage outside of the loop
    metadataTrack = track;
  }
}

metadataTrack.addEventListener('cuechange', function() {
  player.ads.startLinearAdMode();
});

Emulated Text Tracks

By default, video.js will try and use native text tracks if possible and fall back to emulated text tracks if the native functionality is broken or incomplete or non-existent.
The Flash tech will always use the emulated text track functionality.
The video.js API and TextTrack objects were modeled after the w3c's specification.
video.js uses Mozilla's vtt.js library to parse and display its emulated text tracks.

If you wanted to disable native text track functionality and force video.js to use emulated text tracks always, you can supply the nativeTextTracks option to the tech like so:

let player = videojs('myvideo', {
  html5: {
    nativeTextTracks: false
  }
});

Text Track Settings

When using emulated Text Tracks, captions will have an additional item in the menu called "caption settings".
This allows the viewer of the player to change some styles of how the captions are displayed on screen.

If you don't want that, you can disable it by turning off the text track settings component and hiding the menu item like so:

let player = videojs('myvideo', {
  // make the text track settings dialog not initialize
  textTrackSettings: false
});

/* hide the captions settings item from the captions menu */
.vjs-texttrack-settings {
  display: none;
}

Text Track Precedence

In general, the Descriptions tracks is of lower precedence than captions and subtitles.
What this means for you?

If you are using the default attribute, videojs will choose the first track that is marked as default and turn it on. If There are multiple tracks marked default, it will try and turn on the first captions or subtitles track before any descriptions tracks.

This only applied to the emulated captions support, native text tracks behavior will change depending on the browser

If you select a given track from the menu, videojs will turn off all the other tracks of the same kind. This may seem like you can have both subtitles and captions turned on at the same time but unfortuantely, at this time we only support one track being displayed at a time.

This means that for emulated text tracks, we'll choose the first captions or subtitles track that is enabled to display.

When native text tracks are supported, we will still disable the other tracks of the same kind but it is possible that multiple text tracks are shown.

If a descriptions track is selected and subsequently a subtitles or captions track is selected, the descriptions track is disabled and its menu button is also disabled.

When enabling a track programmatically, there's not much checking that videojs does.

For emulated text tracks, when it's time to display the captions, video.js would choose the first track that's showing, again choosing subtitles or captions over descriptions, if necessary.

For native text tracks, this behavior depends on the browser. Some browsers will let you have multiple text tracks but others will disable all other tracks when a new one is selected.

API

player.textTracks() -> TextTrackList

This is the main interface into the text tracks of the player.
It return a TextTrackList which lists all the tracks on the player.

player.remoteTextTracks() -> TextTrackList

This is a helper method to get a list of all the tracks that were created from track elements or that were added to the player by the addRemoteTextTrack method. All these tracks are removeable from the player, where-as not all tracks from player.textTracks() are necessarily removeable.

player.remoteTextTrackEls() -> HTMLTrackElementList

Another helper method, this is a list of all the track elements associated with the player. Both emulated or otherwise.

player.addTextTrack(String kind, [String label [, String language]]) -> TextTrack

This is based on the w3c spec API and when given a kind and an optional label and language, will create a new text track for you to use.
This method is intended for purely programmatic usage of tracks and has one important limitation:
tracks created using this method cannot be removed. The native addTextTrack does not have a corresponding removeTextTrack, so, we actually discourage the usage of this method.

player.addRemoteTextTrack(Object options) -> HTMLTrackElement

This function takes an options object that looks pretty similar to the track element and returns a HTMLTrackElement.
This object has a track property on it which is the actual TextTrack object.
This TextTrack object is equivalent to the one that can be returned from player.addTextTrack with the added bonus that it can be removed from the player.
Internally, video.js will either add a <track> element for you, or emulate that depending on whether native text tracks are supported or not.
The options available are:

kind

label

language (also srclang)

id

src

player.removeRemoteTextTrack(HTMLTrackElement|TextTrack)

This function takes either an HTMLTrackElement or a TextTrack object and removes it from the player.


<!DOCTYPE html>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link rel="stylesheet" type="text/css" href="../css/guides.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700,700italic,600,600italic' rel='stylesheet' type='text/css'><link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.8.0/styles/solarized_light.min.css" />

Removing Players

Sometimes, you want to remove players after page load (in single page apps or modals, for instance). It's easy to manage, but there are some simple rules you need to follow.

Call .dispose()

To remove the html associated with your videojs player from the page always call the player's dispose() method:

var oldPlayer = document.getElementById('my-player');
videojs(oldPlayer).dispose();

This method will:

reset the internal state of videojs

remove the player's dom from the page

Showing / Hiding a Player

For instance, if you have a modal that a player appears in, you should create the player when the modal pops up. When the modal hides, dispose the player. If you try to hide the Flash tech, things will go poorly. Even with other tech, calling dispose() on a player that's not needed will free up resources for the browser.

Why Is This Needed?

VideoJS internally tracks all players and their associated data by html id attribute. If you plan to create new players with the same id as previously created players, you'll need to call the player's dispose() method to clear VideoJS's internal state before creating the new player.

Signs You Did It Wrong

TypeError: this.el_.vjs_getProperty is not a function
"VIDEOJS:" "Video.js: buffered unavailable on Hls playback technology element." TypeError: this.el_.vjs_getProperty is not a function
Stack trace:
...

If you encounter a console error in the browser similar to the above, you've probably forgotten to dispose() a player before removing it from the dom. This would happen when using the contrib-hls plugin.


/* eslint-env qunit */
import HTMLTrackElement from '../../../src/js/tracks/html-track-element.js';
import TechFaker from '../tech/tech-faker';

QUnit.module('HTML Track Element', {
  beforeEach() {
    this.tech = new TechFaker();
  },
  afterEach() {
    this.tech.dispose();
    this.tech = null;
  }
});

QUnit.test('html track element requires a tech', function(assert) {
  assert.throws(
    function() {
      return new HTMLTrackElement();
    },
    new Error('A tech was not provided.'),
    'a tech is required for html track element'
  );
});

QUnit.test('can create a html track element with various properties', function(assert) {
  const kind = 'chapters';
  const label = 'English';
  const language = 'en';

  const htmlTrackElement = new HTMLTrackElement({
    kind,
    label,
    language,
    tech: this.tech
  });

  assert.equal(typeof htmlTrackElement.default, 'undefined', 'we have a default');
  assert.equal(htmlTrackElement.kind, kind, 'we have a kind');
  assert.equal(htmlTrackElement.label, label, 'we have a label');
  assert.equal(htmlTrackElement.readyState, 0, 'we have a readyState');
  assert.equal(htmlTrackElement.srclang, language, 'we have a srclang');

  htmlTrackElement.track.off();
});

QUnit.test('defaults when items not provided', function(assert) {
  const htmlTrackElement = new HTMLTrackElement({
    tech: this.tech
  });

  assert.equal(typeof htmlTrackElement.default, 'undefined', 'we have a default');
  assert.equal(htmlTrackElement.kind, 'subtitles', 'we have a kind');
  assert.equal(htmlTrackElement.label, '', 'we have a label');
  assert.equal(htmlTrackElement.readyState, 0, 'we have a readyState');
  assert.equal(typeof htmlTrackElement.src, 'undefined', 'we have a src');
  assert.equal(htmlTrackElement.srclang, '', 'we have a srclang');
  assert.equal(htmlTrackElement.track.cues.length, 0, 'we have a track');

  htmlTrackElement.track.off();
});

QUnit.test('fires loadeddata when track cues become populated', function(assert) {
  let changes = 0;
  const loadHandler = function() {
    changes++;
  };
  const htmlTrackElement = new HTMLTrackElement({
    tech() {}
  });

  htmlTrackElement.addEventListener('load', loadHandler);

  // trigger loaded cues event
  htmlTrackElement.track.trigger('loadeddata');

  assert.equal(changes, 1, 'a loadeddata event trigger addEventListener');
  assert.equal(htmlTrackElement.readyState, 2, 'readyState is loaded');

  htmlTrackElement.track.off();
  htmlTrackElement.off('load');
});