The MIT License (MIT)

Copyright (c) 2015 Maximilian Stoiber

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

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

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


keep keep keep keep replace keep keep keep keep

/*
 * Actions change things in your application
 * Since this boilerplate uses a uni-directional data flow, specifically redux,
 * we have these actions which are the only way your application interacts with
 * your appliction state. This guarantees that your state is up to date and nobody
 * messes it up weirdly somewhere.
 *
 * To add a new Action:
 * 1) Import your constant
 * 2) Add a function like this:
 *    export function yourAction(var) {
 *        return { type: YOUR_ACTION_CONSTANT, var: var }
 *    }
 * 3) (optional) Add an async function like this:
 *    export function asyncYourAction(var) {
 *        return function(dispatch) {
 *             // Do async stuff here
 *             return dispatch(yourAction(var));
 *        }
 *    }
 *
 *    If you add an async function, remove the export from the function
 *    created in the second step
 */

import { CHANGE_OWNER_NAME, CHANGE_PROJECT_NAME } from '../constants/AppConstants';

export function asyncChangeProjectName(name) {
  return function(dispatch) {
    // You can do async stuff here!
    // API fetching, Animations,...
    // For more information as to how and why you would do this, check https://github.com/gaearon/redux-thunk
    return dispatch(changeProjectName(name));
  }
}

export function asyncChangeOwnerName(name) {
  return function(dispatch) {
    // You can do async stuff here!
    // API fetching, Animations,...
    // For more information as to how and why you would do this, check https://github.com/gaearon/redux-thunk
    return dispatch(changeOwnerName(name));
  }
}

export function changeProjectName(name) {
  return { type: CHANGE_PROJECT_NAME, name };
}

export function changeOwnerName(name) {
  return { type: CHANGE_OWNER_NAME, name };
}


# Files in the root folder

* `.babelrc`: The Babel configuration. Babel is a ES6 to ES5 JavaScript transpiler, which means it's the tool that allows us to use ES6 in our applications.

* `.eslintignore`: Tell `eslint`, the JavaScript linter I have installed in my IDE (and you should too) to ignore certain files I don't want to have linted. Linting a file means to check that the syntax of the language is correct, which helps us catch typos/structural errors before we get an error in the browser console.

* `.eslintrc`: The `eslint` configuration, which checks that the JavaScript files are written in my preferred way of writing JS.

* `.gitattributes`: Normalizes how `git`, the version control system this boilerplate uses, handles certain files.

* `.gitignore`: Tells `git` to ignore certain files and folders which don't need to be version controlled, like the build folder.

* `.htaccess`: A default server configuration for a single page applications for the Apache web server. See the [`README`](README.md) for more information.

* `index.html`: The HTML file that your web application uses.

* `makewebpackconfig.js`: Generates the webpack configuration based on the environment. If we're in development, we add hot reloading and don't minify our code. If we're in production, we minify everything.

  * `webpack.dev.config.js`: Calls `makewebpackconfig.js`, setting the environment to `development`.

  * `webpack.prod.config.js`: Calls `makewebpackconfig.js`, setting the environment to `production`.

* `manifest.json`: Adds `Add to Homescreen` functionality on specific mobile phones. See the [`README`](README.md) for more information.

* `package.json`: `npm`, the package manager this boilerplate uses, specifies the dependencies and tools we use in this file. When you run `npm install`, this is the list that tells `npm` which packages/libraries to download. Also, `npm start`, `npm run build`,... commands are specified here.

* `server.js`: The development server, which allows us to go to `localhost:3000` and see the application live. Also makes hot-reloading work.

* `serviceworker.js`: The script that tells the serviceworker how to cache our files.


{
  "stage": 0,
  "env": {
    // only enable react-transform-hmr when process.env.NODE_ENV is not 'production'
    "development": {
      "plugins": ["react-transform"],
      "extra": {
        "react-transform": {
          "transforms": [{
            "transform": "react-transform-hmr",
            "imports": ["react"],
            "locals": ["module"]
          }]
        }
      }
    }
  }
}


This file exists so the empty directories (`actions`, `components`, `constants`, `dispatcher`, `stores` and `utils`) get commited and uploaded as well.


const { projectName, ownerName } = this.props.data;

This is the demo for the { projectName } by @{ ownerName }

keep keep keep keep replace replace replace keep keep keep

/*
 * ReadmePage
 *
 * This is the page users see when they click the "Setup" button on the HomePage
 */

import React, { Component} from 'react';
import { Link } from 'react-router';

export default class AboutPage extends Component {
	render() {
    return (

Further Setup

Assuming you have already cloned the repo and ran all the commands from the README (otherwise you would not be here), these are the further steps:

Replace my name and the package name in the package.json file

Replace the two components with your first component

Replace the default actions with your first action

Delete css/components/_home.css and add the styling for your component

And finally, update the unit tests

				<Link className="btn" to="/">Home</Link>

		);
  }
}


import expect from 'expect';
import { homeReducer } from '../js/reducers/reducers';
import * as constants from '../js/constants/AppConstants';

// Test Reducer
describe('defaultReducer', () => {
  // Test that the initial state is returning correctly
  it('should return the initial state', () => {
    expect(homeReducer(undefined, {})).toEqual({
      projectName: 'React.js Boilerplate',
      ownerName: 'mxstbr'
    });
  });

  // Test that it handles changing the owner correctly
  it('should handle the CHANGE_OWNER_NAME action', () => {
    const name = 'samsmith';

    expect(
      homeReducer({}, {
        type: constants.CHANGE_OWNER_NAME,
        name
      })
    ).toEqual({
      ownerName: name
    });
  });

  // Test that it handles changing the project name correctly
  it('should handle the CHANGE_PROJECT_NAME action', () => {
    const name = 'Webapplication Boilerplate';

    expect(
      homeReducer({}, {
        type: constants.CHANGE_PROJECT_NAME,
        name
      })
    ).toEqual({
      projectName: name
    });
  });
});


.wrapper {
	background-color: #FEFEFE;
	color: #222222;
	max-width: 978px;
	margin: 0 auto;
}

.home__wrapper a {
	color: #222222;
}

.home__wrapper h1,
.home__wrapper h2 {
	text-align: center;
}

.btn {
	background-color: skyblue;
	padding: 0.5em;
	color: #FEFEFE;
	border-radius: 3px;
	text-decoration: none;
}

.logo {
	max-height: 5em;
	margin: 0 auto;
	display: block;
}

.home__label {
	display: block;
	width: 100%;
	margin: 1em 0;
}

.home__input {
	margin-left: 0.5em;
	border-radius: 2px;
	border-width: 1px;
	border-color: grey;
	padding: 0.3em;
}


<label className="home__label">Change to your project name:

<input className="home__input" type="text" onChange={(evt) => { dispatch(asyncChangeProjectName(evt.target.value)); }} defaultValue="React.js Boilerplate" value={projectName} />

<label className="home__label">Change to your name:

<input className="home__input" type="text" onChange={(evt) => { dispatch(asyncChangeOwnerName(evt.target.value)); }} defaultValue="mxstbr" value={ownerName} />

keep keep keep keep replace keep keep keep

js/bundle.js
js/bundle.min.js
build
node_modules
*.html
*.md
*.json
*.css
config.js
server.js
*.config.js


// Gets called when running npm start

var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.dev.config');

new WebpackDevServer(webpack(config), { // Start a server
  publicPath: config.output.publicPath,
  hot: true, // With hot reloading
  inline: false,
  historyApiFallback: true,
  quiet: true // Without logging
}).listen(3000, 'localhost', function (err, result) {
  if (err) {
    console.log(err);
  }

  console.log('Listening at localhost:3000');
});


/*This file contains all styles related to the footer of the site/application.*/


/*
 * The reducer takes care of our data
 * Using actions, we can change our application state
 * To add a new action, add it to the switch statement in the homeReducer function
 *
 * Example:
 * case YOUR_ACTION_CONSTANT:
 *   return assign({}, state, {
 *       stateVariable: action.var
 *   });
 */

import { CHANGE_OWNER_NAME, CHANGE_PROJECT_NAME } from '../constants/AppConstants';
const assign = Object.assign || require('object.assign');

const initialState = {
  projectName: 'React.js Boilerplate',
  ownerName: 'mxstbr'
};

export function homeReducer(state = initialState, action) {
  switch (action.type) {
  case CHANGE_OWNER_NAME:
    return assign({}, state, {
      ownerName: action.name
    });
  case CHANGE_PROJECT_NAME:
    return assign({}, state, {
      projectName: action.name
    });
  default:
    return state;
  }
}


<ifModule mod_rewrite.c>

  #######################################################################
  # GENERAL                                                             #
  #######################################################################

  # Make apache follow sym links to files
  Options +FollowSymLinks
  # If somebody opens a folder, hide all files from the resulting folder list
  IndexIgnore */*

  #######################################################################
  # REWRITING                                                           #
  #######################################################################

  # Enable rewriting
  RewriteEngine On

  # If its not HTTPS
  RewriteCond %{HTTPS} off

  # Comment out the RewriteCond above, and uncomment the RewriteCond below if you're using a load balancer (e.g. CloudFlare) for SSL
  # RewriteCond %{HTTP:X-Forwarded-Proto} !https

  # Redirect to the same URL with https://, ignoring all further rules if this one is in effect
  RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [R,L]

  # If we get to here, it means we are on https://

  # If the file with the specified name in the browser doesn't exist
  RewriteCond %{REQUEST_FILENAME} !-f

  # and the directory with the specified name in the browser doesn't exist
  RewriteCond %{REQUEST_FILENAME} !-d

  # and we are not opening the root already (otherwise we get a redirect loop)
  RewriteCond %{REQUEST_FILENAME} !\/$

  # Rewrite all requests to the root
  RewriteRule ^(.*) /

</ifModule>


keep keep keep keep keep replace keep keep keep keep

/*
 * HomePage
 * This is the first thing users see of our App
 */

import { asyncChangeProjectName, asyncChangeOwnerName } from '../../actions/AppActions';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';

class HomePage extends Component {
	render() {
    const dispatch = this.props.dispatch;
    const { projectName, ownerName } = this.props.data;
    return (

Hello World!

This is the demo for the { projectName } by @{ ownerName }

        Change to your project name:
				  <input className="home__input" type="text" onChange={(evt) => { dispatch(asyncChangeProjectName(evt.target.value)); }} defaultValue="React.js Boilerplate" value={projectName} />

        Change to your name:
          <input className="home__input" type="text" onChange={(evt) => { dispatch(asyncChangeOwnerName(evt.target.value)); }} defaultValue="mxstbr" value={ownerName} />

        <Link className="btn" to="/readme">Setup</Link>

		);
  }
}

// REDUX STUFF

// Which props do we want to inject, given the global state?
function select(state) {
  return {
    data: state
  };
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(HomePage);


/* This file contains all @font-face declarations, if any. */


export default class AboutPage extends Component {

Assuming you have already cloned the repo and ran all the commands from the README (otherwise you would not be here), these are the further steps:

keep keep keep replace replace keep keep keep


/**
 *
 * App.react.js
 *
 * This component is the skeleton around the actual pages, and should only
 * contain code that should be seen on all pages. (e.g. navigation bar)
 */

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Logo from '../../img/logo.png';

class App extends Component {
  render() {
    return (

        { this.props.children }

    );
  }
}

// REDUX STUFF

// Which props do we want to inject, given the global state?
function select(state) {
  return {
    data: state
  };
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(App);


/*This file contains all styles related to the button component.*/


/* Do not write any CSS in here, add it to the appropriate module or make a new one and import it here */

@charset 'UTF-8';

/* 1. Vendors */
@import 'vendor/_normalize';

/* 2. Configuration and helpers */
@import 'utils/_variables';
@import 'base/_helpers';

/* 3. Base stuff */
@import 'base/_base';

/* 4. Layout-related sections */
@import 'layout/_header';
@import 'layout/_footer';

/* 5. Components */
@import 'components/_buttons';
@import 'components/_home';


import { RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from '../../types/utils'

import { FastifyLoggerInstance } from '../../types/logger'

import { RouteGenericInterface } from '../../types/route'

const getHandler: RouteHandlerMethod = function (_request, reply) {

import { FastifyInstance } from '../../types/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 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)
  })
})


'use strict'

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

test('close callback', t => {
  t.plan(4)
  const fastify = Fastify()
  fastify.addHook('onClose', onClose)
  function onClose (instance, done) {
    t.type(fastify, instance)
    done()
  }

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

    fastify.close((err) => {
      t.error(err)
      t.ok('close callback')
    })
  })
})

test('inside register', t => {
  t.plan(5)
  const fastify = Fastify()
  fastify.register(function (f, opts, done) {
    f.addHook('onClose', onClose)
    function onClose (instance, done) {
      t.ok(instance.prototype === fastify.prototype)
      t.equal(instance, f)
      done()
    }

    done()
  })

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

    fastify.close((err) => {
      t.error(err)
      t.ok('close callback')
    })
  })
})

test('close order', t => {
  t.plan(5)
  const fastify = Fastify()
  const order = [1, 2, 3]

  fastify.register(function (f, opts, done) {
    f.addHook('onClose', (instance, done) => {
      t.equal(order.shift(), 1)
      done()
    })

    done()
  })

  fastify.addHook('onClose', (instance, done) => {
    t.equal(order.shift(), 2)
    done()
  })

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

    fastify.close((err) => {
      t.error(err)
      t.equal(order.shift(), 3)
    })
  })
})

test('close order - async', async t => {
  t.plan(3)
  const fastify = Fastify()
  const order = [1, 2, 3]

  fastify.register(function (f, opts, done) {
    f.addHook('onClose', async instance => {
      t.equal(order.shift(), 1)
    })

    done()
  })

  fastify.addHook('onClose', () => {
    t.equal(order.shift(), 2)
  })

  await fastify.listen(0)
  await fastify.close()

  t.equal(order.shift(), 3)
})

test('should not throw an error if the server is not listening', t => {
  t.plan(2)
  const fastify = Fastify()
  fastify.addHook('onClose', onClose)
  function onClose (instance, done) {
    t.type(fastify, instance)
    done()
  }

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

test('onClose should keep the context', t => {
  t.plan(4)
  const fastify = Fastify()
  fastify.register(plugin)

  function plugin (instance, opts, done) {
    instance.decorate('test', true)
    instance.addHook('onClose', onClose)
    t.ok(instance.prototype === fastify.prototype)

    function onClose (i, done) {
      t.ok(i.test)
      t.equal(i, instance)
      done()
    }

    done()
  }

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

test('Should return error while closing (promise) - injection', t => {
  t.plan(4)
  const fastify = Fastify()

  fastify.addHook('onClose', (instance, done) => { done() })

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

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

    process.nextTick(() => {
      fastify.inject({
        method: 'GET',
        url: '/'
      }).catch(err => {
        t.ok(err)
        t.equal(err.message, 'Server is closed')
      })
    }, 100)
  })
})

test('Should return error while closing (callback) - injection', t => {
  t.plan(4)
  const fastify = Fastify()

  fastify.addHook('onClose', (instance, done) => {
    setTimeout(done, 150)
  })

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

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

    setTimeout(() => {
      fastify.inject({
        method: 'GET',
        url: '/'
      }, (err, res) => {
        t.ok(err)
        t.equal(err.message, 'Server is closed')
      })
    }, 100)
  })
})

t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false', t => {
  const fastify = Fastify({
    return503OnClosing: false
  })

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

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

    const port = fastify.server.address().port
    const client = net.createConnection({ port: port }, () => {
      client.write('GET / HTTP/1.1\r\n\r\n')

      client.once('data', data => {
        t.match(data.toString(), /Connection:\s*keep-alive/i)
        t.match(data.toString(), /200 OK/i)

        client.write('GET / HTTP/1.1\r\n\r\n')

        client.once('data', data => {
          t.match(data.toString(), /Connection:\s*close/i)
          t.match(data.toString(), /200 OK/i)

          // Test that fastify closes the TCP connection
          client.once('close', () => {
            t.end()
          })
        })
      })
    })
  })
})

t.test('Current opened connection should not accept new incoming connections', t => {
  t.plan(3)
  const fastify = Fastify()
  fastify.get('/', (req, reply) => {
    fastify.close()
    setTimeout(() => {
      reply.send({ hello: 'world' })
    }, 250)
  })

  fastify.listen(0, err => {
    t.error(err)
    const instance = new Client('http://localhost:' + fastify.server.address().port)
    instance.request({ path: '/', method: 'GET' }).then(data => {
      t.equal(data.statusCode, 200)
    })
    instance.request({ path: '/', method: 'GET' }).then(data => {
      t.equal(data.statusCode, 503)
    })
  })
})

test('Cannot be reopened the closed server without listen callback', async t => {
  t.plan(2)
  const fastify = Fastify()

  await fastify.listen(0)
  await fastify.close()

  try {
    await fastify.listen(0)
  } catch (err) {
    t.ok(err)
    t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')
  }
})

test('Cannot be reopened the closed server has listen callback', async t => {
  t.plan(2)
  const fastify = Fastify()

  await fastify.listen(0)
  await fastify.close()

  await new Promise((resolve, reject) => {
    fastify.listen(0, err => {
      reject(err)
    })
  }).catch(err => {
    t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')
    t.ok(err)
  })
})


'use strict'

const t = require('tap')
const proxyquire = require('proxyquire')
const test = t.test
const { Readable } = require('stream')
const { kTestInternals } = require('../../lib/symbols')
const Request = require('../../lib/request')
const Reply = require('../../lib/reply')

test('rawBody function', t => {
  t.plan(2)

  const internals = require('../../lib/contentTypeParser')[kTestInternals]
  const body = Buffer.from('你好 世界')
  const parser = {
    asString: true,
    asBuffer: false,
    fn (req, bodyInString, done) {
      t.equal(bodyInString, body.toString())
      t.equal(typeof done, 'function')
      return {
        then (cb) {
          cb()
        }
      }
    }
  }
  const res = {}
  res.end = () => { }
  res.writeHead = () => { }

  res.log = { error: () => { }, info: () => { } }
  const context = {
    Reply: Reply,
    Request: Request,
    preHandler: [],
    onSend: [],
    _parserOptions: {
      limit: 1024
    }
  }
  const rs = new Readable()
  rs._read = function () { }
  rs.headers = { 'content-length': body.length }
  const request = new Request('id', 'params', rs, 'query', 'log', context)
  const reply = new Reply(res, request)
  const done = () => { }

  internals.rawBody(
    request,
    reply,
    reply.context._parserOptions,
    parser,
    done
  )
  rs.emit('data', body.toString())
  rs.emit('end')
})

test('Should support Webpack and faux modules', t => {
  t.plan(2)

  const internals = proxyquire('../../lib/contentTypeParser', {
    'tiny-lru': { default: () => { } }
  })[kTestInternals]

  const body = Buffer.from('你好 世界')
  const parser = {
    asString: true,
    asBuffer: false,
    fn (req, bodyInString, done) {
      t.equal(bodyInString, body.toString())
      t.equal(typeof done, 'function')
      return {
        then (cb) {
          cb()
        }
      }
    }
  }
  const res = {}
  res.end = () => { }
  res.writeHead = () => { }

  res.log = { error: () => { }, info: () => { } }
  const context = {
    Reply: Reply,
    Request: Request,
    preHandler: [],
    onSend: [],
    _parserOptions: {
      limit: 1024
    }
  }
  const rs = new Readable()
  rs._read = function () { }
  rs.headers = { 'content-length': body.length }
  const request = new Request('id', 'params', rs, 'query', 'log', context)
  const reply = new Reply(res, request)
  const done = () => { }

  internals.rawBody(
    request,
    reply,
    reply.context._parserOptions,
    parser,
    done
  )
  rs.emit('data', body.toString())
  rs.emit('end')
})


expectType<(contentType: string) => FastifyReply>(reply.type)

expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer)

expectType<(payload: any) => string>(reply.serialize)

expectType<(fulfilled: () => void, rejected: (err: Error) => void) => void>(reply.then)

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


'use strict'

const Avvio = require('avvio')
const http = require('http')
const querystring = require('querystring')
let lightMyRequest
let version
let versionLoaded = false

const {
  kAvvioBoot,
  kChildren,
  kBodyLimit,
  kRoutePrefix,
  kLogLevel,
  kLogSerializers,
  kHooks,
  kSchemaController,
  kReplySerializerDefault,
  kContentTypeParser,
  kReply,
  kRequest,
  kFourOhFour,
  kState,
  kOptions,
  kPluginNameChain,
  kSchemaErrorFormatter,
  kErrorHandler
} = require('./lib/symbols.js')

const { createServer } = require('./lib/server')
const Reply = require('./lib/reply')
const Request = require('./lib/request')
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
const decorator = require('./lib/decorate')
const ContentTypeParser = require('./lib/contentTypeParser')
const SchemaController = require('./lib/schema-controller')
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
const { createLogger } = require('./lib/logger')
const pluginUtils = require('./lib/pluginUtils')
const reqIdGenFactory = require('./lib/reqIdGenFactory')
const { buildRouting, validateBodyLimitOption } = require('./lib/route')
const build404 = require('./lib/fourOhFour')
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
const override = require('./lib/pluginOverride')
const warning = require('./lib/warnings')
const { defaultInitOptions } = getSecuredInitialConfig

const {
  FST_ERR_BAD_URL,
  FST_ERR_MISSING_MIDDLEWARE
} = require('./lib/errors')

const onBadUrlContext = {
  config: {
  },
  onSend: [],
  onError: []
}

function defaultBuildPrettyMeta (route) {
  // return a shallow copy of route's sanitized context

  const cleanKeys = {}
  const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']

  allowedProps.concat(supportedHooks).forEach(k => {
    cleanKeys[k] = route.store[k]
  })

  return Object.assign({}, cleanKeys)
}

function defaultErrorHandler (error, request, reply) {
  if (reply.statusCode < 500) {
    reply.log.info(
      { res: reply, err: error },
      error && error.message
    )
  } else {
    reply.log.error(
      { req: request, res: reply, err: error },
      error && error.message
    )
  }
  reply.send(error)
}

function fastify (options) {
  // Options validations
  options = options || {}

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

  if (options.querystringParser && typeof options.querystringParser !== 'function') {
    throw new Error(`querystringParser option should be a function, instead got '${typeof options.querystringParser}'`)
  }

  if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
    throw new Error(`schemaController.bucket option should be a function, instead got '${typeof options.schemaController.bucket}'`)
  }

  validateBodyLimitOption(options.bodyLimit)

  const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader
  const querystringParser = options.querystringParser || querystring.parse
  const genReqId = options.genReqId || reqIdGenFactory()
  const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
  const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
  const disableRequestLogging = options.disableRequestLogging || false
  const exposeHeadRoutes = options.exposeHeadRoutes != null ? options.exposeHeadRoutes : false

  const ajvOptions = Object.assign({
    customOptions: {},
    plugins: []
  }, options.ajv)
  const frameworkErrors = options.frameworkErrors

  // Ajv options
  if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
    throw new Error(`ajv.customOptions option should be an object, instead got '${typeof ajvOptions.customOptions}'`)
  }
  if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
    throw new Error(`ajv.plugins option should be an array, instead got '${typeof ajvOptions.plugins}'`)
  }

  // Instance Fastify components
  const { logger, hasLogger } = createLogger(options)

  // Update the options with the fixed values
  options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
  options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
  options.logger = logger
  options.genReqId = genReqId
  options.requestIdHeader = requestIdHeader
  options.querystringParser = querystringParser
  options.requestIdLogLabel = requestIdLogLabel
  options.disableRequestLogging = disableRequestLogging
  options.ajv = ajvOptions
  options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
  options.exposeHeadRoutes = exposeHeadRoutes

  const initialConfig = getSecuredInitialConfig(options)

  let constraints = options.constraints
  if (options.versioning) {
    warning.emit('FSTDEP009')
    constraints = {
      ...constraints,
      version: {
        name: 'version',
        mustMatchWhenDerived: true,
        storage: options.versioning.storage,
        deriveConstraint: options.versioning.deriveVersion,
        validate (value) {
          if (typeof value !== 'string') {
            throw new Error('Version constraint should be a string.')
          }
        }
      }
    }
  }

  // Default router
  const router = buildRouting({
    config: {
      defaultRoute: defaultRoute,
      onBadUrl: onBadUrl,
      constraints: constraints,
      ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
      maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
      caseSensitive: options.caseSensitive,
      buildPrettyMeta: defaultBuildPrettyMeta
    }
  })

  // 404 router, used for handling encapsulated 404 handlers
  const fourOhFour = build404(options)

  // HTTP server and its handler
  const httpHandler = wrapRouting(router.routing, options)

  // we need to set this before calling createServer
  options.http2SessionTimeout = initialConfig.http2SessionTimeout
  const { server, listen } = createServer(options, httpHandler)

  const setupResponseListeners = Reply.setupResponseListeners
  const schemaController = SchemaController.buildSchemaController(null, options.schemaController)

  // Public API
  const fastify = {
    // Fastify internals
    [kState]: {
      listening: false,
      closing: false,
      started: false
    },
    [kOptions]: options,
    [kChildren]: [],
    [kBodyLimit]: bodyLimit,
    [kRoutePrefix]: '',
    [kLogLevel]: '',
    [kLogSerializers]: null,
    [kHooks]: new Hooks(),
    [kSchemaController]: schemaController,
    [kSchemaErrorFormatter]: null,
    [kErrorHandler]: defaultErrorHandler,
    [kReplySerializerDefault]: null,
    [kContentTypeParser]: new ContentTypeParser(
      bodyLimit,
      (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
      (options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
    ),
    [kReply]: Reply.buildReply(Reply),
    [kRequest]: Request.buildRequest(Request, options.trustProxy),
    [kFourOhFour]: fourOhFour,
    [pluginUtils.registeredPlugins]: [],
    [kPluginNameChain]: [],
    [kAvvioBoot]: null,
    // routing method
    routing: httpHandler,
    getDefaultRoute: router.getDefaultRoute.bind(router),
    setDefaultRoute: router.setDefaultRoute.bind(router),
    // routes shorthand methods
    delete: function _delete (url, opts, handler) {
      return router.prepareRoute.call(this, 'DELETE', url, opts, handler)
    },
    get: function _get (url, opts, handler) {
      return router.prepareRoute.call(this, 'GET', url, opts, handler)
    },
    head: function _head (url, opts, handler) {
      return router.prepareRoute.call(this, 'HEAD', url, opts, handler)
    },
    patch: function _patch (url, opts, handler) {
      return router.prepareRoute.call(this, 'PATCH', url, opts, handler)
    },
    post: function _post (url, opts, handler) {
      return router.prepareRoute.call(this, 'POST', url, opts, handler)
    },
    put: function _put (url, opts, handler) {
      return router.prepareRoute.call(this, 'PUT', url, opts, handler)
    },
    options: function _options (url, opts, handler) {
      return router.prepareRoute.call(this, 'OPTIONS', url, opts, handler)
    },
    all: function _all (url, opts, handler) {
      return router.prepareRoute.call(this, supportedMethods, url, opts, handler)
    },
    // extended route
    route: function _route (opts) {
      // we need the fastify object that we are producing so we apply a lazy loading of the function,
      // otherwise we should bind it after the declaration
      return router.route.call(this, opts)
    },
    // expose logger instance
    log: logger,
    // hooks
    addHook: addHook,
    // schemas
    addSchema: addSchema,
    getSchema: schemaController.getSchema.bind(schemaController),
    getSchemas: schemaController.getSchemas.bind(schemaController),
    setValidatorCompiler: setValidatorCompiler,
    setSerializerCompiler: setSerializerCompiler,
    setSchemaController: setSchemaController,
    setReplySerializer: setReplySerializer,
    setSchemaErrorFormatter: setSchemaErrorFormatter,
    // custom parsers
    addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,
    hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,
    getDefaultJsonParser: ContentTypeParser.defaultParsers.getDefaultJsonParser,
    defaultTextParser: ContentTypeParser.defaultParsers.defaultTextParser,
    // Fastify architecture methods (initialized by Avvio)
    register: null,
    after: null,
    ready: null,
    onClose: null,
    close: null,
    printPlugins: null,
    // http server
    listen: listen,
    server: server,
    // extend fastify objects
    decorate: decorator.add,
    hasDecorator: decorator.exist,
    decorateReply: decorator.decorateReply,
    decorateRequest: decorator.decorateRequest,
    hasRequestDecorator: decorator.existRequest,
    hasReplyDecorator: decorator.existReply,
    // fake http injection
    inject: inject,
    // pretty print of the registered routes
    printRoutes,
    // custom error handling
    setNotFoundHandler: setNotFoundHandler,
    setErrorHandler: setErrorHandler,
    // Set fastify initial configuration options read-only object
    initialConfig
  }

  fastify[kReply].prototype.server = fastify
  fastify[kRequest].prototype.server = fastify

  Object.defineProperties(fastify, {
    pluginName: {
      get () {
        if (this[kPluginNameChain].length > 1) {
          return this[kPluginNameChain].join(' -> ')
        }
        return this[kPluginNameChain][0]
      }
    },
    prefix: {
      get () { return this[kRoutePrefix] }
    },
    validatorCompiler: {
      get () { return this[kSchemaController].getValidatorCompiler() }
    },
    serializerCompiler: {
      get () { return this[kSchemaController].getSerializerCompiler() }
    },
    version: {
      get () {
        if (versionLoaded === false) {
          version = loadVersion()
        }
        return version
      }
    },
    errorHandler: {
      get () {
        return this[kErrorHandler]
      }
    }
  })

  // We are adding `use` to the fastify prototype so the user
  // can still access it (and get the expected error), but `decorate`
  // will not detect it, and allow the user to override it.
  Object.setPrototypeOf(fastify, { use })

  if (options.schemaErrorFormatter) {
    validateSchemaErrorFormatter(options.schemaErrorFormatter)
    fastify[kSchemaErrorFormatter] = options.schemaErrorFormatter.bind(fastify)
  }

  // Install and configure Avvio
  // Avvio will update the following Fastify methods:
  // - register
  // - after
  // - ready
  // - onClose
  // - close
  const avvio = Avvio(fastify, {
    autostart: false,
    timeout: Number(options.pluginTimeout) || defaultInitOptions.pluginTimeout,
    expose: {
      use: 'register'
    }
  })
  // Override to allow the plugin encapsulation
  avvio.override = override
  avvio.on('start', () => (fastify[kState].started = true))
  fastify[kAvvioBoot] = fastify.ready // the avvio ready function
  fastify.ready = ready // overwrite the avvio ready function
  fastify.printPlugins = avvio.prettyPrint.bind(avvio)

  // cache the closing value, since we are checking it in an hot path
  avvio.once('preReady', () => {
    fastify.onClose((instance, done) => {
      fastify[kState].closing = true
      router.closeRoutes()
      if (fastify[kState].listening) {
        // No new TCP connections are accepted
        instance.server.close(done)
      } else {
        done(null)
      }
    })
  })

  // Set the default 404 handler
  fastify.setNotFoundHandler()
  fourOhFour.arrange404(fastify)

  router.setup(options, {
    avvio,
    fourOhFour,
    logger,
    hasLogger,
    setupResponseListeners,
    throwIfAlreadyStarted
  })

  // Delay configuring clientError handler so that it can access fastify state.
  server.on('clientError', options.clientErrorHandler.bind(fastify))

  return fastify

  function throwIfAlreadyStarted (msg) {
    if (fastify[kState].started) throw new Error(msg)
  }

  // HTTP injection handling
  // If the server is not ready yet, this
  // utility will automatically force it.
  function inject (opts, cb) {
    // lightMyRequest is dynamically laoded as it seems very expensive
    // because of Ajv
    if (lightMyRequest === undefined) {
      lightMyRequest = require('light-my-request')
    }

    if (fastify[kState].started) {
      if (fastify[kState].closing) {
        // Force to return an error
        const error = new Error('Server is closed')
        if (cb) {
          cb(error)
          return
        } else {
          return Promise.reject(error)
        }
      }
      return lightMyRequest(httpHandler, opts, cb)
    }

    if (cb) {
      this.ready(err => {
        if (err) cb(err, null)
        else lightMyRequest(httpHandler, opts, cb)
      })
    } else {
      return lightMyRequest((req, res) => {
        this.ready(function (err) {
          if (err) {
            res.emit('error', err)
            return
          }
          httpHandler(req, res)
        })
      }, opts)
    }
  }

  function ready (cb) {
    let resolveReady
    let rejectReady

    // run the hooks after returning the promise
    process.nextTick(runHooks)

    if (!cb) {
      return new Promise(function (resolve, reject) {
        resolveReady = resolve
        rejectReady = reject
      })
    }

    function runHooks () {
      // start loading
      fastify[kAvvioBoot]((err, done) => {
        if (err || fastify[kState].started) {
          manageErr(err)
        } else {
          hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)
        }
        done()
      })
    }

    function manageErr (err) {
      if (cb) {
        if (err) {
          cb(err)
        } else {
          cb(undefined, fastify)
        }
      } else {
        if (err) {
          return rejectReady(err)
        }
        resolveReady(fastify)
      }
    }
  }

  function use () {
    throw new FST_ERR_MISSING_MIDDLEWARE()
  }

  // wrapper that we expose to the user for hooks handling
  function addHook (name, fn) {
    throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!')

    if (name === 'onSend' || name === 'preSerialization' || name === 'onError') {
      if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
        throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
      }
    } else if (name === 'onReady') {
      if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
        throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
      }
    } else if (name !== 'preParsing') {
      if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
        throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
      }
    }

    if (name === 'onClose') {
      this[kHooks].validate(name, fn)
      this.onClose(fn)
    } else if (name === 'onReady') {
      this[kHooks].validate(name, fn)
      this[kHooks].add(name, fn)
    } else {
      this.after((err, done) => {
        _addHook.call(this, name, fn)
        done(err)
      })
    }
    return this

    function _addHook (name, fn) {
      this[kHooks].add(name, fn)
      this[kChildren].forEach(child => _addHook.call(child, name, fn))
    }
  }

  // wrapper that we expose to the user for schemas handling
  function addSchema (schema) {
    throwIfAlreadyStarted('Cannot call "addSchema" when fastify instance is already started!')
    this[kSchemaController].add(schema)
    this[kChildren].forEach(child => child.addSchema(schema))
    return this
  }

  function defaultClientErrorHandler (err, socket) {
    // In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
    // https://github.com/fastify/fastify/issues/2036
    // https://github.com/nodejs/node/issues/33302
    if (err.code === 'ECONNRESET') {
      return
    }

    const body = JSON.stringify({
      error: http.STATUS_CODES['400'],
      message: 'Client Error',
      statusCode: 400
    })

    // Most devs do not know what to do with this error.
    // In the vast majority of cases, it's a network error and/or some
    // config issue on the load balancer side.
    this.log.trace({ err }, 'client error')

    // If the socket is not writable, there is no reason to try to send data.
    if (socket.writable) {
      socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
    }
  }

  // If the router does not match any route, every request will land here
  // req and res are Node.js core objects
  function defaultRoute (req, res) {
    if (req.headers['accept-version'] !== undefined) {
      req.headers['accept-version'] = undefined
    }
    fourOhFour.router.lookup(req, res)
  }

  function onBadUrl (path, req, res) {
    if (frameworkErrors) {
      const id = genReqId(req)
      const childLogger = logger.child({ reqId: id })

      childLogger.info({ req }, 'incoming request')

      const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
      const reply = new Reply(res, request, childLogger)
      return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
    }
    const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}`
    res.writeHead(400, {
      'Content-Type': 'application/json',
      'Content-Length': body.length
    })
    res.end(body)
  }

  function setNotFoundHandler (opts, handler) {
    throwIfAlreadyStarted('Cannot call "setNotFoundHandler" when fastify instance is already started!')

    fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler)
    return this
  }

  function setValidatorCompiler (validatorCompiler) {
    throwIfAlreadyStarted('Cannot call "setValidatorCompiler" when fastify instance is already started!')
    this[kSchemaController].setValidatorCompiler(validatorCompiler)
    return this
  }

  function setSchemaErrorFormatter (errorFormatter) {
    throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter" when fastify instance is already started!')
    validateSchemaErrorFormatter(errorFormatter)
    this[kSchemaErrorFormatter] = errorFormatter.bind(this)
    return this
  }

  function setSerializerCompiler (serializerCompiler) {
    throwIfAlreadyStarted('Cannot call "setSerializerCompiler" when fastify instance is already started!')
    this[kSchemaController].setSerializerCompiler(serializerCompiler)
    return this
  }

  function setSchemaController (schemaControllerOpts) {
    throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!')
    const old = this[kSchemaController]
    const schemaController = SchemaController.buildSchemaController(old.parent, Object.assign({}, old.opts, schemaControllerOpts))
    this[kSchemaController] = schemaController
    this.getSchema = schemaController.getSchema.bind(schemaController)
    this.getSchemas = schemaController.getSchemas.bind(schemaController)
    return this
  }

  function setReplySerializer (replySerializer) {
    throwIfAlreadyStarted('Cannot call "setReplySerializer" when fastify instance is already started!')

    this[kReplySerializerDefault] = replySerializer
    return this
  }

  // wrapper that we expose to the user for configure the custom error handler
  function setErrorHandler (func) {
    throwIfAlreadyStarted('Cannot call "setErrorHandler" when fastify instance is already started!')

    this[kErrorHandler] = func.bind(this)
    return this
  }

  function printRoutes (opts = {}) {
    // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
    opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
    return router.printRoutes(opts)
  }
}

function validateSchemaErrorFormatter (schemaErrorFormatter) {
  if (typeof schemaErrorFormatter !== 'function') {
    throw new Error(`schemaErrorFormatter option should be a function, instead got ${typeof schemaErrorFormatter}`)
  } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {
    throw new Error('schemaErrorFormatter option should not be an async function')
  }
}

function wrapRouting (httpHandler, { rewriteUrl, logger }) {
  if (!rewriteUrl) {
    return httpHandler
  }
  return function preRouting (req, res) {
    const originalUrl = req.url
    const url = rewriteUrl(req)
    if (originalUrl !== url) {
      logger.debug({ originalUrl, url }, 'rewrite url')
      if (typeof url === 'string') {
        req.url = url
      } else {
        req.destroy(new Error(`Rewrite url for "${req.url}" needs to be of type "string" but received "${typeof url}"`))
      }
    }
    httpHandler(req, res)
  }
}

function loadVersion () {
  versionLoaded = true
  const fs = require('fs')
  const path = require('path')
  try {
    const pkgPath = path.join(__dirname, 'package.json')
    fs.accessSync(pkgPath, fs.constants.R_OK)
    const pkg = JSON.parse(fs.readFileSync(pkgPath))
    return pkg.name === 'fastify' ? pkg.version : undefined
  } catch (e) {
    return undefined
  }
}

/**
 * These export configurations enable JS and TS developers
 * to consumer fastify in whatever way best suits their needs.
 * Some examples of supported import syntax includes:
 * - `const fastify = require('fastify')`
 * - `const { fastify } = require('fastify')`
 * - `import * as Fastify from 'fastify'`
 * - `import { fastify, TSC_definition } from 'fastify'`
 * - `import fastify from 'fastify'`
 * - `import fastify, { TSC_definition } from 'fastify'`
 */
module.exports = fastify
module.exports.fastify = fastify
module.exports.default = fastify


'use strict'

const fs = require('fs')
const path = require('path')
const fastify = require('../fastify')({
  http2: true,
  https: {
    key: fs.readFileSync(path.join(__dirname, '../test/https/fastify.key')),
    cert: fs.readFileSync(path.join(__dirname, '../test/https/fastify.cert'))
  },
  logger: true
})

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

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

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


import fastify, { FastifyBodyParser, FastifyContentTypeParser } from '../../fastify'
import { expectError, expectType } from 'tsd'
import { IncomingMessage } from 'http'
import { FastifyRequest } from '../../types/request'

expectType<void>(fastify().addContentTypeParser('contentType', function (request, payload, done) {
  expectType<FastifyRequest>(request)
  expectType<IncomingMessage>(payload)
  done(null)
}))

// Body limit options

expectType<void>(fastify().addContentTypeParser('contentType', { bodyLimit: 99 }, function (request, payload, done) {
  expectType<FastifyRequest>(request)
  expectType<IncomingMessage>(payload)
  done(null)
}))

// Array for contentType

expectType<void>(fastify().addContentTypeParser(['contentType'], function (request, payload, done) {
  expectType<FastifyRequest>(request)
  expectType<IncomingMessage>(payload)
  done(null)
}))

// Body Parser - the generic after addContentTypeParser enforces the type of the `body` parameter as well as the value of the `parseAs` property

expectType<void>(fastify().addContentTypeParser<string>('bodyContentType', { parseAs: 'string' }, function (request, body, done) {
  expectType<FastifyRequest>(request)
  expectType<string>(body)
  done(null)
}))

expectType<void>(fastify().addContentTypeParser<Buffer>('bodyContentType', { parseAs: 'buffer' }, function (request, body, done) {
  expectType<FastifyRequest>(request)
  expectType<Buffer>(body)
  done(null)
}))

expectType<void>(fastify().addContentTypeParser('contentType', async function (request: FastifyRequest, payload: IncomingMessage) {
  expectType<FastifyRequest>(request)
  expectType<IncomingMessage>(payload)
  return null
}))

expectType<void>(fastify().addContentTypeParser<string>('bodyContentType', { parseAs: 'string' }, async function (request: FastifyRequest, body: string) {
  expectType<FastifyRequest>(request)
  expectType<string>(body)
  return null
}))

expectType<void>(fastify().addContentTypeParser<Buffer>('bodyContentType', { parseAs: 'buffer' }, async function (request: FastifyRequest, body: Buffer) {
  expectType<FastifyRequest>(request)
  expectType<Buffer>(body)
  return null
}))

expectType<FastifyBodyParser<string>>(fastify().getDefaultJsonParser('error', 'ignore'))

expectError(fastify().getDefaultJsonParser('error', 'skip'))

expectError(fastify().getDefaultJsonParser('nothing', 'ignore'))


import { RawServerDefault, RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault, RawReplyDefaultExpression } from '../../types/utils'

import { FastifyRequest } from '../../types/request'

import { FastifyReply } from '../../types/reply'


'use strict'

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

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

test('chainable - get', t => {
  t.plan(1)
  t.type(fastify.get('/', opts, noop), fastify)
})

test('chainable - post', t => {
  t.plan(1)
  t.type(fastify.post('/', opts, noop), fastify)
})

test('chainable - route', t => {
  t.plan(1)
  t.type(fastify.route({
    method: 'GET',
    url: '/other',
    schema: opts.schema,
    handler: noop
  }), fastify)
})