fastify 2.10.0 → 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/LICENSE +1 -1
- package/README.md +52 -28
- package/build/build-validation.js +12 -2
- package/docs/Decorators.md +136 -75
- package/docs/Ecosystem.md +13 -2
- package/docs/Errors.md +10 -0
- package/docs/Fluent-Schema.md +1 -3
- package/docs/Hooks.md +75 -37
- package/docs/Plugins-Guide.md +19 -2
- package/docs/Plugins.md +27 -0
- package/docs/Recommendations.md +159 -0
- package/docs/Reply.md +19 -2
- package/docs/Routes.md +96 -6
- package/docs/Server.md +86 -4
- package/docs/Serverless.md +1 -1
- package/docs/Testing.md +18 -3
- package/docs/TypeScript.md +37 -14
- package/docs/Validation-and-Serialization.md +214 -13
- package/fastify.d.ts +33 -40
- package/fastify.js +65 -6
- package/lib/configValidator.js +129 -52
- package/lib/contentTypeParser.js +4 -4
- package/lib/context.js +2 -1
- package/lib/decorate.js +13 -2
- package/lib/errors.js +8 -0
- package/lib/hooks.js +3 -1
- package/lib/logger.js +2 -2
- package/lib/reply.js +10 -4
- package/lib/route.js +37 -27
- package/lib/schemas.js +10 -2
- package/lib/server.js +11 -0
- package/lib/symbols.js +3 -1
- package/lib/validation.js +11 -5
- package/package.json +33 -29
- package/test/404s.test.js +40 -0
- package/test/decorator.test.js +80 -0
- package/test/esm/esm.mjs +13 -0
- package/test/esm/index.test.js +19 -0
- package/test/esm/other.mjs +7 -0
- package/test/esm/plugin.mjs +7 -0
- package/test/fastify-instance.test.js +29 -0
- package/test/genReqId.test.js +1 -1
- package/test/hooks-async.js +19 -0
- package/test/hooks.test.js +139 -7
- package/test/http2/closing.js +53 -0
- package/test/http2/plain.js +15 -0
- package/test/input-validation.js +63 -0
- package/test/input-validation.test.js +161 -0
- package/test/internals/decorator.test.js +37 -2
- package/test/internals/initialConfig.test.js +6 -2
- package/test/internals/reply.test.js +27 -3
- package/test/logger.test.js +310 -1
- package/test/proto-poisoning.test.js +76 -0
- package/test/reply-error.test.js +30 -0
- package/test/route-hooks.test.js +23 -14
- package/test/route.test.js +62 -24
- package/test/router-options.test.js +34 -0
- package/test/schemas.test.js +156 -0
- package/test/shared-schemas.test.js +65 -0
- package/test/types/index.ts +75 -2
package/docs/Server.md
CHANGED
|
@@ -75,7 +75,7 @@ Defines the maximum payload, in bytes, the server is allowed to accept.
|
|
|
75
75
|
|
|
76
76
|
Defines what action the framework must take when parsing a JSON object
|
|
77
77
|
with `__proto__`. This functionality is provided by
|
|
78
|
-
[
|
|
78
|
+
[secure-json-parse](https://github.com/fastify/secure-json-parse).
|
|
79
79
|
See https://hueniverse.com/a-tale-of-prototype-poisoning-2610fa170061
|
|
80
80
|
for more details about prototype poisoning attacks.
|
|
81
81
|
|
|
@@ -83,6 +83,19 @@ Possible values are `'error'`, `'remove'` and `'ignore'`.
|
|
|
83
83
|
|
|
84
84
|
+ Default: `'error'`
|
|
85
85
|
|
|
86
|
+
<a name="factory-on-constructor-poisoning"></a>
|
|
87
|
+
### `onConstructorPoisoning`
|
|
88
|
+
|
|
89
|
+
Defines what action the framework must take when parsing a JSON object
|
|
90
|
+
with `constructor`. This functionality is provided by
|
|
91
|
+
[secure-json-parse](https://github.com/fastify/secure-json-parse).
|
|
92
|
+
See https://hueniverse.com/a-tale-of-prototype-poisoning-2610fa170061
|
|
93
|
+
for more details about prototype poisoning attacks.
|
|
94
|
+
|
|
95
|
+
Possible values are `'error'`, `'remove'` and `'ignore'`.
|
|
96
|
+
|
|
97
|
+
+ Default: `'ignore'`
|
|
98
|
+
|
|
86
99
|
<a name="factory-logger"></a>
|
|
87
100
|
### `logger`
|
|
88
101
|
|
|
@@ -156,7 +169,7 @@ fastify.addHook('onRequest', (req, reply, done) => {
|
|
|
156
169
|
})
|
|
157
170
|
|
|
158
171
|
fastify.addHook('onResponse', (req, reply, done) => {
|
|
159
|
-
req.log.info({ url: req.req.originalUrl, statusCode:
|
|
172
|
+
req.log.info({ url: req.req.originalUrl, statusCode: reply.res.statusCode }, 'request completed')
|
|
160
173
|
done()
|
|
161
174
|
})
|
|
162
175
|
```
|
|
@@ -223,7 +236,7 @@ Defines the label used for the request identifier when logging the request.
|
|
|
223
236
|
|
|
224
237
|
Function for generating the request id. It will receive the incoming request as a parameter.
|
|
225
238
|
|
|
226
|
-
+ Default: `value of 'request-id' if provided or monotonically increasing integers`
|
|
239
|
+
+ Default: `value of 'request-id' header if provided or monotonically increasing integers`
|
|
227
240
|
|
|
228
241
|
Especially in distributed systems, you may want to override the default id generation behaviour as shown below. For generating `UUID`s you may want to checkout [hyperid](https://github.com/mcollina/hyperid)
|
|
229
242
|
|
|
@@ -234,7 +247,7 @@ const fastify = require('fastify')({
|
|
|
234
247
|
})
|
|
235
248
|
```
|
|
236
249
|
|
|
237
|
-
**Note: genReqId will _not_ be called if the 'request-id'
|
|
250
|
+
**Note: genReqId will _not_ be called if the header set in <code>[requestIdHeader](#requestidheader)</code> is available (defaults to 'request-id').**
|
|
238
251
|
|
|
239
252
|
<a name="factory-trust-proxy"></a>
|
|
240
253
|
### `trustProxy`
|
|
@@ -360,6 +373,75 @@ If `false`, the server routes the incoming request as usual.
|
|
|
360
373
|
|
|
361
374
|
+ Default: `true`
|
|
362
375
|
|
|
376
|
+
<a name="factory-ajv"></a>
|
|
377
|
+
### `ajv`
|
|
378
|
+
|
|
379
|
+
Configure the ajv instance used by Fastify without providing a custom one.
|
|
380
|
+
|
|
381
|
+
+ Default:
|
|
382
|
+
|
|
383
|
+
```js
|
|
384
|
+
{
|
|
385
|
+
customOptions: {
|
|
386
|
+
removeAdditional: true,
|
|
387
|
+
useDefaults: true,
|
|
388
|
+
coerceTypes: true,
|
|
389
|
+
allErrors: true,
|
|
390
|
+
nullable: true
|
|
391
|
+
},
|
|
392
|
+
plugins: []
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
const fastify = require('fastify')({
|
|
398
|
+
ajv: {
|
|
399
|
+
customOptions: {
|
|
400
|
+
nullable: false // Refer to [ajv options](https://ajv.js.org/#options)
|
|
401
|
+
},
|
|
402
|
+
plugins: [
|
|
403
|
+
require('ajv-merge-patch')
|
|
404
|
+
[require('ajv-keywords'), 'instanceof'];
|
|
405
|
+
// Usage: [plugin, pluginOptions] - Plugin with options
|
|
406
|
+
// Usage: plugin - Plugin without options
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
<a name="http2-session-timeout"></a>
|
|
413
|
+
### `http2SessionTimeout`
|
|
414
|
+
|
|
415
|
+
Set a default
|
|
416
|
+
[timeout](https://nodejs.org/api/http2.html#http2_http2session_settimeout_msecs_callback) to every incoming http2 session. The session will be closed on the timeout. Default: `5000` ms.
|
|
417
|
+
|
|
418
|
+
Note that this is needed to offer the graceful "close" experience when
|
|
419
|
+
using http2. Node core defaults this to `0`.
|
|
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
|
+
|
|
363
445
|
## Instance
|
|
364
446
|
|
|
365
447
|
### Server Methods
|
package/docs/Serverless.md
CHANGED
|
@@ -98,7 +98,7 @@ Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless **
|
|
|
98
98
|
|
|
99
99
|
*Follow the steps below to deploy to Google Cloud Run if you are already familiar with gcloud or just follow their [quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*.
|
|
100
100
|
|
|
101
|
-
### Adjust
|
|
101
|
+
### Adjust Fastify server
|
|
102
102
|
|
|
103
103
|
In order for Fastify to properly listen for requests within the container, be sure to set the correct port and address:
|
|
104
104
|
|
package/docs/Testing.md
CHANGED
|
@@ -14,12 +14,26 @@ fastify.inject({
|
|
|
14
14
|
url: String,
|
|
15
15
|
query: Object,
|
|
16
16
|
payload: Object,
|
|
17
|
-
headers: Object
|
|
17
|
+
headers: Object,
|
|
18
|
+
cookies: Object
|
|
18
19
|
}, (error, response) => {
|
|
19
20
|
// your tests
|
|
20
21
|
})
|
|
21
22
|
```
|
|
22
23
|
|
|
24
|
+
`.inject` methods can also be chained by omitting the callback function:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
fastify
|
|
28
|
+
.inject()
|
|
29
|
+
.get('/')
|
|
30
|
+
.headers({ foo: 'bar' })
|
|
31
|
+
.query({ foo: 'bar' })
|
|
32
|
+
.end((err, res) => { // the .end call will trigger the request
|
|
33
|
+
console.log(res.payload)
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
23
37
|
or in the promisified version
|
|
24
38
|
|
|
25
39
|
```js
|
|
@@ -29,7 +43,8 @@ fastify
|
|
|
29
43
|
url: String,
|
|
30
44
|
query: Object,
|
|
31
45
|
payload: Object,
|
|
32
|
-
headers: Object
|
|
46
|
+
headers: Object,
|
|
47
|
+
cookies: Object
|
|
33
48
|
})
|
|
34
49
|
.then(response => {
|
|
35
50
|
// your tests
|
|
@@ -89,7 +104,7 @@ tap.test('GET `/` route', t => {
|
|
|
89
104
|
t.error(err)
|
|
90
105
|
t.strictEqual(response.statusCode, 200)
|
|
91
106
|
t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
|
|
92
|
-
t.deepEqual(
|
|
107
|
+
t.deepEqual(response.json(), { hello: 'world' })
|
|
93
108
|
})
|
|
94
109
|
})
|
|
95
110
|
```
|
package/docs/TypeScript.md
CHANGED
|
@@ -21,6 +21,7 @@ import { Server, IncomingMessage, ServerResponse } from 'http'
|
|
|
21
21
|
// Create a http server. We pass the relevant typings for our http version used.
|
|
22
22
|
// By passing types we get correctly typed access to the underlying http objects in routes.
|
|
23
23
|
// If using http2 we'd pass <http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>
|
|
24
|
+
// For https pass http2.Http2SecureServer or http.SecureServer instead of Server.
|
|
24
25
|
const server: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse> = fastify({})
|
|
25
26
|
|
|
26
27
|
const opts: fastify.RouteShorthandOptions = {
|
|
@@ -109,8 +110,8 @@ const opts: fastify.RouteShorthandOptions = {
|
|
|
109
110
|
server.get<Query, Params, Headers, Body>('/ping/:bar', opts, (request, reply) => {
|
|
110
111
|
console.log(request.query) // this is of type Query!
|
|
111
112
|
console.log(request.params) // this is of type Params!
|
|
112
|
-
console.log(request.body) // this is of type Body!
|
|
113
113
|
console.log(request.headers) // this is of type Headers!
|
|
114
|
+
console.log(request.body) // this is of type Body!
|
|
114
115
|
reply.code(200).send({ pong: 'it worked!' })
|
|
115
116
|
})
|
|
116
117
|
```
|
|
@@ -142,8 +143,8 @@ const opts: fastify.RouteShorthandOptions = {
|
|
|
142
143
|
server.get<fastify.DefaultQuery, Params, unknown>('/ping/:bar', opts, (request, reply) => {
|
|
143
144
|
console.log(request.query) // this is of type fastify.DefaultQuery!
|
|
144
145
|
console.log(request.params) // this is of type Params!
|
|
145
|
-
console.log(request.
|
|
146
|
-
console.log(request.
|
|
146
|
+
console.log(request.headers) // this is of type unknown!
|
|
147
|
+
console.log(request.body) // this is of type fastify.DefaultBody because typescript will use the default type value!
|
|
147
148
|
reply.code(200).send({ pong: 'it worked!' })
|
|
148
149
|
})
|
|
149
150
|
|
|
@@ -154,8 +155,8 @@ server.get<fastify.DefaultQuery, Params, unknown>('/ping/:bar', opts, (request,
|
|
|
154
155
|
server.get<unknown, Params, unknown, unknown>('/ping/:bar', opts, (request, reply) => {
|
|
155
156
|
console.log(request.query) // this is of type unknown!
|
|
156
157
|
console.log(request.params) // this is of type Params!
|
|
157
|
-
console.log(request.body) // this is of type unknown!
|
|
158
158
|
console.log(request.headers) // this is of type unknown!
|
|
159
|
+
console.log(request.body) // this is of type unknown!
|
|
159
160
|
reply.code(200).send({ pong: 'it worked!' })
|
|
160
161
|
})
|
|
161
162
|
```
|
|
@@ -189,8 +190,8 @@ application.
|
|
|
189
190
|
## Contributing
|
|
190
191
|
TypeScript related changes can be considered to fall into one of two categories:
|
|
191
192
|
|
|
192
|
-
* Core - The typings bundled with fastify
|
|
193
|
-
* Plugins - Fastify ecosystem plugins
|
|
193
|
+
* [`Core`](#core-types) - The typings bundled with fastify
|
|
194
|
+
* [`Plugins`](#plugin-types) - Fastify ecosystem plugins
|
|
194
195
|
|
|
195
196
|
Make sure to read our [`CONTRIBUTING.md`](https://github.com/fastify/fastify/blob/master/CONTRIBUTING.md) file before getting started to make sure things go smoothly!
|
|
196
197
|
|
|
@@ -215,29 +216,46 @@ Some types might not be available yet, so don't be shy about contributing.
|
|
|
215
216
|
### Authoring Plugin Types
|
|
216
217
|
Typings for many plugins that extend the `FastifyRequest`, `FastifyReply` or `FastifyInstance` objects can be achieved as shown below.
|
|
217
218
|
|
|
218
|
-
This code shows the typings for the `fastify-static` plugin.
|
|
219
|
+
This code shows the typings for the [`fastify-static`](https://github.com/fastify/fastify-static) plugin.
|
|
219
220
|
|
|
220
221
|
```ts
|
|
222
|
+
/// <reference types="node" />
|
|
223
|
+
|
|
221
224
|
// require fastify typings
|
|
222
|
-
import fastify
|
|
223
|
-
|
|
225
|
+
import * as fastify from 'fastify';
|
|
226
|
+
|
|
227
|
+
// require necessary http, http2, https typings
|
|
224
228
|
import { Server, IncomingMessage, ServerResponse } from "http";
|
|
229
|
+
import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse } from "http2";
|
|
230
|
+
import * as https from "https";
|
|
231
|
+
|
|
232
|
+
type HttpServer = Server | Http2Server | Http2SecureServer | https.Server;
|
|
233
|
+
type HttpRequest = IncomingMessage | Http2ServerRequest;
|
|
234
|
+
type HttpResponse = ServerResponse | Http2ServerResponse;
|
|
225
235
|
|
|
226
236
|
// extend fastify typings
|
|
227
237
|
declare module "fastify" {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
interface FastifyReply<HttpResponse> {
|
|
239
|
+
sendFile(filename: string): FastifyReply<HttpResponse>;
|
|
240
|
+
}
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
// declare plugin type using fastify.Plugin
|
|
234
|
-
declare
|
|
244
|
+
declare function fastifyStatic(): fastify.Plugin<
|
|
245
|
+
Server,
|
|
246
|
+
IncomingMessage,
|
|
247
|
+
ServerResponse,
|
|
248
|
+
{
|
|
235
249
|
root: string;
|
|
236
250
|
prefix?: string;
|
|
237
251
|
serve?: boolean;
|
|
238
252
|
decorateReply?: boolean;
|
|
239
253
|
schemaHide?: boolean;
|
|
240
254
|
setHeaders?: (...args: any[]) => void;
|
|
255
|
+
redirect?: boolean;
|
|
256
|
+
wildcard?: boolean | string;
|
|
257
|
+
|
|
258
|
+
// Passed on to `send`
|
|
241
259
|
acceptRanges?: boolean;
|
|
242
260
|
cacheControl?: boolean;
|
|
243
261
|
dotfiles?: boolean;
|
|
@@ -247,7 +265,12 @@ declare const fastifyStatic: fastify.Plugin<Server, IncomingMessage, ServerRespo
|
|
|
247
265
|
index?: string[];
|
|
248
266
|
lastModified?: boolean;
|
|
249
267
|
maxAge?: string | number;
|
|
250
|
-
}
|
|
268
|
+
}
|
|
269
|
+
>;
|
|
270
|
+
|
|
271
|
+
declare namespace fastifyStatic {
|
|
272
|
+
interface FastifyStaticOptions {}
|
|
273
|
+
}
|
|
251
274
|
|
|
252
275
|
// export plugin type
|
|
253
276
|
export = fastifyStatic;
|
|
@@ -50,11 +50,26 @@ const bodyJsonSchema = {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const queryStringJsonSchema = {
|
|
54
|
+
type: 'object',
|
|
55
|
+
required: ['name'],
|
|
56
|
+
properties: {
|
|
57
|
+
name: { type: 'string' },
|
|
58
|
+
excitement: { type: 'integer' }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* If you don't need required query strings,
|
|
63
|
+
* A short hand syntax is also there:
|
|
64
|
+
|
|
53
65
|
const queryStringJsonSchema = {
|
|
54
66
|
name: { type: 'string' },
|
|
55
67
|
excitement: { type: 'integer' }
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
|
|
58
73
|
const paramsJsonSchema = {
|
|
59
74
|
type: 'object',
|
|
60
75
|
properties: {
|
|
@@ -240,6 +255,77 @@ This example will returns:
|
|
|
240
255
|
| /sub | one, two |
|
|
241
256
|
| /deep | one, two, three |
|
|
242
257
|
|
|
258
|
+
<a name="ajv-plugins"></a>
|
|
259
|
+
#### Ajv Plugins
|
|
260
|
+
|
|
261
|
+
You can provide a list of plugins you want to use with Ajv:
|
|
262
|
+
|
|
263
|
+
> Refer to [`ajv options`](https://github.com/fastify/fastify/blob/master/docs/Server.md#factory-ajv) to check plugins format
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
const fastify = require('fastify')({
|
|
267
|
+
ajv: {
|
|
268
|
+
plugins: [
|
|
269
|
+
require('ajv-merge-patch')
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
fastify.route({
|
|
275
|
+
method: 'POST',
|
|
276
|
+
url: '/',
|
|
277
|
+
schema: {
|
|
278
|
+
body: {
|
|
279
|
+
$patch: {
|
|
280
|
+
source: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
q: {
|
|
284
|
+
type: 'string'
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
with: [
|
|
289
|
+
{
|
|
290
|
+
op: 'add',
|
|
291
|
+
path: '/properties/q',
|
|
292
|
+
value: { type: 'number' }
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
handler (req, reply) {
|
|
299
|
+
reply.send({ ok: 1 })
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
fastify.route({
|
|
304
|
+
method: 'POST',
|
|
305
|
+
url: '/',
|
|
306
|
+
schema: {
|
|
307
|
+
body: {
|
|
308
|
+
$merge: {
|
|
309
|
+
source: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
q: {
|
|
313
|
+
type: 'string'
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
with: {
|
|
318
|
+
required: ['q']
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
handler (req, reply) {
|
|
324
|
+
reply.send({ ok: 1 })
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
243
329
|
<a name="schema-compiler"></a>
|
|
244
330
|
#### Schema Compiler
|
|
245
331
|
|
|
@@ -257,7 +343,9 @@ Fastify's [baseline ajv configuration](https://github.com/epoberezkin/ajv#option
|
|
|
257
343
|
}
|
|
258
344
|
```
|
|
259
345
|
|
|
260
|
-
This baseline configuration
|
|
346
|
+
This baseline configuration can be modified by providing [`ajv.customOptions`](https://github.com/fastify/fastify/blob/master/docs/Server.md#factory-ajv) to your Fastify factory.
|
|
347
|
+
|
|
348
|
+
If you want to change or set additional config options, you will need to create your own instance and override the existing one like:
|
|
261
349
|
|
|
262
350
|
```js
|
|
263
351
|
const fastify = require('fastify')()
|
|
@@ -282,24 +370,136 @@ fastify.schemaCompiler = function (schema) { return ajv.compile(schema) })
|
|
|
282
370
|
```
|
|
283
371
|
_**Note:** If you use a custom instance of any validator (even Ajv), you have to add schemas to the validator instead of fastify, since fastify's default validator is no longer used, and fastify's `addSchema` method has no idea what validator you are using._
|
|
284
372
|
|
|
285
|
-
|
|
373
|
+
<a name="using-other-validation-libraries"></a>
|
|
374
|
+
#### Using other validation libraries
|
|
375
|
+
|
|
376
|
+
The `schemaCompiler` function makes it easy to substitute `ajv` with almost any Javascript validation library ([joi](https://github.com/hapijs/joi/), [yup](https://github.com/jquense/yup/), ...).
|
|
377
|
+
|
|
378
|
+
However, in order to make your chosen validation engine play well with Fastify's request/response pipeline, the function returned by your `schemaCompiler` function should return an object with either :
|
|
379
|
+
|
|
380
|
+
* in case of validation failure: an `error` property, filled with an instance of `Error` or a string that describes the validation error
|
|
381
|
+
* in case of validation success: an `value` property, filled with the coerced value that passed the validation
|
|
382
|
+
|
|
383
|
+
The examples below are therefore equivalent:
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
const joi = require('joi')
|
|
387
|
+
|
|
388
|
+
// Validation options to match ajv's baseline options used in Fastify
|
|
389
|
+
const joiOptions = {
|
|
390
|
+
abortEarly: false, // return all errors
|
|
391
|
+
convert: true, // change data type of data to match type keyword
|
|
392
|
+
allowUnknown : false, // remove additional properties
|
|
393
|
+
noDefaults: false
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const joiBodySchema = joi.object().keys({
|
|
397
|
+
age: joi.number().integer().required(),
|
|
398
|
+
sub: joi.object().keys({
|
|
399
|
+
name: joi.string().required()
|
|
400
|
+
}).required()
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const joiSchemaCompiler = schema => data => {
|
|
404
|
+
// joi `validate` function returns an object with an error property (if validation failed) and a value property (always present, coerced value if validation was successful)
|
|
405
|
+
const { error, value } = joiSchema.validate(data, joiOptions)
|
|
406
|
+
if (error) {
|
|
407
|
+
return { error }
|
|
408
|
+
} else {
|
|
409
|
+
return { value }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// or more simply...
|
|
414
|
+
const joiSchemaCompiler = schema => data => joiSchema.validate(data, joiOptions)
|
|
415
|
+
|
|
416
|
+
fastify.post('/the/url', {
|
|
417
|
+
schema: {
|
|
418
|
+
body: joiBodySchema
|
|
419
|
+
},
|
|
420
|
+
schemaCompiler: joiSchemaCompiler
|
|
421
|
+
}, handler)
|
|
422
|
+
```
|
|
286
423
|
|
|
287
424
|
```js
|
|
288
|
-
const
|
|
425
|
+
const yup = require('yup')
|
|
426
|
+
|
|
427
|
+
// Validation options to match ajv's baseline options used in Fastify
|
|
428
|
+
const yupOptions = {
|
|
429
|
+
strict: false,
|
|
430
|
+
abortEarly: false, // return all errors
|
|
431
|
+
stripUnknown: true, // remove additional properties
|
|
432
|
+
recursive: true
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const yupBodySchema = yup.object({
|
|
436
|
+
age: yup.number().integer().required(),
|
|
437
|
+
sub: yup.object().shape({
|
|
438
|
+
name: yup.string().required()
|
|
439
|
+
}).required()
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
const yupSchemaCompiler = schema => data => {
|
|
443
|
+
// with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed
|
|
444
|
+
try {
|
|
445
|
+
const result = schema.validateSync(data, yupOptions)
|
|
446
|
+
return { value: result }
|
|
447
|
+
} catch (e) {
|
|
448
|
+
return { error: e }
|
|
449
|
+
}
|
|
450
|
+
}
|
|
289
451
|
|
|
290
452
|
fastify.post('/the/url', {
|
|
291
453
|
schema: {
|
|
292
|
-
body:
|
|
293
|
-
hello: Joi.string().required()
|
|
294
|
-
}).required()
|
|
454
|
+
body: yupBodySchema
|
|
295
455
|
},
|
|
296
|
-
schemaCompiler:
|
|
456
|
+
schemaCompiler: yupSchemaCompiler
|
|
297
457
|
}, handler)
|
|
298
458
|
```
|
|
299
459
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
460
|
+
##### Validation messages with other validation libraries
|
|
461
|
+
|
|
462
|
+
Fastify's validation error messages are tightly coupled to the default validation engine: errors returned from `ajv` are eventually run through the `schemaErrorsText` function which is responsible for building human-friendly error messages. However, the `schemaErrorsText` function is written with `ajv` in mind : as a result, you may run into odd or incomplete error messages when using other validation librairies.
|
|
463
|
+
|
|
464
|
+
To circumvent this issue, you have 2 main options :
|
|
465
|
+
|
|
466
|
+
1. make sure your validation function (returned by your custom `schemaCompiler`) returns errors in the exact same structure and format as `ajv` (although this could prove to be difficult and tricky due to differences between validation engines)
|
|
467
|
+
2. or use a custom `errorHandler` to intercept and format your 'custom' validation errors
|
|
468
|
+
|
|
469
|
+
To help you in writing a custom `errorHandler`, Fastify adds 2 properties to all validation errors:
|
|
470
|
+
|
|
471
|
+
* validation: the content of the `error` property of the object returned by the validation function (returned by your custom `schemaCompiler`)
|
|
472
|
+
* validationContext: the 'context' (body, params, query, headers) where the validation error occurred
|
|
473
|
+
|
|
474
|
+
A very contrived example of such a custom `errorHandler` handling validation errors is shown below:
|
|
475
|
+
|
|
476
|
+
```js
|
|
477
|
+
const errorHandler = (error, request, reply) => {
|
|
478
|
+
|
|
479
|
+
const statusCode = error.statusCode
|
|
480
|
+
let response
|
|
481
|
+
|
|
482
|
+
const { validation, validationContext } = error
|
|
483
|
+
|
|
484
|
+
// check if we have a validation error
|
|
485
|
+
if (validation) {
|
|
486
|
+
response = {
|
|
487
|
+
message: `A validation error occured when validating the ${validationContext}...`, // validationContext will be 'body' or 'params' or 'headers' or 'query'
|
|
488
|
+
errors: validation // this is the result of your validation library...
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
response = {
|
|
492
|
+
message: 'An error occurred...'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// any additional work here, eg. log error
|
|
497
|
+
// ...
|
|
498
|
+
|
|
499
|
+
reply.status(statusCode).send(response)
|
|
500
|
+
|
|
501
|
+
}
|
|
502
|
+
```
|
|
303
503
|
|
|
304
504
|
<a name="schema-resolver"></a>
|
|
305
505
|
#### Schema Resolver
|
|
@@ -427,10 +627,10 @@ const schema = {
|
|
|
427
627
|
and fail to satisfy it, the route will immediately return a response with the following payload
|
|
428
628
|
|
|
429
629
|
```js
|
|
430
|
-
{
|
|
630
|
+
{
|
|
431
631
|
"statusCode": 400,
|
|
432
632
|
"error": "Bad Request",
|
|
433
|
-
"message": "body should have required property 'name'"
|
|
633
|
+
"message": "body should have required property 'name'"
|
|
434
634
|
}
|
|
435
635
|
```
|
|
436
636
|
|
|
@@ -452,7 +652,8 @@ You can also use [setErrorHandler](https://www.fastify.io/docs/latest/Server/#se
|
|
|
452
652
|
```js
|
|
453
653
|
fastify.setErrorHandler(function (error, request, reply) {
|
|
454
654
|
if (error.validation) {
|
|
455
|
-
|
|
655
|
+
// error.validationContext can be on of [body, params, querystring, headers]
|
|
656
|
+
reply.status(422).send(new Error(`validation failed of the ${error.validationContext}`))
|
|
456
657
|
}
|
|
457
658
|
})
|
|
458
659
|
```
|