fastify 2.12.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  <div align="center">
17
17
 
18
18
  [![NPM version](https://img.shields.io/npm/v/fastify.svg?style=flat)](https://www.npmjs.com/package/fastify)
19
- [![NPM downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.npmjs.com/package/fastify) [![Gitter](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/fastify)
19
+ [![NPM downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.npmjs.com/package/fastify)
20
20
  [![Security Responsible
21
21
  Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/nodejs/security-wg/blob/master/processes/responsible_disclosure_template.md)
22
22
 
@@ -29,8 +29,47 @@ How can you efficiently handle the resources of your server, knowing that you ar
29
29
 
30
30
  Enter Fastify. Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.
31
31
 
32
+ ### Quick start
33
+
34
+ Create a folder and make it your current working directory:
35
+
36
+ ```
37
+ mkdir my-app
38
+ cd my-app
39
+ ```
40
+
41
+ Generate a fastify project with `npm init`:
42
+
43
+ ```sh
44
+ npm init fastify
45
+ ```
46
+
47
+ Install dependencies:
48
+
49
+ ```js
50
+ npm install
51
+ ```
52
+
53
+ To start the app in dev mode:
54
+
55
+ ```sh
56
+ npm run dev
57
+ ```
58
+
59
+ For production mode:
60
+
61
+ ```sh
62
+ npm start
63
+ ```
64
+
65
+ Under the hood `npm init` downloads and runs [Fastify Create](https://github.com/fastify/fastify-create),
66
+ which in turn uses the generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli).
67
+
68
+
32
69
  ### Install
33
70
 
71
+ If installing in an existing project, then Fastify can be installed into the project as a dependency:
72
+
34
73
  Install with npm:
35
74
  ```
36
75
  npm i fastify --save
@@ -80,29 +119,6 @@ fastify.listen(3000, (err, address) => {
80
119
 
81
120
  Do you want to know more? Head to the <a href="https://github.com/fastify/fastify/blob/master/docs/Getting-Started.md"><code><b>Getting Started</b></code></a>.
82
121
 
83
- ### Quick start with Fastify CLI
84
-
85
- Good tools make API development quicker and easier to maintain than doing everything manually.
86
-
87
- The [Fastify CLI](https://github.com/fastify/fastify-cli) is a command line interface tool that can create new projects, manage plugins, and perform a variety of development tasks testing and running the application.
88
-
89
- The goal in this guide is to build and run a simple Fastify project, using the [Fastify CLI](https://github.com/fastify/fastify-cli), while adhering to the Style Guide recommendations that benefit every Fastify project.
90
-
91
- ### Example
92
-
93
- Open a terminal window.
94
-
95
- ```
96
- npm install fastify-cli --global
97
- ```
98
-
99
- Generate a new project and default app by running the following command:
100
-
101
- ```
102
- fastify generate
103
- ```
104
-
105
- For more information, see the [Fastify CLI documentation](https://github.com/fastify/fastify-cli).
106
122
 
107
123
  ### Fastify v1.x
108
124
 
@@ -176,8 +192,8 @@ matters to you.
176
192
  - [Live Examples](https://github.com/fastify/example) - Multirepo with a broad set of real working examples.
177
193
 
178
194
  ## Support
179
- - [Fastify help](https://github.com/fastify/help)
180
- - [Gitter Chat](https://gitter.im/fastify)
195
+ Please visit [Fastify help](https://github.com/fastify/help) to view prior
196
+ support issues and to ask new support questions.
181
197
 
182
198
  ## Team
183
199
 
@@ -191,6 +207,7 @@ Team members are listed in alphabetical order.
191
207
  ### Fastify Core team
192
208
  * [__Tommaso Allevi__](https://github.com/allevo), <https://twitter.com/allevitommaso>, <https://www.npmjs.com/~allevo>
193
209
  * [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/), <https://twitter.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
210
+ * [__David Mark Clements__](https://github.com/davidmarkclements), <https://twitter.com/davidmarkclem>, <https://www.npmjs.com/~davidmarkclements>
194
211
  * [__Matteo Collina__](https://github.com/mcollina), <https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
195
212
  * [__Tomas Della Vedova__](https://github.com/delvedor), <https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
196
213
  * [__Dustin Deus__](https://github.com/StarpTech), <https://twitter.com/dustindeus>, <https://www.npmjs.com/~starptech>
@@ -2,22 +2,81 @@
2
2
 
3
3
  ## Decorators
4
4
 
5
- If you need to add functionality to the Fastify instance, the `decorate` API is what you want.
5
+ The decorators API allows customization of the core Fastify objects, such as
6
+ the server instance itself and any request and reply objects used during the
7
+ HTTP request lifecycle. The decorators API can be used to attach any type of
8
+ property to the core objects, e.g. functions, plain objects, or native types.
9
+
10
+ This API is a *synchronous* API. Attempting to define a decoration
11
+ asynchronously could result in the Fastify instance booting prior to the
12
+ decoration completing its initialization. To avoid this issue, and register an
13
+ asynchronous decoration, the `register` API, in combination with
14
+ `fastify-plugin`, must be used instead. To learn more, see the
15
+ [Plugins](Plugins.md) documentation.
16
+
17
+ Decorating core objects with this API allows the underlying JavaScript engine
18
+ to optimize handling of the server, request, and reply objects. This is
19
+ accomplished by defining the shape of all such object instances before they are
20
+ instantiated and used. As an example, the following is not recommended because
21
+ it will change the shape of objects during their lifecycle:
6
22
 
7
- The API allows you to add new properties to the Fastify instance. Possible values are not restricted by type and could be functions, objects or strings, for example.
23
+ ```js
24
+ // Bad example! Continue reading.
25
+
26
+ // Attach a user property to the incoming request before the request
27
+ // handler is invoked.
28
+ fastify.addHook('preHandler', function (req, reply, done) {
29
+ req.user = 'Bob Dylan'
30
+ done()
31
+ })
32
+
33
+ // Use the attached user property in the request handler.
34
+ fastify.get('/', function (req, reply) {
35
+ reply.send(`Hello, ${req.user}`)
36
+ })
37
+ ```
38
+
39
+ Since the above example mutates the request object after it has already
40
+ been instantiated, the JavaScript engine must deoptimize access to the request
41
+ object. By using the decoration API this deoptimization is avoided:
42
+
43
+ ```js
44
+ // Decorate request with a 'user' property
45
+ fastify.decorateRequest('user', '')
46
+
47
+ // Update our property
48
+ fastify.addHook('preHandler', (req, reply, done) => {
49
+ req.user = 'Bob Dylan'
50
+ done()
51
+ })
52
+ // And finally access it
53
+ fastify.get('/', (req, reply) => {
54
+ reply.send(`Hello, ${req.user}!`)
55
+ })
56
+ ```
57
+
58
+ See
59
+ [JavaScript engine fundamentals: Shapes and Inline Caches](https://web.archive.org/web/20200201163000/https://mathiasbynens.be/notes/shapes-ics)
60
+ for more information on this topic.
8
61
 
9
- <a name="usage"></a>
10
62
  ### Usage
63
+ <a name="usage"></a>
64
+
65
+ #### `decorate(name, value, [dependencies])`
11
66
  <a name="decorate"></a>
12
- **decorate**
13
- Just call the `decorate` API and pass the name of the new property and its value.
67
+
68
+ This method is used to customize the Fastify [server](Server.md) instance.
69
+
70
+ For example, to attach a new method to the server instance:
71
+
14
72
  ```js
15
- fastify.decorate('utility', () => {
73
+ fastify.decorate('utility', function () {
16
74
  // Something very useful
17
75
  })
18
76
  ```
19
77
 
20
- As mentioned above, you can also decorate the instance with non-function values:
78
+ As mentioned above, non-function values can be attached:
79
+
21
80
  ```js
22
81
  fastify.decorate('conf', {
23
82
  db: 'some.db',
@@ -25,39 +84,95 @@ fastify.decorate('conf', {
25
84
  })
26
85
  ```
27
86
 
28
- Once the instance was decorated, you can access the new value by using the name you passed as a parameter:
87
+ To access decorated properties, simply use the name provided to the
88
+ decoration API:
89
+
29
90
  ```js
30
91
  fastify.utility()
31
92
 
32
93
  console.log(fastify.conf.db)
33
94
  ```
34
95
 
96
+ The `dependencies` parameter is an optional list of decorators that the
97
+ decorator being defined relies upon. This list is simply a list of string names
98
+ of other decorators. In the following example, the "utility" decorator depends
99
+ upon "greet" and "log" decorators:
100
+
101
+ ```js
102
+ fastify.decorate('utility', fn, ['greet', 'log'])
103
+ ```
104
+
105
+ If a dependency is not satisfied, the `decorate` method will throw an exception.
106
+ The dependency check is peformed before the server instance is booted. Thus,
107
+ it cannot occur during runtime.
108
+
109
+ #### `decorateReply(name, value, [dependencies])`
35
110
  <a name="decorate-reply"></a>
36
- **decorateReply**
37
- As the name suggests, this API can be used to add new methods to the `Reply` core object. Just call the `decorateReply` API and pass the name of the new property and its value:
111
+
112
+ As the name suggests, this API is used to add new methods/properties to the core
113
+ `Reply` object:
114
+
38
115
  ```js
39
116
  fastify.decorateReply('utility', function () {
40
117
  // Something very useful
41
118
  })
42
119
  ```
43
120
 
44
- Note: using an arrow function will break the binding of `this` to the Fastify `Reply` instance.
121
+ Note: using an arrow function will break the binding of `this` to the Fastify
122
+ `Reply` instance.
45
123
 
124
+ See [`decorate`](#decorate) for information about the `dependencies` parameter.
125
+
126
+ #### `decorateRequest(name, value, [dependencies])`
46
127
  <a name="decorate-request"></a>
47
- **decorateRequest**
48
- As above, this API is needed if you want to add new methods to the `Request` core object. Just call the `decorateRequest` API and pass the name of the new property and its value:
128
+
129
+ As above with [`decorateReply`](#decorate-reply), this API is used add new
130
+ methods/properties to the core `Request` object:
131
+
49
132
  ```js
50
133
  fastify.decorateRequest('utility', function () {
51
134
  // something very useful
52
135
  })
53
136
  ```
54
137
 
55
- Note: using an arrow function will break the binding of `this` to the Fastify `Request` instance.
138
+ Note: using an arrow function will break the binding of `this` to the Fastify
139
+ `Request` instance.
140
+
141
+ See [`decorate`](#decorate) for information about the `dependencies` parameter.
142
+
143
+ #### `hasDecorator(name)`
144
+ <a name="has-decorator"></a>
145
+
146
+ Used to check for the existence of a server instance decoration:
147
+
148
+ ```js
149
+ fastify.hasDecorator('utility')
150
+ ```
151
+
152
+ #### hasRequestDecorator
153
+ <a name="has-request-decorator"></a>
154
+
155
+ Used to check for the existence of a Request decoration:
156
+
157
+ ```js
158
+ fastify.hasRequestDecorator('utility')
159
+ ```
160
+
161
+ #### hasReplyDecorator
162
+ <a name="has-reply-decorator"></a>
163
+
164
+ Used to check for the existence of a Reply decoration:
165
+
166
+ ```js
167
+ fastify.hasReplyDecorator('utility')
168
+ ```
56
169
 
170
+ ### Decorators and Encapsulation
57
171
  <a name="decorators-encapsulation"></a>
58
- #### Decorators and Encapsulation
59
172
 
60
- If you define a decorator (using `decorate`, `decorateRequest` or `decorateReply`) with the same name more than once in the same **encapsulated** plugin, Fastify will throw an exception.
173
+ Defining a decorator (using `decorate`, `decorateRequest` or `decorateReply`)
174
+ with the same name more than once in the same **encapsulated** context will
175
+ throw an exception.
61
176
 
62
177
  As an example, the following will throw:
63
178
 
@@ -107,10 +222,12 @@ server.register(async function (server, opts) {
107
222
  server.listen(3000)
108
223
  ```
109
224
 
225
+ ### Getters and Setters
110
226
  <a name="getters-setters"></a>
111
- #### Getters and Setters
112
227
 
113
- Decorators accept special "getter/setter" objects. These objects have functions named `getter` and `setter` (though, the `setter` function is optional). This allows defining properties via decorators. For example:
228
+ Decorators accept special "getter/setter" objects. These objects have functions
229
+ named `getter` and `setter` (though, the `setter` function is optional). This
230
+ allows defining properties via decorators. For example:
114
231
 
115
232
  ```js
116
233
  fastify.decorate('foo', {
@@ -120,64 +237,8 @@ fastify.decorate('foo', {
120
237
  })
121
238
  ```
122
239
 
123
- Will define the `foo` property on the *Fastify* instance:
240
+ Will define the `foo` property on the Fastify instance:
124
241
 
125
242
  ```js
126
243
  console.log(fastify.foo) // 'a getter'
127
244
  ```
128
-
129
- <a name="usage_notes"></a>
130
- #### Usage Notes
131
- `decorateReply` and `decorateRequest` are used to modify the `Reply` and `Request` constructors respectively by adding methods or properties. To update these properties you should directly access the desired property of the `Reply` or `Request` object.
132
-
133
- As an example let's add a user property to the `Request` object:
134
-
135
- ```js
136
- // Decorate request with a 'user' property
137
- fastify.decorateRequest('user', '')
138
-
139
- // Update our property
140
- fastify.addHook('preHandler', (req, reply, done) => {
141
- req.user = 'Bob Dylan'
142
- done()
143
- })
144
- // And finally access it
145
- fastify.get('/', (req, reply) => {
146
- reply.send(`Hello ${req.user}!`)
147
- })
148
- ```
149
- Note: The usage of `decorateReply` and `decorateRequest` is optional in this case but will allow Fastify to optimize for performance.
150
-
151
- <a name="sync-async"></a>
152
- #### Sync and Async
153
- `decorate` is a *synchronous* API. If you need to add a decorator that has an *asynchronous* bootstrap, Fastify could boot up before your decorator is ready. To avoid this issue, you must use the `register` API in combination with `fastify-plugin`. To learn more, check out the [Plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins.md) documentation as well.
154
-
155
- <a name="dependencies"></a>
156
- #### Dependencies
157
- If your decorator depends on another decorator, you can easily declare the other decorator as a dependency. You just need to add an array of strings (representing the names of the decorators on which yours depends) as the third parameter:
158
- ```js
159
- fastify.decorate('utility', fn, ['greet', 'log'])
160
- ```
161
-
162
- If a dependency is not satisfied, `decorate` will throw an exception, but don't worry: the dependency check is executed before the server boots up, so it won't ever happen at runtime.
163
-
164
- <a name="has-decorator"></a>
165
- #### hasDecorator
166
- You can check for the presence of a decorator with the `hasDecorator` API:
167
- ```js
168
- fastify.hasDecorator('utility')
169
- ```
170
-
171
- <a name="has-request-decorator"></a>
172
- #### hasRequestDecorator
173
- You can check for the presence of a Request decorator with the `hasRequestDecorator` API:
174
- ```js
175
- fastify.hasRequestDecorator('utility')
176
- ```
177
-
178
- <a name="has-reply-decorator"></a>
179
- #### hasReplyDecorator
180
- You can check for the presence of a Reply decorator with the `hasReplyDecorator` API:
181
- ```js
182
- fastify.hasReplyDecorator('utility')
183
- ```
package/docs/Errors.md CHANGED
@@ -16,6 +16,11 @@ If routes are declared as `async` though - the error will safely be caught by th
16
16
  <a name="fastify-error-codes"></a>
17
17
  ### Fastify Error Codes
18
18
 
19
+ <a name="FST_ERR_BAD_URL"></a>
20
+ #### FST_ERR_BAD_URL
21
+
22
+ The router received an invalid url.
23
+
19
24
  <a name="FST_ERR_CTP_ALREADY_PRESENT"></a>
20
25
  #### FST_ERR_CTP_ALREADY_PRESENT
21
26
 
package/docs/Reply.md CHANGED
@@ -338,7 +338,7 @@ fastify.get('/async-await', options, async function (request, reply) {
338
338
  })
339
339
  ```
340
340
 
341
- Rejected promises default to a `500` HTTP status code. Reject the promise, or `throw` in an `async function`, with an object that has `statusCode` (or `status`) and `message` properties to modify the reply.
341
+ Rejected promises default to a `500` HTTP status code. Reject the promise, or `throw` in an `async function`, with an _Error_ object that has `statusCode` (or `status`) and `message` properties to modify the reply. Throwing plain objects is not supported, it must be an instance of _Error_, see:
342
342
 
343
343
  ```js
344
344
  fastify.get('/teapot', async function (request, reply) => {
package/docs/Server.md CHANGED
@@ -418,6 +418,30 @@ Set a default
418
418
  Note that this is needed to offer the graceful "close" experience when
419
419
  using http2. Node core defaults this to `0`.
420
420
 
421
+ <a name="framework-errors"></a>
422
+ ### `frameworkErrors`
423
+
424
+ + Default: `null`
425
+
426
+ Fastify provides default error handlers for the most common use cases.
427
+ Using this option it is possible to override one or more of those handlers with custom code.
428
+
429
+ *Note: Only `FST_ERR_BAD_URL` is implemented at the moment.*
430
+
431
+ ```js
432
+ const fastify = require('fastify')({
433
+ frameworkErrors: function (error, req, res) {
434
+ if (error instanceof FST_ERR_BAD_URL) {
435
+ res.code(400)
436
+ return res.send("Provided url is not valid")
437
+ } else {
438
+ res.send(err)
439
+ }
440
+ }
441
+ })
442
+ ```
443
+
444
+
421
445
  ## Instance
422
446
 
423
447
  ### Server Methods
package/fastify.d.ts CHANGED
@@ -8,6 +8,7 @@ import * as ajv from 'ajv'
8
8
  import * as http from 'http'
9
9
  import * as http2 from 'http2'
10
10
  import * as https from 'https'
11
+ import * as LightMyRequest from 'light-my-request'
11
12
 
12
13
  declare function fastify<
13
14
  HttpServer extends (http.Server | http2.Http2Server) = http.Server,
@@ -36,6 +37,10 @@ declare namespace fastify {
36
37
 
37
38
  type HTTPMethod = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS'
38
39
 
40
+ // // Keep the original name of the interfaces to avoid braking change
41
+ interface HTTPInjectOptions extends LightMyRequest.InjectOptions {}
42
+ interface HTTPInjectResponse extends LightMyRequest.Response {}
43
+
39
44
  interface ValidationResult {
40
45
  keyword: string;
41
46
  dataPath: string;
@@ -317,42 +322,6 @@ declare namespace fastify {
317
322
  logSerializers?: Object
318
323
  }
319
324
 
320
- /**
321
- * Fake http inject options
322
- */
323
- interface HTTPInjectOptions {
324
- url: string,
325
- method?: HTTPMethod,
326
- authority?: string,
327
- headers?: DefaultHeaders,
328
- query?: DefaultQuery,
329
- remoteAddress?: string,
330
- payload?: string | object | Buffer | NodeJS.ReadableStream
331
- simulate?: {
332
- end?: boolean,
333
- split?: boolean,
334
- error?: boolean,
335
- close?: boolean
336
- },
337
- validate?: boolean
338
- }
339
-
340
- /**
341
- * Fake http inject response
342
- */
343
- interface HTTPInjectResponse {
344
- raw: {
345
- req: NodeJS.ReadableStream,
346
- res: http.ServerResponse
347
- },
348
- headers: Record<string, string>,
349
- statusCode: number,
350
- statusMessage: string,
351
- payload: string,
352
- rawPayload: Buffer,
353
- trailers: object
354
- }
355
-
356
325
  /**
357
326
  * Server listen options
358
327
  */
package/fastify.js CHANGED
@@ -42,6 +42,11 @@ const { buildRouting, validateBodyLimitOption } = require('./lib/route')
42
42
  const build404 = require('./lib/fourOhFour')
43
43
  const getSecuredInitialConfig = require('./lib/initialConfigValidation')
44
44
  const { defaultInitOptions } = getSecuredInitialConfig
45
+ const {
46
+ codes: {
47
+ FST_ERR_BAD_URL
48
+ }
49
+ } = require('./lib/errors')
45
50
 
46
51
  function build (options) {
47
52
  // Options validations
@@ -73,6 +78,7 @@ function build (options) {
73
78
  customOptions: {},
74
79
  plugins: []
75
80
  }, options.ajv)
81
+ const frameworkErrors = options.frameworkErrors
76
82
 
77
83
  // Ajv options
78
84
  if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
@@ -431,6 +437,20 @@ function build (options) {
431
437
  }
432
438
 
433
439
  function onBadUrl (path, req, res) {
440
+ if (frameworkErrors) {
441
+ req.id = genReqId(req)
442
+ req.originalUrl = req.url
443
+ var childLogger = logger.child({ reqId: req.id })
444
+ if (modifyCoreObjects) {
445
+ req.log = res.log = childLogger
446
+ }
447
+
448
+ childLogger.info({ req }, 'incoming request')
449
+
450
+ const request = new Request(null, req, null, req.headers, childLogger)
451
+ const reply = new Reply(res, { onSend: [], onError: [] }, request, childLogger)
452
+ return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
453
+ }
434
454
  const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}`
435
455
  res.writeHead(400, {
436
456
  'Content-Type': 'application/json',
package/lib/decorate.js CHANGED
@@ -4,13 +4,15 @@
4
4
 
5
5
  const {
6
6
  kReply,
7
- kRequest
7
+ kRequest,
8
+ kState
8
9
  } = require('./symbols.js')
9
10
 
10
11
  const {
11
12
  codes: {
12
13
  FST_ERR_DEC_ALREADY_PRESENT,
13
- FST_ERR_DEC_MISSING_DEPENDENCY
14
+ FST_ERR_DEC_MISSING_DEPENDENCY,
15
+ FST_ERR_DEC_AFTER_START
14
16
  }
15
17
  } = require('./errors')
16
18
 
@@ -34,6 +36,7 @@ function decorate (instance, name, fn, dependencies) {
34
36
  }
35
37
 
36
38
  function decorateFastify (name, fn, dependencies) {
39
+ assertNotStarted(this, name)
37
40
  decorate(this, name, fn, dependencies)
38
41
  return this
39
42
  }
@@ -63,15 +66,23 @@ function checkDependencies (instance, deps) {
63
66
  }
64
67
 
65
68
  function decorateReply (name, fn, dependencies) {
69
+ assertNotStarted(this, name)
66
70
  decorate(this[kReply].prototype, name, fn, dependencies)
67
71
  return this
68
72
  }
69
73
 
70
74
  function decorateRequest (name, fn, dependencies) {
75
+ assertNotStarted(this, name)
71
76
  decorate(this[kRequest].prototype, name, fn, dependencies)
72
77
  return this
73
78
  }
74
79
 
80
+ function assertNotStarted (instance, name) {
81
+ if (instance[kState].started) {
82
+ throw new FST_ERR_DEC_AFTER_START(name)
83
+ }
84
+ }
85
+
75
86
  module.exports = {
76
87
  add: decorateFastify,
77
88
  exist: checkExistence,
package/lib/errors.js CHANGED
@@ -27,6 +27,7 @@ createError('FST_ERR_CTP_EMPTY_JSON_BODY', "Body cannot be empty when content-ty
27
27
  */
28
28
  createError('FST_ERR_DEC_ALREADY_PRESENT', "The decorator '%s' has already been added!")
29
29
  createError('FST_ERR_DEC_MISSING_DEPENDENCY', "The decorator is missing dependency '%s'.")
30
+ createError('FST_ERR_DEC_AFTER_START', "The decorator '%s' has been added after start!")
30
31
 
31
32
  /**
32
33
  * hooks
@@ -74,6 +75,11 @@ createError('FST_ERR_HTTP2_INVALID_VERSION', 'HTTP2 is available only from node
74
75
  */
75
76
  createError('FST_ERR_INIT_OPTS_INVALID', "Invalid initialization options: '%s'")
76
77
 
78
+ /**
79
+ * router
80
+ */
81
+ createError('FST_ERR_BAD_URL', "'%s' is not a valid url component", 400)
82
+
77
83
  function createError (code, message, statusCode = 500, Base = Error) {
78
84
  if (!code) throw new Error('Fastify error code must not be empty')
79
85
  if (!message) throw new Error('Fastify error message must not be empty')
package/lib/route.js CHANGED
@@ -218,29 +218,6 @@ function buildRouting (options) {
218
218
  this[kReplySerializerDefault]
219
219
  )
220
220
 
221
- // TODO this needs to be refactored so that buildSchemaCompiler is
222
- // not called for every single route. Creating a new one for every route
223
- // is going to be very expensive.
224
- if (opts.schema) {
225
- if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
226
- done(new FST_ERR_SCH_MISSING_COMPILER(opts.method, url))
227
- return
228
- }
229
-
230
- try {
231
- if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
232
- const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
233
- this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
234
- }
235
-
236
- buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
237
- } catch (error) {
238
- // bubble up the FastifyError instance
239
- done(error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
240
- return
241
- }
242
- }
243
-
244
221
  for (const hook of supportedHooks) {
245
222
  if (opts[hook]) {
246
223
  if (Array.isArray(opts[hook])) {
@@ -280,6 +257,24 @@ function buildRouting (options) {
280
257
  // Must store the 404 Context in 'preReady' because it is only guaranteed to
281
258
  // be available after all of the plugins and routes have been loaded.
282
259
  fourOhFour.setContext(this, context)
260
+
261
+ if (opts.schema) {
262
+ if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
263
+ throw new FST_ERR_SCH_MISSING_COMPILER(opts.method, url)
264
+ }
265
+
266
+ try {
267
+ if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
268
+ const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
269
+ this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
270
+ }
271
+
272
+ buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
273
+ } catch (error) {
274
+ // bubble up the FastifyError instance
275
+ throw (error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
276
+ }
277
+ }
283
278
  })
284
279
 
285
280
  done(notHandledErr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "typings": "fastify.d.ts",
@@ -92,28 +92,28 @@
92
92
  "node": ">=6"
93
93
  },
94
94
  "devDependencies": {
95
- "@types/node": "^12.12.17",
96
- "@typescript-eslint/eslint-plugin": "^2.11.0",
97
- "@typescript-eslint/parser": "^2.11.0",
95
+ "@types/node": "^12.12.30",
96
+ "@typescript-eslint/eslint-plugin": "^2.24.0",
97
+ "@typescript-eslint/parser": "^2.24.0",
98
98
  "JSONStream": "^1.3.5",
99
99
  "ajv-merge-patch": "^4.1.0",
100
100
  "ajv-pack": "^0.3.1",
101
101
  "autocannon": "^3.2.2",
102
102
  "branch-comparer": "^0.4.0",
103
- "concurrently": "^5.0.1",
103
+ "concurrently": "^5.1.0",
104
104
  "cors": "^2.8.5",
105
- "coveralls": "^3.0.9",
105
+ "coveralls": "^3.0.11",
106
106
  "dns-prefetch-control": "^0.2.0",
107
107
  "eslint": "^6.7.2",
108
- "eslint-import-resolver-node": "^0.3.2",
108
+ "eslint-import-resolver-node": "^0.3.3",
109
109
  "events.once": "^2.0.2",
110
110
  "fast-json-body": "^1.1.0",
111
- "fastify-plugin": "^1.5.0",
111
+ "fastify-plugin": "^1.6.1",
112
112
  "fluent-schema": "^0.10.0",
113
113
  "form-data": "^3.0.0",
114
114
  "frameguard": "^3.0.0",
115
115
  "h2url": "^0.2.0",
116
- "helmet": "^3.20.0",
116
+ "helmet": "^3.21.3",
117
117
  "hide-powered-by": "^1.0.0",
118
118
  "hsts": "^2.1.0",
119
119
  "http-errors": "^1.7.1",
@@ -130,28 +130,28 @@
130
130
  "simple-get": "^3.0.3",
131
131
  "snazzy": "^8.0.0",
132
132
  "split2": "^3.1.0",
133
- "standard": "^14.0.0",
133
+ "standard": "^14.3.3",
134
134
  "tap": "^12.5.2",
135
135
  "tap-mocha-reporter": "^3.0.7",
136
136
  "then-sleep": "^1.0.1",
137
- "typescript": "^3.7.3",
137
+ "typescript": "^3.8.3",
138
138
  "x-xss-protection": "^1.1.0",
139
- "yup": "^0.28.1"
139
+ "yup": "^0.28.3"
140
140
  },
141
141
  "dependencies": {
142
142
  "abstract-logging": "^2.0.0",
143
- "ajv": "^6.10.2",
144
- "avvio": "^6.3.0",
145
- "fast-json-stringify": "^1.16.0",
146
- "find-my-way": "^2.2.0",
143
+ "ajv": "^6.12.0",
144
+ "avvio": "^6.3.1",
145
+ "fast-json-stringify": "^1.18.0",
146
+ "find-my-way": "^2.2.2",
147
147
  "flatstr": "^1.0.12",
148
- "light-my-request": "^3.7.0",
148
+ "light-my-request": "^3.7.2",
149
149
  "middie": "^4.1.0",
150
- "pino": "^5.15.0",
151
- "proxy-addr": "^2.0.4",
152
- "readable-stream": "^3.1.1",
150
+ "pino": "^5.17.0",
151
+ "proxy-addr": "^2.0.6",
152
+ "readable-stream": "^3.6.0",
153
153
  "rfdc": "^1.1.2",
154
- "secure-json-parse": "^2.0.0",
154
+ "secure-json-parse": "^2.1.0",
155
155
  "tiny-lru": "^7.0.2"
156
156
  },
157
157
  "greenkeeper": {
@@ -708,3 +708,35 @@ test('after can access to a decorated instance and previous plugin decoration',
708
708
  t.equal(response.statusCode, 200)
709
709
  })
710
710
  })
711
+
712
+ test('decorate* should throw if called after ready', t => {
713
+ t.plan(3)
714
+ const fastify = Fastify()
715
+
716
+ fastify.get('/', (request, reply) => {
717
+ reply.send({
718
+ hello: 'world'
719
+ })
720
+ })
721
+
722
+ fastify.listen(0)
723
+ .then(() => {
724
+ try {
725
+ fastify.decorate('test', true)
726
+ } catch (e) {
727
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
728
+ }
729
+ try {
730
+ fastify.decorateRequest('test', true)
731
+ } catch (e) {
732
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
733
+ }
734
+ try {
735
+ fastify.decorateReply('test', true)
736
+ } catch (e) {
737
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
738
+ }
739
+ return fastify.close()
740
+ })
741
+ .catch(err => t.fail(err))
742
+ })
@@ -5,11 +5,19 @@
5
5
  const t = require('tap')
6
6
  const test = t.test
7
7
  const decorator = require('../../lib/decorate')
8
+ const {
9
+ kState
10
+ } = require('../../lib/symbols')
8
11
 
9
12
  test('decorate should add the given method to its instance', t => {
10
13
  t.plan(1)
11
14
  function build () {
12
15
  server.add = decorator.add
16
+ server[kState] = {
17
+ listening: false,
18
+ closing: false,
19
+ started: false
20
+ }
13
21
  return server
14
22
  function server () {}
15
23
  }
@@ -23,6 +31,11 @@ test('decorate is chainable', t => {
23
31
  t.plan(3)
24
32
  function build () {
25
33
  server.add = decorator.add
34
+ server[kState] = {
35
+ listening: false,
36
+ closing: false,
37
+ started: false
38
+ }
26
39
  return server
27
40
  function server () {}
28
41
  }
@@ -49,6 +62,11 @@ test('checkExistence should find the instance if not given', t => {
49
62
  function build () {
50
63
  server.add = decorator.add
51
64
  server.check = decorator.exist
65
+ server[kState] = {
66
+ listening: false,
67
+ closing: false,
68
+ started: false
69
+ }
52
70
  return server
53
71
  function server () {}
54
72
  }
@@ -83,6 +101,11 @@ test('decorate should internally call checkDependencies', t => {
83
101
  t.plan(2)
84
102
  function build () {
85
103
  server.add = decorator.add
104
+ server[kState] = {
105
+ listening: false,
106
+ closing: false,
107
+ started: false
108
+ }
86
109
  return server
87
110
  function server () {}
88
111
  }
@@ -101,7 +124,13 @@ test('decorate should internally call checkDependencies', t => {
101
124
  test('decorate should recognize getter/setter objects', t => {
102
125
  t.plan(6)
103
126
 
104
- const one = {}
127
+ const one = {
128
+ [kState]: {
129
+ listening: false,
130
+ closing: false,
131
+ started: false
132
+ }
133
+ }
105
134
  decorator.add.call(one, 'foo', {
106
135
  getter: () => this._a,
107
136
  setter: (val) => {
@@ -115,7 +144,13 @@ test('decorate should recognize getter/setter objects', t => {
115
144
  t.is(one.foo, 'a')
116
145
 
117
146
  // getter only
118
- const two = {}
147
+ const two = {
148
+ [kState]: {
149
+ listening: false,
150
+ closing: false,
151
+ started: false
152
+ }
153
+ }
119
154
  decorator.add.call(two, 'foo', {
120
155
  getter: () => 'a getter'
121
156
  })
@@ -7,7 +7,7 @@ const joi = require('joi')
7
7
  const Fastify = require('..')
8
8
 
9
9
  test('route', t => {
10
- t.plan(10)
10
+ t.plan(9)
11
11
  const test = t.test
12
12
  const fastify = Fastify()
13
13
 
@@ -65,29 +65,6 @@ test('route', t => {
65
65
  }
66
66
  })
67
67
 
68
- test('invalid schema - route', t => {
69
- t.plan(1)
70
- try {
71
- fastify.route({
72
- method: 'GET',
73
- url: '/invalid',
74
- schema: {
75
- querystring: {
76
- id: 'string'
77
- }
78
- },
79
- handler: function (req, reply) {
80
- reply.send({ hello: 'world' })
81
- }
82
- })
83
- fastify.after(err => {
84
- t.ok(err instanceof Error)
85
- })
86
- } catch (e) {
87
- t.fail()
88
- }
89
- })
90
-
91
68
  test('Multiple methods', t => {
92
69
  t.plan(1)
93
70
  try {
@@ -190,6 +167,29 @@ test('route', t => {
190
167
  })
191
168
  })
192
169
 
170
+ test('invalid schema - route', t => {
171
+ t.plan(3)
172
+
173
+ const fastify = Fastify()
174
+ fastify.route({
175
+ handler: () => {},
176
+ method: 'GET',
177
+ url: '/invalid',
178
+ schema: {
179
+ querystring: {
180
+ id: 'string'
181
+ }
182
+ }
183
+ })
184
+ fastify.after(err => {
185
+ t.notOk(err, 'the error is throw on preReady')
186
+ })
187
+ fastify.ready(err => {
188
+ t.is(err.code, 'FST_ERR_SCH_BUILD')
189
+ t.isLike(err.message, /Failed building the schema for GET: \/invalid/)
190
+ })
191
+ })
192
+
193
193
  test('path can be specified in place of uri', t => {
194
194
  t.plan(3)
195
195
  const fastify = Fastify()
@@ -3,6 +3,11 @@
3
3
  const test = require('tap').test
4
4
  const sget = require('simple-get')
5
5
  const Fastify = require('../')
6
+ const {
7
+ codes: {
8
+ FST_ERR_BAD_URL
9
+ }
10
+ } = require('../lib/errors')
6
11
 
7
12
  test('Should honor ignoreTrailingSlash option', t => {
8
13
  t.plan(4)
@@ -58,3 +63,32 @@ test('Should honor maxParamLength option', t => {
58
63
  t.strictEqual(res.statusCode, 404)
59
64
  })
60
65
  })
66
+
67
+ test('Should honor frameworkErrors option', t => {
68
+ t.plan(3)
69
+ const fastify = Fastify({
70
+ frameworkErrors: function (err, req, res) {
71
+ if (err instanceof FST_ERR_BAD_URL) {
72
+ t.ok(true)
73
+ } else {
74
+ t.fail()
75
+ }
76
+ res.send(err.message)
77
+ }
78
+ })
79
+
80
+ fastify.get('/test/:id', (req, res) => {
81
+ res.send('{ hello: \'world\' }')
82
+ })
83
+
84
+ fastify.inject(
85
+ {
86
+ method: 'GET',
87
+ url: '/test/%world'
88
+ },
89
+ (err, res) => {
90
+ t.error(err)
91
+ t.equals(res.body, 'FST_ERR_BAD_URL: \'%world\' is not a valid url component')
92
+ }
93
+ )
94
+ })
@@ -3,6 +3,7 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('..')
6
+ const fp = require('fastify-plugin')
6
7
 
7
8
  const ajvMergePatch = require('ajv-merge-patch')
8
9
  const AJV = require('ajv')
@@ -448,3 +449,32 @@ test('Should handle root $patch keywords in header', t => {
448
449
  })
449
450
  })
450
451
  })
452
+
453
+ test('Add schema order should not break the startup', t => {
454
+ t.plan(1)
455
+ const fastify = Fastify()
456
+
457
+ fastify.get('/', { schema: { random: 'options' } }, () => {})
458
+
459
+ fastify.register(fp((f, opts) => {
460
+ f.addSchema({
461
+ $id: 'https://example.com/bson/objectId',
462
+ type: 'string',
463
+ pattern: '\\b[0-9A-Fa-f]{24}\\b'
464
+ })
465
+ return Promise.resolve() // avoid async for node 6
466
+ }))
467
+
468
+ fastify.get('/:id', {
469
+ schema: {
470
+ params: {
471
+ type: 'object',
472
+ properties: {
473
+ id: { $ref: 'https://example.com/bson/objectId#' }
474
+ }
475
+ }
476
+ }
477
+ }, () => {})
478
+
479
+ fastify.ready(err => { t.error(err) })
480
+ })
@@ -6,6 +6,7 @@ import * as fastify from '../../fastify'
6
6
  import * as http from 'http'
7
7
  import * as http2 from 'http2'
8
8
  import { readFileSync } from 'fs'
9
+ import * as LightMyRequest from 'light-my-request'
9
10
 
10
11
  // were importing cors using require, which causes it to be an `any`. This is done because `cors` exports
11
12
  // itself as an express.RequestHandler which is not compatible with the fastify TypeScript types
@@ -609,11 +610,20 @@ server.listen({
609
610
  }).then((address: string) => console.log(address))
610
611
 
611
612
  // http injections
613
+ server.inject({ url: '/test' }, (err: Error, res: LightMyRequest.Response) => {
614
+ server.log.debug(err)
615
+ server.log.debug(res.payload)
616
+ })
617
+
618
+ // http injections with the fastify types
612
619
  server.inject({ url: '/test' }, (err: Error, res: fastify.HTTPInjectResponse) => {
613
620
  server.log.debug(err)
614
621
  server.log.debug(res.payload)
615
622
  })
616
623
 
624
+ server.inject({ url: '/testAgain' })
625
+ .then((res: LightMyRequest.Response) => console.log(res.payload))
626
+
617
627
  server.inject({ url: '/testAgain' })
618
628
  .then((res: fastify.HTTPInjectResponse) => console.log(res.payload))
619
629