'use strict'

/* eslint no-prototype-builtins: 0 */

const t = require('tap')
const test = t.test
const Fastify = require('..')
const sget = require('simple-get').concat
const fp = require('fastify-plugin')
const fakeTimer = require('@sinonjs/fake-timers')

test('require a plugin', t => {
  t.plan(1)
  const fastify = Fastify()
  fastify.register(require('./plugin.helper'))
  fastify.ready(() => {
    t.ok(fastify.test)
  })
})

test('plugin metadata - ignore prefix', t => {
  t.plan(2)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  fastify.register(plugin, { prefix: 'foo' })

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

  function plugin (instance, opts, done) {
    instance.get('/', function (request, reply) {
      reply.send('hello')
    })
    done()
  }
})

test('fastify.register with fastify-plugin should not encapsulate his code', t => {
  t.plan(10)
  const fastify = Fastify()

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      n()
    }))

    t.notOk(instance.test)

    // the decoration is added at the end
    instance.after(() => {
      t.ok(instance.test)
    })

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      reply.send({ hello: 'world' })
    })

    done()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

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

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

test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', t => {
  t.plan(22)
  const fastify = Fastify()

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      i.decorate('global', () => {})
      t.ok(i.global)
      n()
    }))

    instance.register((i, o, n) => n(), p => {
      t.notOk(p === instance || p === fastify)
      t.ok(instance.isPrototypeOf(p))
      t.ok(fastify.isPrototypeOf(p))
      t.ok(p.global)
    })

    instance.register((i, o, n) => {
      i.decorate('local', () => {})
      n()
    })

    instance.register((i, o, n) => n(), p => t.notOk(p.local))

    instance.register((i, o, n) => {
      t.ok(i.local)
      n()
    }, p => p.decorate('local', () => {}))

    instance.register((i, o, n) => n(), p => t.notOk(p.local))

    instance.register(fp((i, o, n) => {
      t.ok(i.global_2)
      n()
    }), p => p.decorate('global_2', () => 'hello'))

    instance.register((i, o, n) => {
      i.decorate('global_2', () => 'world')
      n()
    }, p => p.get('/', (req, reply) => {
      t.ok(p.global_2)
      reply.send({ hello: p.global_2() })
    }))

    t.notOk(instance.global)
    t.notOk(instance.global_2)
    t.notOk(instance.local)

    // the decoration is added at the end
    instance.after(() => {
      t.ok(instance.global)
      t.equal(instance.global_2(), 'hello')
      t.notOk(instance.local)
    })

    done()
  })

  fastify.ready(() => {
    t.notOk(fastify.global)
  })

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

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

test('fastify.register with fastify-plugin registers root level plugins', t => {
  t.plan(15)
  const fastify = Fastify()

  function rootPlugin (instance, opts, done) {
    instance.decorate('test', 'first')
    t.ok(instance.test)
    done()
  }

  function innerPlugin (instance, opts, done) {
    instance.decorate('test2', 'second')
    done()
  }

  fastify.register(fp(rootPlugin))

  fastify.register((instance, opts, done) => {
    t.ok(instance.test)
    instance.register(fp(innerPlugin))

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

    done()
  })

  fastify.ready(() => {
    t.ok(fastify.test)
    t.notOk(fastify.test2)
  })

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

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

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

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

test('check dependencies - should not throw', t => {
  t.plan(12)
  const fastify = Fastify()

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      n()
    }))

    instance.register(fp((i, o, n) => {
      try {
        i.decorate('otherTest', () => {}, ['test'])
        t.ok(i.test)
        t.ok(i.otherTest)
        n()
      } catch (e) {
        t.fail()
      }
    }))

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      t.ok(instance.otherTest)
      reply.send({ hello: 'world' })
    })

    done()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
    t.notOk(fastify.otherTest)
  })

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

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

test('check dependencies - should throw', t => {
  t.plan(12)
  const fastify = Fastify()

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      try {
        i.decorate('otherTest', () => {}, ['test'])
        t.fail()
      } catch (e) {
        t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY')
        t.equal(e.message, 'The decorator is missing dependency \'test\'.')
      }
      n()
    }))

    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      t.notOk(i.otherTest)
      n()
    }))

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      t.notOk(instance.otherTest)
      reply.send({ hello: 'world' })
    })

    done()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

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

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

test('set the plugin name based on the plugin displayName symbol', t => {
  t.plan(5)
  const fastify = Fastify()

  fastify.register(fp((fastify, opts, done) => {
    t.equal(fastify.pluginName, 'plugin-A')
    fastify.register(fp((fastify, opts, done) => {
      t.equal(fastify.pluginName, 'plugin-A -> plugin-AB')
      done()
    }, { name: 'plugin-AB' }))
    fastify.register(fp((fastify, opts, done) => {
      t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
      done()
    }, { name: 'plugin-AC' }))
    done()
  }, { name: 'plugin-A' }))

  fastify.register(fp((fastify, opts, done) => {
    t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC -> plugin-B')
    done()
  }, { name: 'plugin-B' }))

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

test('plugin name will change when using no encapsulation', t => {
  t.plan(5)
  const fastify = Fastify()

  fastify.register(fp((fastify, opts, done) => {
    // store it in a different variable will hold the correct name
    const pluginName = fastify.pluginName
    fastify.register(fp((fastify, opts, done) => {
      t.equal(fastify.pluginName, 'plugin-A -> plugin-AB')
      done()
    }, { name: 'plugin-AB' }))
    fastify.register(fp((fastify, opts, done) => {
      t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
      done()
    }, { name: 'plugin-AC' }))
    setImmediate(() => {
      // normally we would expect the name plugin-A
      // but we operate on the same instance in each plugin
      t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
      t.equal(pluginName, 'plugin-A')
    })
    done()
  }, { name: 'plugin-A' }))

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

test('plugin name is undefined when accessing in no plugin context', t => {
  t.plan(2)
  const fastify = Fastify()

  t.equal(fastify.pluginName, undefined)

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

test('set the plugin name based on the plugin function name', t => {
  t.plan(5)
  const fastify = Fastify()

  fastify.register(function myPluginA (fastify, opts, done) {
    t.equal(fastify.pluginName, 'myPluginA')
    fastify.register(function myPluginAB (fastify, opts, done) {
      t.equal(fastify.pluginName, 'myPluginAB')
      done()
    })
    setImmediate(() => {
      // exact name due to encapsulation
      t.equal(fastify.pluginName, 'myPluginA')
    })
    done()
  })

  fastify.register(function myPluginB (fastify, opts, done) {
    t.equal(fastify.pluginName, 'myPluginB')
    done()
  })

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

test('approximate a plugin name when no meta data is available', t => {
  t.plan(7)
  const fastify = Fastify()

  fastify.register((fastify, opts, done) => {
    // A
    t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true)
    t.equal(fastify.pluginName.includes('// A'), true)
    fastify.register((fastify, opts, done) => {
      // B
      t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true)
      t.equal(fastify.pluginName.includes('// B'), true)
      done()
    })
    setImmediate(() => {
      t.equal(fastify.pluginName.startsWith('(fastify, opts, done)'), true)
      t.equal(fastify.pluginName.includes('// A'), true)
    })
    done()
  })

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

test('approximate a plugin name also when fastify-plugin has no meta data', t => {
  t.plan(4)
  const fastify = Fastify()

  fastify.register(fp((fastify, opts, done) => {
    t.match(fastify.pluginName, /plugin\.test/)
    fastify.register(fp(function B (fastify, opts, done) {
      // function has name
      t.match(fastify.pluginName, /plugin\.test-auto-\d+ -> B/)
      done()
    }))
    setImmediate(() => {
      t.match(fastify.pluginName, /plugin\.test-auto-\d+ -> B/)
    })
    done()
  }))

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

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

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', 'first')
      n()
    }))

    instance.get('/first', (req, reply) => {
      reply.send({ plugin: instance.test })
    })

    done()
  })

  fastify.register((instance, opts, done) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', 'second')
      n()
    }))

    instance.get('/second', (req, reply) => {
      reply.send({ plugin: instance.test })
    })

    done()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

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

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

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

test('if a plugin raises an error and there is not a callback to handle it, the server must not start', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.register((instance, opts, done) => {
    done(new Error('err'))
  })

  fastify.listen(0, err => {
    t.ok(err instanceof Error)
    t.equal(err.message, 'err')
  })
})

test('add hooks after route declaration', t => {
  t.plan(3)
  const fastify = Fastify()

  function plugin (instance, opts, done) {
    instance.decorateRequest('check', null)
    instance.addHook('onRequest', (req, reply, done) => {
      req.check = {}
      done()
    })
    setImmediate(done)
  }
  fastify.register(fp(plugin))

  fastify.register((instance, options, done) => {
    instance.addHook('preHandler', function b (req, res, done) {
      req.check.hook2 = true
      done()
    })

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

    instance.addHook('preHandler', function c (req, res, done) {
      req.check.hook3 = true
      done()
    })

    done()
  })

  fastify.addHook('preHandler', function a (req, res, done) {
    req.check.hook1 = true
    done()
  })

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

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.same(JSON.parse(body), { hook1: true, hook2: true, hook3: true })
      fastify.close()
    })
  })
})

test('nested plugins', t => {
  t.plan(5)

  const fastify = Fastify()

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

  fastify.register(function (fastify, opts, done) {
    fastify.register((fastify, opts, done) => {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 1')
      })
      done()
    }, { prefix: '/child1' })

    fastify.register((fastify, opts, done) => {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 2')
      })
      done()
    }, { prefix: '/child2' })

    done()
  }, { prefix: '/parent' })

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

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child1'
    }, (err, response, body) => {
      t.error(err)
      t.same(body.toString(), 'I am child 1')
    })

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child2'
    }, (err, response, body) => {
      t.error(err)
      t.same(body.toString(), 'I am child 2')
    })
  })
})

test('nested plugins awaited', t => {
  t.plan(5)

  const fastify = Fastify()

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

  fastify.register(async function wrap (fastify, opts) {
    await fastify.register(async function child1 (fastify, opts) {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 1')
      })
    }, { prefix: '/child1' })

    await fastify.register(async function child2 (fastify, opts) {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 2')
      })
    }, { prefix: '/child2' })
  }, { prefix: '/parent' })

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

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child1'
    }, (err, response, body) => {
      t.error(err)
      t.same(body.toString(), 'I am child 1')
    })

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child2'
    }, (err, response, body) => {
      t.error(err)
      t.same(body.toString(), 'I am child 2')
    })
  })
})

test('plugin metadata - decorators', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.decorate('plugin1', true)
  fastify.decorateReply('plugin1', true)
  fastify.decorateRequest('plugin1', true)

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

  fastify.register(plugin)

  fastify.ready(() => {
    t.ok(fastify.plugin)
  })

  function plugin (instance, opts, done) {
    instance.decorate('plugin', true)
    done()
  }
})

test('plugin metadata - decorators - should throw', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.decorate('plugin1', true)
  fastify.decorateReply('plugin1', true)

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

  fastify.register(plugin)
  fastify.ready((err) => {
    t.equal(err.message, "The decorator 'plugin1' is not present in Request")
  })

  function plugin (instance, opts, done) {
    instance.decorate('plugin', true)
    done()
  }
})

test('plugin metadata - decorators - should throw with plugin name', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.decorate('plugin1', true)
  fastify.decorateReply('plugin1', true)

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'the-plugin',
    decorators: {
      fastify: ['plugin1'],
      reply: ['plugin1'],
      request: ['plugin1']
    }
  }

  fastify.register(plugin)
  fastify.ready((err) => {
    t.equal(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request")
  })

  function plugin (instance, opts, done) {
    instance.decorate('plugin', true)
    done()
  }
})

test('plugin metadata - dependencies', t => {
  t.plan(1)
  const fastify = Fastify()

  dependency[Symbol.for('skip-override')] = true
  dependency[Symbol.for('plugin-meta')] = {
    name: 'plugin'
  }

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

  fastify.register(dependency)
  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function dependency (instance, opts, done) {
    done()
  }

  function plugin (instance, opts, done) {
    done()
  }
})

test('plugin metadata - dependencies (nested)', t => {
  t.plan(1)
  const fastify = Fastify()

  dependency[Symbol.for('skip-override')] = true
  dependency[Symbol.for('plugin-meta')] = {
    name: 'plugin'
  }

  nested[Symbol.for('skip-override')] = true
  nested[Symbol.for('plugin-meta')] = {
    dependencies: ['plugin']
  }

  fastify.register(dependency)
  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function dependency (instance, opts, done) {
    done()
  }

  function plugin (instance, opts, done) {
    instance.register(nested)
    done()
  }

  function nested (instance, opts, done) {
    done()
  }
})

test('pluginTimeout', t => {
  t.plan(2)
  const fastify = Fastify({
    pluginTimeout: 10
  })
  fastify.register(function (app, opts, done) {
    // to no call done on purpose
  })
  fastify.ready((err) => {
    t.ok(err)
    t.equal(err.code, 'ERR_AVVIO_PLUGIN_TIMEOUT')
  })
})

test('pluginTimeout default', t => {
  t.plan(2)
  const clock = fakeTimer.install()

  const fastify = Fastify()
  fastify.register(function (app, opts, done) {
    // default time elapsed without calling done
    clock.tick(10000)
  })

  fastify.ready((err) => {
    t.ok(err)
    t.equal(err.code, 'ERR_AVVIO_PLUGIN_TIMEOUT')
  })

  t.teardown(clock.uninstall)
})

test('plugin metadata - version', t => {
  t.plan(1)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'plugin',
    fastify: '2.0.0'
  }

  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function plugin (instance, opts, done) {
    done()
  }
})

test('plugin metadata - version range', t => {
  t.plan(1)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'plugin',
    fastify: '>=2.0.0'
  }

  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function plugin (instance, opts, done) {
    done()
  }
})

test('plugin metadata - version not matching requirement', t => {
  t.plan(2)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'plugin',
    fastify: '99.0.0'
  }

  fastify.register(plugin)

  fastify.ready((err) => {
    t.ok(err)
    t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH')
  })

  function plugin (instance, opts, done) {
    done()
  }
})

test('plugin metadata - version not matching requirement 2', t => {
  t.plan(2)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'plugin',
    fastify: '<=3.0.0'
  }

  fastify.register(plugin)

  fastify.ready((err) => {
    t.ok(err)
    t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH')
  })

  function plugin (instance, opts, done) {
    done()
  }
})

test('plugin metadata - version not matching requirement 3', t => {
  t.plan(2)
  const fastify = Fastify()

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    name: 'plugin',
    fastify: '>=99.0.0'
  }

  fastify.register(plugin)

  fastify.ready((err) => {
    t.ok(err)
    t.equal(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH')
  })

  function plugin (instance, opts, done) {
    done()
  }
})


request: FastifyRequest<RouteGeneric, RawServer, RawRequest>;

code(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;

status(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;

'use strict'

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


'use strict'

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

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

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

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

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

test('body - delete', t => {
  t.plan(1)
  try {
    fastify.delete('/body', bodySchema, function (req, reply) {
      reply.send(req.body)
    })
    t.pass()
  } catch (e) {
    t.fail()
  }
})

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

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

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

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

  test('shorthand - request delete headers schema', t => {
    t.plan(4)
    sget({
      method: 'DELETE',
      headers: {
        'x-test': 1
      },
      url: 'http://localhost:' + fastify.server.address().port + '/headers'
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
      t.equal(response.headers['content-length'], '' + body.length)
      t.equal(JSON.parse(body)['x-test'], 1)
    })
  })

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

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

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

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

  test('shorthand - delete with body', t => {
    t.plan(3)
    sget({
      method: 'DELETE',
      url: 'http://localhost:' + fastify.server.address().port + '/body',
      body: {
        hello: 'world'
      },
      json: true
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
      t.same(body, { hello: 'world' })
    })
  })
})

// https://github.com/fastify/fastify/issues/936
test('shorthand - delete with application/json Content-Type header and without body', t => {
  t.plan(4)
  const fastify = require('..')()
  fastify.delete('/', {}, (req, reply) => {
    t.equal(req.body, null)
    reply.send(req.body)
  })
  fastify.inject({
    method: 'DELETE',
    url: '/',
    headers: { 'Content-Type': 'application/json' },
    body: null
  }, (err, response) => {
    t.error(err)
    t.equal(response.statusCode, 200)
    t.same(JSON.parse(response.payload), null)
  })
})


import { FastifyInstance } from './instance'

import { FastifyLoggerInstance } from './logger'
import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils'
import { RouteGenericInterface } from './route'

export interface RequestGenericInterface {
  Body?: RequestBodyDefault;
  Querystring?: RequestQuerystringDefault;
  Params?: RequestParamsDefault;
  Headers?: RequestHeadersDefault;
}

/**
 * FastifyRequest is an instance of the standard http or http2 request objects.
 * It defaults to http.IncomingMessage, and it also extends the relative request object.
 */
export interface FastifyRequest<
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
  RawServer extends RawServerBase = RawServerDefault,
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
> {
  id: any;
  params: RouteGeneric['Params'];
  raw: RawRequest;
  query: RouteGeneric['Querystring'];
  log: FastifyLoggerInstance;
  body: RouteGeneric['Body'];

  /** in order for this to be used the user should ensure they have set the attachValidation option. */
  validationError?: Error & { validation: any; validationContext: string };

  /**
   * @deprecated Use `raw` property
   */
  readonly req: RawRequest;
  readonly headers: RawRequest['headers'] & RouteGeneric['Headers']; // this enables the developer to extend the existing http(s|2) headers list
  readonly ip: string;
  readonly ips?: string[];
  readonly hostname: string;
  readonly url: string;
  readonly protocol: 'http' | 'https';
  readonly method: string;
  readonly routerPath: string;
  readonly routerMethod: string;
  readonly is404: boolean;
  readonly socket: RawRequest['socket'];

  // Prefer `socket` over deprecated `connection` property in node 13.0.0 or higher
  // @deprecated
  readonly connection: RawRequest['socket'];
}


'use strict'

const fastify = require('../../fastify')({
  logger: false
})

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

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

fastify.listen(3000, (err, address) => {
  if (err) throw err
})


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('../..')
const h2url = require('h2url')
const msg = { hello: 'world' }

let fastify
try {
  fastify = Fastify({
    http2: true
  })
  t.pass('http2 successfully loaded')
} catch (e) {
  t.fail('http2 loading failed', e)
}

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

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

  test('http HEAD request', async (t) => {
    t.plan(1)

    const url = `http://localhost:${fastify.server.address().port}`
    const res = await h2url.concat({ url, method: 'HEAD' })

    t.equal(res.headers[':status'], 200)
  })
})


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('..')
const { Client } = require('undici')

test('Should return 503 while closing - pipelining', t => {
  const fastify = Fastify({
    return503OnClosing: true
  })

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

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

    const instance = new Client('http://localhost:' + fastify.server.address().port, {
      pipelining: 1
    })

    const codes = [200, 503]
    for (const code of codes) {
      instance.request(
        { path: '/', method: 'GET' }
      ).then(data => {
        t.equal(data.statusCode, code)
      }).catch((e) => {
        t.fail(e)
      })
    }
    instance.close(() => {
      t.end('Done')
    })
  })
})

test('Should not return 503 while closing - pipelining - return503OnClosing', t => {
  const fastify = Fastify({
    return503OnClosing: false
  })

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

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

    const instance = new Client('http://localhost:' + fastify.server.address().port, {
      pipelining: 1
    })

    const codes = [200, 200]
    for (const code of codes) {
      instance.request(
        { path: '/', method: 'GET' }
      ).then(data => {
        t.equal(data.statusCode, code)
      }).catch((e) => {
        t.fail(e)
      })
    }
    instance.close(() => {
      t.end('Done')
    })
  })
})


/** in order for this to be used the user should ensure they have set the attachValidation option. */

validationError?: Error & { validation: any; validationContext: string };

keep keep keep keep add keep keep keep keep keep

import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils'
import { FastifyContext } from './context'
import { FastifyLoggerInstance } from './logger'
import { FastifyRequest } from './request'
import { RouteGenericInterface } from './route'

export interface ReplyGenericInterface {
  Reply?: ReplyDefault;
}

/**
 * FastifyReply is an instance of the standard http or http2 reply types.
 * It defaults to http.ServerResponse, and it also extends the relative reply object.
 */
export interface FastifyReply<
  RawServer extends RawServerBase = RawServerDefault,
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
  ContextConfig = ContextConfigDefault,
> {
  raw: RawReply;
  context: FastifyContext<ContextConfig>;
  log: FastifyLoggerInstance;
  request: FastifyRequest<RouteGeneric, RawServer, RawRequest>;
  code(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  status(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  statusCode: number;
  sent: boolean;
  send(payload?: RouteGeneric['Reply']): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  header(key: string, value: any): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  headers(values: {[key: string]: any}): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  getHeader(key: string): string | undefined;
  getHeaders(): {
    // Node's `getHeaders()` can return numbers and arrays, so they're included here as possible types.
    [key: string]: number | string | string[] | undefined;
  };
  removeHeader(key: string): void;
  hasHeader(key: string): boolean;
  // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param
  redirect(statusCode: number, url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  redirect(url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  hijack(): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  callNotFound(): void;
  getResponseTime(): number;
  type(contentType: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  serializer(fn: (payload: any) => string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
  serialize(payload: any): string;
  then(fulfilled: () => void, rejected: (err: Error) => void): void;
}


Fastify

## Hooks

Hooks are registered with the `fastify.addHook` method and allow you to listen to specific events in the application or request/response lifecycle. You have to register a hook before the event is triggered, otherwise, the event is lost.

By using hooks you can interact directly with the lifecycle of Fastify. There are Request/Reply hooks and application hooks:

- [Request/Reply Hooks](#requestreply-hooks)
  - [onRequest](#onrequest)
  - [preParsing](#preparsing)
  - [preValidation](#prevalidation)
  - [preHandler](#prehandler)
  - [preSerialization](#preserialization)
  - [onError](#onerror)
  - [onSend](#onsend)
  - [onResponse](#onresponse)
  - [onTimeout](#ontimeout)
  - [Manage Errors from a hook](#manage-errors-from-a-hook)
  - [Respond to a request from a hook](#respond-to-a-request-from-a-hook)
- [Application Hooks](#application-hooks)
  - [onReady](#onready)
  - [onClose](#onclose)
  - [onRoute](#onroute)
  - [onRegister](#onregister)
- [Scope](#scope)
- [Route level hooks](#route-level-hooks)

**Notice:** the `done` callback is not available when using `async`/`await` or returning a `Promise`. If you do invoke a `done` callback in this situation unexpected behavior may occur, e.g. duplicate invocation of handlers.

## Request/Reply Hooks

[Request](Request.md) and [Reply](Reply.md) are the core Fastify objects.

`done` is the function to continue with the [lifecycle](Lifecycle.md).

It is easy to understand where each hook is executed by looking at the [lifecycle page](Lifecycle.md).

Hooks are affected by Fastify's encapsulation, and can thus be applied to selected routes. See the [Scopes](#scope) section for more information.

There are eight different hooks that you can use in Request/Reply *(in order of execution)*:

### onRequest
```js
fastify.addHook('onRequest', (request, reply, done) => {
  // Some code
  done()
})
```
Or `async/await`:
```js
fastify.addHook('onRequest', async (request, reply) => {
  // Some code
  await asyncMethod()
})
```

**Notice:** in the [onRequest](#onrequest) hook, `request.body` will always be `null`, because the body parsing happens before the [preValidation](#prevalidation) hook.

### preParsing

If you are using the `preParsing` hook, you can transform the request payload stream before it is parsed. It receives the request and reply objects as other hooks, and a stream with the current request payload.

If it returns a value (via `return` or via the callback function), it must return a stream.

For instance, you can uncompress the request body:

```js
fastify.addHook('preParsing', (request, reply, payload, done) => {
  // Some code
  done(null, newPayload)
})
```
Or `async/await`:
```js
fastify.addHook('preParsing', async (request, reply, payload) => {
  // Some code
  await asyncMethod()
  return newPayload
})
```

**Notice:** in the [preParsing](#preparsing) hook, `request.body` will always be `null`, because the body parsing happens before the [preValidation](#prevalidation) hook.

**Notice:** you should also add a `receivedEncodedLength` property to the returned stream. This property is used to correctly match the request payload with the `Content-Length` header value. Ideally, this property should be updated on each received chunk.

**Notice**: The old syntaxes `function(request, reply, done)` and `async function(request, reply)` for the parser are still supported but they are deprecated.

### preValidation

If you are using the `preValidation` hook, you can change the payload before it is validated. For example:

```js
fastify.addHook('preValidation', (request, reply, done) => {
  req.body = { ...req.body, importantKey: 'randomString' }
  done()
})
```
Or `async/await`:
```js
fastify.addHook('preValidation', async (request, reply) => {
  const importantKey = await generateRandomString()
  req.body = { ...req.body, importantKey }
})
```

### preHandler
```js
fastify.addHook('preHandler', (request, reply, done) => {
  // some code
  done()
})
```
Or `async/await`:
```js
fastify.addHook('preHandler', async (request, reply) => {
  // Some code
  await asyncMethod()
})
```
### preSerialization

If you are using the `preSerialization` hook, you can change (or replace) the payload before it is serialized. For example:

```js
fastify.addHook('preSerialization', (request, reply, payload, done) => {
  const err = null
  const newPayload = { wrapped: payload }
  done(err, newPayload)
})
```
Or `async/await`:
```js
fastify.addHook('preSerialization', async (request, reply, payload) => {
  return { wrapped: payload }
})
```

Note: the hook is NOT called if the payload is a `string`, a `Buffer`, a `stream`, or `null`.

### onError
```js
fastify.addHook('onError', (request, reply, error, done) => {
  // Some code
  done()
})
```
Or `async/await`:
```js
fastify.addHook('onError', async (request, reply, error) => {
  // Useful for custom error logging
  // You should not use this hook to update the error
})
```
This hook is useful if you need to do some custom error logging or add some specific header in case of error.

It is not intended for changing the error, and calling `reply.send` will throw an exception.

This hook will be executed only after the `customErrorHandler` has been executed, and only if the `customErrorHandler` sends an error back to the user *(Note that the default `customErrorHandler` always sends the error back to the user)*.

**Notice:** unlike the other hooks, pass an error to the `done` function is not supported.

### onSend
If you are using the `onSend` hook, you can change the payload. For example:

```js
fastify.addHook('onSend', (request, reply, payload, done) => {
  const err = null;
  const newPayload = payload.replace('some-text', 'some-new-text')
  done(err, newPayload)
})
```
Or `async/await`:
```js
fastify.addHook('onSend', async (request, reply, payload) => {
  const newPayload = payload.replace('some-text', 'some-new-text')
  return newPayload
})
```

You can also clear the payload to send a response with an empty body by replacing the payload with `null`:

```js
fastify.addHook('onSend', (request, reply, payload, done) => {
  reply.code(304)
  const newPayload = null
  done(null, newPayload)
})
```

> You can also send an empty body by replacing the payload with the empty string `''`, but be aware that this will cause the `Content-Length` header to be set to `0`, whereas the `Content-Length` header will not be set if the payload is `null`.

Note: If you change the payload, you may only change it to a `string`, a `Buffer`, a `stream`, or `null`.

### onResponse
```js

fastify.addHook('onResponse', (request, reply, done) => {
  // Some code
  done()
})
```
Or `async/await`:
```js
fastify.addHook('onResponse', async (request, reply) => {
  // Some code
  await asyncMethod()
})
```

The `onResponse` hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics.

### onTimeout

```js

fastify.addHook('onTimeout', (request, reply, done) => {
  // Some code
  done()
})
```
Or `async/await`:
```js
fastify.addHook('onTimeout', async (request, reply) => {
  // Some code
  await asyncMethod()
})
```
`onTimeout` is useful if you need to monitor the request timed out in your service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hanged up. Therefore ,you will not be able to send data to the client.

### Manage Errors from a hook
If you get an error during the execution of your hook, just pass it to `done()` and Fastify will automatically close the request and send the appropriate error code to the user.

```js
fastify.addHook('onRequest', (request, reply, done) => {
  done(new Error('Some error'))
})
```

If you want to pass a custom error code to the user, just use `reply.code()`:
```js
fastify.addHook('preHandler', (request, reply, done) => {
  reply.code(400)
  done(new Error('Some error'))
})
```
*The error will be handled by [`Reply`](Reply.md#errors).*

Or if you're using `async/await` you can just throw an error:
```js
fastify.addHook('onResponse', async (request, reply) => {
  throw new Error('Some error')
})
```

### Respond to a request from a hook

If needed, you can respond to a request before you reach the route handler,
for example when implementing an authentication hook.
Replying from a hook implies that the hook chain is __stopped__ and
the rest of the hooks and handlers are not executed. If the hook is
using the callback approach, i.e. it is not an `async` function or it
returns a `Promise`, it is as simple as calling `reply.send()` and avoiding
calling the callback. If the hook is `async`, `reply.send()` __must__ be
called _before_ the function returns or the promise resolves, otherwise, the
request will proceed. When `reply.send()` is called outside of the
promise chain, it is important to `return reply` otherwise the request
will be executed twice.

It is important to __not mix callbacks and `async`/`Promise`__, otherwise
the hook chain will be executed twice.

If you are using `onRequest` or `preHandler` use `reply.send`.

```js
fastify.addHook('onRequest', (request, reply, done) => {
  reply.send('Early response')
})

// Works with async functions too
fastify.addHook('preHandler', async (request, reply) => {
  await something()
  reply.send({ hello: 'world' })
  return reply // optional in this case, but it is a good practice
})
```

If you want to respond with a stream, you should avoid using an `async` function for the hook. If you must use an `async` function, your code will need to follow the pattern in [test/hooks-async.js](https://github.com/fastify/fastify/blob/94ea67ef2d8dce8a955d510cd9081aabd036fa85/test/hooks-async.js#L269-L275).

```js
fastify.addHook('onRequest', (request, reply, done) => {
  const stream = fs.createReadStream('some-file', 'utf8')
  reply.send(stream)
})
```

If you are sending a response without `await` on it, make sure to always
`return reply`:

```js
fastify.addHook('preHandler', async (request, reply) => {
  setImmediate(() => { reply.send('hello') })

  // This is needed to signal the handler to wait for a response
  // to be sent outside of the promise chain
  return reply
})

fastify.addHook('preHandler', async (request, reply) => {
  // the fastify-static plugin will send a file asynchronously,
  // so we should return reply
  reply.sendFile('myfile')
  return reply
})
```

## Application Hooks

You can hook into the application-lifecycle as well.

- [onReady](#onready)
- [onClose](#onclose)
- [onRoute](#onroute)
- [onRegister](#onregister)

### onReady
Triggered before the server starts listening for requests and when `.ready()` is invoked. It cannot change the routes or add new hooks.
Registered hook functions are executed serially.
Only after all `onReady` hook functions have completed will the server start listening for requests.
Hook functions accept one argument: a callback, `done`, to be invoked after the hook function is complete.
Hook functions are invoked with `this` bound to the associated Fastify instance.

```js
// callback style
fastify.addHook('onReady', function (done) {
  // Some code
  const err = null;
  done(err)
})

// or async/await style
fastify.addHook('onReady', async function () {
  // Some async code
  await loadCacheFromDatabase()
})
```

### onClose
Triggered when `fastify.close()` is invoked to stop the server. It is useful when [plugins](Plugins.md) need a "shutdown" event, for example, to close an open connection to a database.

The first argument is the Fastify instance, the second one the `done` callback.
```js
fastify.addHook('onClose', (instance, done) => {
  // Some code
  done()
})
```

### onRoute
Triggered when a new route is registered. Listeners are passed a `routeOptions` object as the sole parameter. The interface is synchronous, and, as such, the listeners are not passed a callback. This hook is encapsulated.
```js
fastify.addHook('onRoute', (routeOptions) => {
  //Some code
  routeOptions.method
  routeOptions.schema
  routeOptions.url // the complete URL of the route, it will include the prefix if any
  routeOptions.path // `url` alias
  routeOptions.routePath // the URL of the route without the prefix
  routeOptions.bodyLimit
  routeOptions.logLevel
  routeOptions.logSerializers
  routeOptions.prefix
})
```

If you are authoring a plugin and you need to customize application routes, like modifying the options or adding new route hooks, this is the right place.

```js
fastify.addHook('onRoute', (routeOptions) => {
  function onPreSerialization(request, reply, payload, done) {
    // Your code
    done(null, payload)
  }
  // preSerialization can be an array or undefined
  routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
})
```

### onRegister
Triggered when a new plugin is registered and a new encapsulation context is created. The hook will be executed **before** the registered code.

This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context, thus this hook is encapsulated.

**Note:** This hook will not be called if a plugin is wrapped inside [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
```js
fastify.decorate('data', [])

fastify.register(async (instance, opts) => {
  instance.data.push('hello')
  console.log(instance.data) // ['hello']

  instance.register(async (instance, opts) => {
    instance.data.push('world')
    console.log(instance.data) // ['hello', 'world']
  }, { prefix: '/hola' })
}, { prefix: '/ciao' })

fastify.register(async (instance, opts) => {
  console.log(instance.data) // []
}, { prefix: '/hello' })

fastify.addHook('onRegister', (instance, opts) => {
  // Create a new array from the old one
  // but without keeping the reference
  // allowing the user to have encapsulated
  // instances of the `data` property
  instance.data = instance.data.slice()

  // the options of the new registered instance
  console.log(opts.prefix)
})
```

## Scope
Except for [onClose](#onclose), all hooks are encapsulated. This means that you can decide where your hooks should run by using `register` as explained in the [plugins guide](Plugins-Guide.md). If you pass a function, that function is bound to the right Fastify context and from there you have full access to the Fastify API.

```js
fastify.addHook('onRequest', function (request, reply, done) {
  const self = this // Fastify context
  done()
})
```

Note that the Fastify context in each hook is the same as the plugin where the route was registered, for example:

```js
fastify.addHook('onRequest', async function (req, reply) {
  if (req.raw.url === '/nested') {
    assert.strictEqual(this.foo, 'bar')
  } else {
    assert.strictEqual(this.foo, undefined)
  }
})

fastify.get('/', async function (req, reply) {
  assert.strictEqual(this.foo, undefined)
  return { hello: 'world' }
})

fastify.register(async function plugin (fastify, opts) {
  fastify.decorate('foo', 'bar')

  fastify.get('/nested', async function (req, reply) {
    assert.strictEqual(this.foo, 'bar')
    return { hello: 'world' }
  })
})
```

Warn: if you declare the function with an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), the `this` will not be Fastify, but the one of the current scope.

## Route level hooks
You can declare one or more custom lifecycle hooks ([onRequest](#onrequest), [onResponse](#onresponse), [preParsing](#preparsing), [preValidation](#prevalidation), [preHandler](#prehandler), [preSerialization](#preserialization), [onSend](#onsend), [onTimeout](#ontimeout), and [onError](#onerror)) hook(s) that will be **unique** for the route.
If you do so, those hooks are always executed as the last hook in their category.

This can be useful if you need to implement authentication, where the [preParsing](#preparsing) or [preValidation](#prevalidation) hooks are exactly what you need.
Multiple route-level hooks can also be specified as an array.

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

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

fastify.addHook('preParsing', (request, reply, done) => {
  // Your code
  done()
})

fastify.addHook('preValidation', (request, reply, done) => {
  // Your code
  done()
})

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

fastify.addHook('preSerialization', (request, reply, payload, done) => {
  // Your code
  done(null, payload)
})

fastify.addHook('onSend', (request, reply, payload, done) => {
  // Your code
  done(null, payload)
})

fastify.addHook('onTimeout', (request, reply, done) => {
  // Your code
  done()
})

fastify.addHook('onError', (request, reply, error, done) => {
  // Your code
  done()
})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { ... },
  onRequest: function (request, reply, done) {
    // This hook will always be executed after the shared `onRequest` hooks
    done()
  },
  onResponse: function (request, reply, done) {
    // this hook will always be executed after the shared `onResponse` hooks
    done()
  },
  preParsing: function (request, reply, done) {
    // This hook will always be executed after the shared `preParsing` hooks
    done()
  },
  preValidation: function (request, reply, done) {
    // This hook will always be executed after the shared `preValidation` hooks
    done()
  },
  preHandler: function (request, reply, done) {
    // This hook will always be executed after the shared `preHandler` hooks
    done()
  },
  // // Example with an array. All hooks support this syntax.
  //
  // preHandler: [function (request, reply, done) {
  //   // This hook will always be executed after the shared `preHandler` hooks
  //   done()
  // }],
  preSerialization: (request, reply, payload, done) => {
    // This hook will always be executed after the shared `preSerialization` hooks
    done(null, payload)
  },
  onSend: (request, reply, payload, done) => {
    // This hook will always be executed after the shared `onSend` hooks
    done(null, payload)
  },
  onTimeout: (request, reply, done) => {
    // This hook will always be executed after the shared `onTimeout` hooks
    done()
  },
  onError: (request, reply, error, done) => {
    // This hook will always be executed after the shared `onError` hooks
    done()
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})
```

**Note**: both options also accept an array of functions.


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
  │
  └─▶ Routing
        │
        └─▶ Instance Logger
             │
   4**/5** ◀─┴─▶ onRequest Hook
                  │
        4**/5** ◀─┴─▶ preParsing Hook
                        │
              4**/5** ◀─┴─▶ Parsing
                             │
                   4**/5** ◀─┴─▶ preValidation Hook
                                  │
                            415 ◀─┴─▶ Validation
                                        │
                              4**/5** ◀─┴─▶ preHandler Hook
                                              │
                                    4**/5** ◀─┴─▶ User Handler
                                                    │
                                                    └─▶ Reply
                                                          │
                                                4**/5** ◀─┴─▶ preSerialization Hook
                                                                │
                                                                └─▶ onSend Hook
                                                                      │
                                                            4**/5** ◀─┴─▶ Outgoing Response
                                                                            │
                                                                            └─▶ onResponse Hook
```

At any point before or during the `User Handler`, `reply.hijack()` can be called to prevent Fastify from:
- Running all the following hooks and user handler
- Sending the response automatically

NB (*): If `reply.raw` is used to send a response back to the user, `onResponse` hooks will still be executed

## Reply Lifecycle

Whenever the user handles the request, the result may be:

- in async handler: it returns a payload
- in async handler: it throws an `Error`
- in sync handler: it sends a payload
- in sync handler: it sends an `Error` instance

If the reply was hijacked, we skip all the below steps. Otherwise, when it is being submitted, the data flow performed is the following:

```
                        ★ schema validation Error
                                    │
                                    └─▶ schemaErrorFormatter
                                               │
                          reply sent ◀── JSON ─┴─ Error instance
                                                      │
                                                      │         ★ throw an Error
                     ★ send or return                 │                 │
                            │                         │                 │
                            │                         ▼                 │
       reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler ◀─────┘
                                                      │
                                 reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook
                                                                                │
                                                                                └─▶ reply sent
```

Note: `reply sent` means that the JSON payload will be serialized by:

- the [reply serialized](Server.md#setreplyserializer) if set
- or by the [serializer compiler](Server.md#setserializercompiler) when a JSON schema has been set for the returning HTTP status code
- or by the default `JSON.stringify` function


'use strict'

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

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

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

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

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

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

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

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

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

  test('shorthand - request head', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })

  test('shorthand - request head params schema', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port + '/params/world/123'
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })

  test('shorthand - request head params schema error', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port + '/params/world/string'
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 400)
    })
  })

  test('shorthand - request head querystring schema', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port + '/query?hello=123'
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })

  test('shorthand - request head querystring schema error', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port + '/query?hello=world'
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 400)
    })
  })

  test('shorthand - request head missing schema', t => {
    t.plan(2)
    sget({
      method: 'HEAD',
      url: 'http://localhost:' + fastify.server.address().port + '/missing'
    }, (err, response) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })
})


// Add the platform (mac or win) to the body tag so we can have platform-specific CSS rules

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, brackets: true, $: false, PathUtils: false */

/**
 * brackets is the root of the Brackets codebase. This file pulls in all other modules as
 * dependencies (or dependencies thereof), initializes the UI, and binds global menus & keyboard
 * shortcuts to their Commands.
 *
 * TODO: (issue #264) break out the definition of brackets into a separate module from the application controller logic
 *
 * Unlike other modules, this one can be accessed without an explicit require() because it exposes
 * a global object, window.brackets.
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent non-module scripts
    require("widgets/bootstrap-dropdown");
    require("widgets/bootstrap-modal");
    require("thirdparty/path-utils/path-utils.min");
    require("thirdparty/smart-auto-complete/jquery.smart_autocomplete");

    // Load dependent modules
    var ProjectManager          = require("ProjectManager"),
        DocumentManager         = require("DocumentManager"),
        EditorManager           = require("EditorManager"),
        InlineEditorProviders   = require("InlineEditorProviders"),
        WorkingSetView          = require("WorkingSetView"),
        FileCommandHandlers     = require("FileCommandHandlers"),
        FileViewController      = require("FileViewController"),
        FileSyncManager         = require("FileSyncManager"),
        KeyBindingManager       = require("KeyBindingManager"),
        KeyMap                  = require("KeyMap"),
        Commands                = require("Commands"),
        CommandManager          = require("CommandManager"),
        CodeHintManager         = require("CodeHintManager"),
        PerfUtils               = require("PerfUtils"),
        FileIndexManager        = require("FileIndexManager"),
        QuickFileOpen           = require("QuickFileOpen"),
        Menus                   = require("Menus");

    //Load modules the self-register and just need to get included in the main project
    require("JSLint");
    require("CodeHintManager");
    require("DebugCommandHandlers");
    require("FindInFiles");

    // Define core brackets namespace if it isn't already defined
    //
    // We can't simply do 'brackets = {}' to define it in the global namespace because
    // we're in "use strict" mode. Most likely, 'window' will always point to the global
    // object when this code is running. However, in case it isn't (e.g. if we're running
    // inside Node for CI testing) we use this trick to get the global object.
    //
    // Taken from:
    //   http://stackoverflow.com/questions/3277182/how-to-get-the-global-object-in-javascript
    var Fn = Function, global = (new Fn('return this'))();
    if (!global.brackets) {
        global.brackets = {};
    }

    // TODO: (issue #265) Make sure the "test" object is not included in final builds
    // All modules that need to be tested from the context of the application
    // must to be added to this object. The unit tests cannot just pull
    // in the modules since they would run in context of the unit test window,
    // and would not have access to the app html/css.
    brackets.test = {
        PreferencesManager      : require("PreferencesManager"),
        ProjectManager          : ProjectManager,
        FileCommandHandlers     : FileCommandHandlers,
        FileViewController      : FileViewController,
        DocumentManager         : DocumentManager,
        EditorManager           : EditorManager,
        Commands                : Commands,
        WorkingSetView          : WorkingSetView,
        CommandManager          : require("CommandManager"),
        FileIndexManager        : FileIndexManager,
        CSSUtils                : require("CSSUtils")
    };

    // Uncomment the following line to force all low level file i/o routines to complete
    // asynchronously. This should only be done for testing/debugging.
    // NOTE: Make sure this line is commented out again before committing!
    //brackets.forceAsyncCallbacks = true;

    // Load native shell when brackets is run in a native shell rather than the browser
    // TODO: (issue #266) load conditionally
    brackets.shellAPI = require("ShellAPI");

    brackets.inBrowser = !brackets.hasOwnProperty("fs");

    brackets.platform = (global.navigator.platform === "MacIntel" || global.navigator.platform === "MacPPC") ? "mac" : "win";

    // Main Brackets initialization
    $(document).ready(function () {

        function initListeners() {
            // Prevent unhandled drag and drop of files into the browser from replacing
            // the entire Brackets app. This doesn't prevent children from choosing to
            // handle drops.
            $(document.body)
                .on("dragover", function (event) {
                    if (event.originalEvent.dataTransfer.files) {
                        event.stopPropagation();
                        event.preventDefault();
                        event.originalEvent.dataTransfer.dropEffect = "none";
                    }
                })
                .on("drop", function (event) {
                    if (event.originalEvent.dataTransfer.files) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                });
        }

        function initProject() {
            ProjectManager.loadProject();

            // Open project button
            $("#btn-open-project").click(function () {
                ProjectManager.openProject();
            });

            // Handle toggling top level disclosure arrows of file list area
            $("#open-files-header").click(function () {
                $("#open-files-disclosure-arrow").toggleClass("disclosure-arrow-closed");
                $("#open-files-container").toggle();
            });
            $("#project-files-header").click(function () {
                $("#project-files-disclosure-arrow").toggleClass("disclosure-arrow-closed");
                $("#project-files-container").toggle();
            });
        }

        function initCommandHandlers() {
            FileCommandHandlers.init($("#main-toolbar .title"));
        }

        function initKeyBindings() {
            // Register keymaps and install the keyboard handler
            // TODO: (issue #268) show keyboard equivalents in the menus
            var _globalKeymap = KeyMap.create({
                "bindings": [
                    {"Ctrl-O": Commands.FILE_OPEN},
                    {"Ctrl-S": Commands.FILE_SAVE},
                    {"Ctrl-W": Commands.FILE_CLOSE},
                    {"Ctrl-Shift-O": Commands.FILE_QUICK_NAVIGATE},
                    {"Ctrl-Shift-F": Commands.FIND_IN_FILES},
                    {"Ctrl-R": Commands.FILE_RELOAD, "platform": "mac"},
                    {"F5"    : Commands.FILE_RELOAD, "platform": "win"}
                ],
                "platform": brackets.platform
            });
            KeyBindingManager.installKeymap(_globalKeymap);

            $(document.body).keydown(function (event) {
                if (KeyBindingManager.handleKey(KeyMap.translateKeyboardEvent(event))) {
                    event.preventDefault();
                }
            });
        }

        function initWindowListeners() {
            // TODO: (issue 269) to support IE, need to listen to document instead (and even then it may not work when focus is in an input field?)
            $(window).focus(function () {
                FileSyncManager.syncOpenDocuments();
                FileIndexManager.markDirty();
            });

            $(window).unload(function () {
                CommandManager.execute(Commands.FILE_CLOSE_WINDOW);
            });

            $(window).contextmenu(function (e) {
                e.preventDefault();
            });
        }

        EditorManager.setEditorHolder($('#editorHolder'));
        InlineEditorProviders.init();

        initListeners();
        initProject();
        Menus.init();
        initCommandHandlers();
        initKeyBindings();
        initWindowListeners();

        PerfUtils.addMeasurement("Application Startup");
    });

});


/*
 * Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, describe: false, beforeEach: false, afterEach: false, it: false, runs: false, waitsFor: false, expect: false, brackets: false */

define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var FileIndexManager,
        ProjectManager,
        SpecRunnerUtils     = require("./SpecRunnerUtils.js");

    describe("FileIndexManager", function () {

        var testPath = SpecRunnerUtils.getTestPath("/spec/FileIndexManager-test-files");
        var brackets;

        beforeEach(function () {

            runs(function () {
                SpecRunnerUtils.createTestWindowAndRun(this, function (testWindow) {
                    brackets = testWindow.brackets;

                    // Load module instances from brackets.test
                    FileIndexManager  = testWindow.brackets.test.FileIndexManager;
                    ProjectManager = testWindow.brackets.test.ProjectManager;

                });
            });

        });

        afterEach(function () {
            SpecRunnerUtils.closeTestWindow();
        });

        it("should index files in directory", function () {
            // Open a directory
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles, cssFiles;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        allFiles = result;
                    });
            });
            waitsFor(function () { return allFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                FileIndexManager.getFileInfoList("css")
                    .done(function (result) {
                        cssFiles = result;
                    });
            });
            waitsFor(function () { return cssFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(allFiles.length).toEqual(8);
                expect(cssFiles.length).toEqual(3);
            });

        });

        it("should match a specific filename and return the correct FileInfo", function () {
            // Open a directory
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var fileList;

            runs(function () {
                FileIndexManager.getFilenameMatches("all", "file_four.css")
                    .done(function (results) {
                        fileList = results;
                    });
            });

            waitsFor(function () { return fileList; }, 1000);

            runs(function () {
                expect(fileList.length).toEqual(1);
                expect(fileList[0].name).toEqual("file_four.css");
                expect(fileList[0].fullPath).toEqual(testPath + "/file_four.css");
            });
        });

        it("should update the indicies on project change", function () {
            // Load spec/FileIndexManager-test-files
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        allFiles = result;
                    });
            });

            waitsFor(function () { return allFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(allFiles.length).toEqual(8);
            });

            // load a subfolder in the test project
            // spec/FileIndexManager-test-files/dir1/dir2
            SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dir1/dir2/");

            var dir2Files;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        dir2Files = result;
                    });
            });

            waitsFor(function () { return dir2Files; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(dir2Files.length).toEqual(2);
                expect(dir2Files[0].name).toEqual("file_eight.css");
                expect(dir2Files[1].name).toEqual("file_seven.js");
            });
        });

        it("should update the indicies after being marked dirty", function () {
            // Load spec/FileIndexManager-test-files
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles; // set by checkAllFileCount

            // helper function to validate base state of 8 files
            function checkAllFileCount(fileCount) {
                var files;
                runs(function () {
                    FileIndexManager.getFileInfoList("all")
                        .done(function (result) {
                            files = result;
                        });
                });

                waitsFor(function () { return files; }, "FileIndexManager.getFileInfoList() timeout", 1000);

                runs(function () {
                    allFiles = files;
                    expect(files.length).toEqual(fileCount);
                });
            }

            // verify 8 files in base state
            checkAllFileCount(8);

            // add a temporary file to the folder
            var entry;

            // create a 9th file
            runs(function () {
                var root = ProjectManager.getProjectRoot();
                root.getFile("new-file.txt",
                             { create: true, exclusive: true },
                             function (fileEntry) { entry = fileEntry; });
            });

            waitsFor(function () { return entry; }, "getFile() timeout", 1000);

            runs(function () {
                // mark FileIndexManager dirty after new file was created
                FileIndexManager.markDirty();
            });

            // verify 9 files
            checkAllFileCount(9);

            var cleanupComplete = false;

            // verify the new file was added to the "all" index
            runs(function () {
                var filtered = allFiles.filter(function (value) {
                    return (value.name === "new-file.txt");
                });
                expect(filtered.length).toEqual(1);

                // remove the 9th file
                brackets.fs.unlink(entry.fullPath, function (err) {
                    cleanupComplete = (err === brackets.fs.NO_ERROR);
                });
            });

            // wait for the file to be deleted
            waitsFor(function () { return cleanupComplete; }, 1000);

            runs(function () {
                // mark FileIndexManager dirty after new file was deleted
                FileIndexManager.markDirty();
            });

            // verify that we're back to 8 files
            checkAllFileCount(8);

            // make sure the 9th file was removed from the index
            runs(function () {
                var filtered = allFiles.filter(function (value) {
                    return (value.name === "new-file.txt");
                });
                expect(filtered.length).toEqual(0);
            });
        });
    });
});


/* Reset.less
 * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here	that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

// ERIC MEYER RESET
// --------------------------------------------------

html, body { margin: 0; padding: 0; }
h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; }
table { border-collapse: collapse; border-spacing: 0; }
ol, ul { list-style: none; }
q:before, q:after, blockquote:before, blockquote:after { content: ""; }

// Normalize.css
// Pulling in select resets form the normalize.css project
// --------------------------------------------------

// Display in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
html {
  overflow-y: scroll;
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
}
// Focus states
a:focus {
  outline: thin dotted;
}
// Hover & Active
a:hover,
a:active {
  outline: 0;
}

// Display in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
nav,
section {
  display: block;
}

// Display block in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
audio,
canvas,
video {
  display: inline-block;
  *display: inline;
  *zoom: 1;
}

// Prevents modern browsers from displaying 'audio' without controls
// -------------------------
// Source: http://github.com/necolas/normalize.css
audio:not([controls]) {
    display: none;
}

// Prevents sub and sup affecting line-height in all browsers
// -------------------------
// Source: http://github.com/necolas/normalize.css
sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
sup {
  top: -0.5em;
}
sub {
  bottom: -0.25em;
}

// Img border in a's and image quality
// -------------------------
// Source: http://github.com/necolas/normalize.css
img {
    border: 0;
    -ms-interpolation-mode: bicubic;
}

// Forms
// -------------------------
// Source: http://github.com/necolas/normalize.css

// Font size in all browsers, margin changes, misc consistency
button,
input,
select,
textarea {
  font-size: 100%;
  margin: 0;
  vertical-align: baseline;
  *vertical-align: middle;
}
button,
input {
  line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
  *overflow: visible; // Inner spacing ie IE6/7
}
button::-moz-focus-inner,
input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
  border: 0;
  padding: 0;
}
button,
input[type="button"],
input[type="reset"],
input[type="submit"] {
  cursor: pointer; // Cursors on all buttons applied consistently
  -webkit-appearance: button; // Style clicable inputs in iOS
}
input[type="search"] { // Appearance in Safari/Chrome
  -webkit-appearance: textfield;
  -webkit-box-sizing: content-box;
     -moz-box-sizing: content-box;
          box-sizing: content-box;
}
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
}
textarea {
  overflow: auto; // Remove vertical scrollbar in IE6-9
  vertical-align: top; // Readability and alignment cross-browser
}


/* Brackets / CodeMirror Code Formatting */

.CodeMirror-scroll {
    background-color: @background-color-3;
    .code-font();
}

.cm-s-default {
    .CodeMirror-cursor {
        .code-cursor();
    }

    span.cm-keyword {color: @accent-keyword;}
    span.cm-atom {color: @accent-atom;}
    span.cm-number {color: @accent-number;}
    span.cm-def {color: @accent-def;}
    span.cm-variable {color: @accent-variable;}
    span.cm-variable-2 {color: @accent-variable-2;}
    span.cm-variable-3 {color: @accent-variable-3;}
    span.cm-property {color: @accent-property;}
    span.cm-operator {color: @accent-operator;}
    span.cm-comment {color: @accent-comment;}
    span.cm-string {color: @accent-string;}
    span.cm-string-2 {color: @accent-string-2;}
    span.cm-meta {color: @accent-meta;}
    span.cm-error {color: @accent-error;}
    span.cm-qualifier {color: @accent-qualifier;}
    span.cm-builtin {color: @accent-builtin;}
    span.cm-bracket {color: @accent-bracket;}
    span.cm-tag {color: @accent-tag;}
    span.cm-attribute {color: @accent-attribute;}
    span.cm-header {color: @accent-header;}
    span.cm-quote {color: @accent-quote;}
    span.cm-hr {color: @accent-hr;}
    span.cm-link {color: @accent-link;}

}

div.CodeMirror span.CodeMirror-matchingbracket {color: @accent-bracket; background-color: @background-color-2;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: @accent-bracket;}

/*
    CodeMirror's use of descendant selectors for certain styling causes problems when editors are
    nested because, for items in the inner editor, the left-hand clause in the selector will now
    match either the actual containing CodeMirror instance *OR* the outer "host" CodeMirror instance.

    TODO (issue #324): We'll still have problems if editors can be nested more than one level deep,
    or if any other descendant-selector-driven CM styles can differ between inner & outer editors
    (potential problem areas include line wrap and coloring theme: basically, anything in codemirror.css
    that uses a descandant selector where the CSS class name to the left of the space is something
    other than a vanilla .CodeMirror)
 */
.CodeMirror {
    .CodeMirror pre.CodeMirror-cursor {
        visibility: hidden;
    }
    .CodeMirror.CodeMirror-focused pre.CodeMirror-cursor {
        visibility: visible;
    }

    .CodeMirror div.CodeMirror-selected {
        background: #d9d9d9;
    }
    .CodeMirror.CodeMirror-focused div.CodeMirror-selected {
        background: #d2dcf8;
    }
}

.inlineCodeEditor {
    .cm-s-default {
        background-color: #F3F3F3;
    }
    .filename {
        position: absolute;
        #font > .sans-serif(normal, 12px, auto);
        top: 0;
        background: #FFF;
        padding: 2px 6px 0 2px;
        border: solid #C0C0C0;
        border-width: 0px 1px 1px;
        border-bottom-right-radius: 3px 5px;
        border-bottom-left-radius: 3px 5px;
        z-index: 11;

        .dirty-indicator {
            .bracketSprite;
            display: inline-block;
            background-position: 0 0;
            padding-top: 3px;
        }
    }
    .shadow {
        display: block;
        height: 6px;
        width: 100%;
        position: absolute;
        content: " ";
        left: 0;
        z-index: 10;
    }
    .shadow.top {
        top: 0px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
    }
    .shadow.bottom {
        bottom: 0px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
    }
}


This content was generated from LowLevelFileIO-test.js


/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, describe: false, it: false, expect: false, beforeEach: false, afterEach: false, waitsFor: false, runs: false */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var PreferencesManager      = require("PreferencesManager"),
        SpecRunnerUtils         = require("./SpecRunnerUtils.js");

    var CLIENT_ID = "PreferencesManager-test";

    describe("PreferencesManager", function () {

        beforeEach(function () {
            // SpecRunner.js already initializes the unit test instance of
            // PreferencesManager to use the unit test key. All we need to do
            // here is reset to clear callbacks and in-memory preferences.
            PreferencesManager._reset();
        });

        it("should register clients", function () {
            var pass1 = false,
                pass2 = false;

            function callback1(storage) {
                pass1 = true;
            }
            function callback2(storage) {
                pass2 = true;
            }

            PreferencesManager.addPreferencesClient("client1", callback1);
            PreferencesManager.addPreferencesClient("client2", callback2);
            PreferencesManager.savePreferences();

            expect(pass1 && pass2).toBeTruthy();
        });

        it("should assign 'this' object in callback", function () {
            var pass = false;
            var thisObj = {
                prop : "value",
                callback : function (storage) {
                    pass = (this.prop === "value");
                }
            };

            PreferencesManager.addPreferencesClient(CLIENT_ID, thisObj.callback, thisObj);
            PreferencesManager.savePreferences();

            expect(pass).toBeTruthy();
        });

        it("should register clients without duplicates", function () {
            var count = 0;

            function callback(storage) {
                count++;
            }

            // register listener twice
            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);
            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);

            PreferencesManager.savePreferences();

            expect(count).toEqual(1);
        });

        it("should use default preferences", function () {
            var pass = false,
                defaults = { prop : "default value" };

            PreferencesManager.addPreferencesClient(CLIENT_ID, function () {}, null, defaults);
            var prefs = PreferencesManager.getPreferences(CLIENT_ID);

            expect(prefs.prop).toEqual("default value");
        });

        it("should return undefined prefs for unregistered clients", function () {
            var pass = (undefined === PreferencesManager.getPreferences(CLIENT_ID));
            expect(pass).toBeTruthy();
        });

        it("should fail when storing an invalid JSON object", function () {
            // define a callback that stores a function value
            function callback(storage) {
                storage.badJSON = function () {};
            }

            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);
            PreferencesManager.savePreferences();

            // get prefs and confirm badJSON was never persisted
            var actual = PreferencesManager.getPreferences(CLIENT_ID);

            expect(actual.badJSON).toEqual(undefined);
        });

        it("should only write to preferences during save", function () {
            var fooUndefined = false,
                fooValue     = null;

            function callback(storage) {
                // storage.foo should not exist
                if (storage.foo === undefined) {
                    fooUndefined = true;
                } else {
                    // record previous value of foo
                    fooUndefined = false;
                    fooValue = storage.foo;
                }

                storage.foo = "callback";
            }

            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);

            // try to modify preferences outside of savePreferences
            var before = PreferencesManager.getPreferences(CLIENT_ID);
            before.foo = "before";

            // storage.foo = "callback"
            PreferencesManager.savePreferences();

            var after1 = PreferencesManager.getPreferences(CLIENT_ID);

            expect(fooUndefined).toBeTruthy();
            expect(fooValue).toEqual(null);
            expect(after1.foo).toEqual("callback");

            // try to modify preferences copy from getPreferences()
            after1.foo = "after";

            // save again, storage.foo = "callback"
            PreferencesManager.savePreferences();

            var after2 = PreferencesManager.getPreferences(CLIENT_ID);

            // foo should still be callback, after1.foo = "after" as no effect
            expect(fooUndefined).toBeFalsy();
            expect(fooValue).toEqual("callback");
            expect(after2.foo).toEqual("callback");
        });
    });
});


}
.cm-s-default {
.CodeMirror-cursor {
.code-cursor();
}