fastify 4.8.0 → 4.9.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/docs/Guides/Ecosystem.md +11 -5
- package/docs/Reference/Reply.md +52 -23
- package/docs/Reference/Routes.md +2 -2
- package/docs/Reference/Server.md +1 -1
- package/docs/Reference/Validation-and-Serialization.md +39 -1
- package/fastify.js +1 -1
- package/lib/contentTypeParser.js +20 -19
- package/lib/error-handler.js +2 -0
- package/lib/error-serializer.js +1 -1
- package/lib/errors.js +9 -1
- package/lib/hooks.js +1 -1
- package/lib/reply.js +31 -12
- package/lib/route.js +16 -1
- package/lib/schema-controller.js +9 -5
- package/lib/schemas.js +51 -3
- package/lib/validation.js +22 -6
- package/package.json +2 -2
- package/test/content-parser.test.js +68 -2
- package/test/content-type.test.js +43 -0
- package/test/hooks.test.js +38 -0
- package/test/internals/hooks.test.js +3 -3
- package/test/internals/reply-serialize.test.js +120 -4
- package/test/schema-serialization.test.js +230 -0
- package/test/schema-special-usage.test.js +42 -0
- package/types/reply.d.ts +4 -4
- package/types/schema.d.ts +1 -0
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -155,6 +155,10 @@ section.
|
|
|
155
155
|
- [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server)
|
|
156
156
|
A plugin to easily create git server and make one/many Git repositories available
|
|
157
157
|
for clone/fetch/push through the standard `git` (over http) commands.
|
|
158
|
+
- [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id)
|
|
159
|
+
Fastify Request ID Plugin
|
|
160
|
+
- [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner)
|
|
161
|
+
Fastify typeorm QueryRunner plugin
|
|
158
162
|
- [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server)
|
|
159
163
|
Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your
|
|
160
164
|
Fastify API graceful.
|
|
@@ -235,7 +239,7 @@ section.
|
|
|
235
239
|
send HTTP requests via [axios](https://github.com/axios/axios).
|
|
236
240
|
- [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for
|
|
237
241
|
development servers that require Babel transformations of JavaScript sources.
|
|
238
|
-
- [`fastify-bcrypt`](https://github.com/
|
|
242
|
+
- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
|
|
239
243
|
generator & checker.
|
|
240
244
|
- [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your
|
|
241
245
|
routes to the console, so you definitely know which endpoints are available.
|
|
@@ -263,8 +267,8 @@ section.
|
|
|
263
267
|
Sequelize ORM.
|
|
264
268
|
- [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify
|
|
265
269
|
plugin to add CouchDB support via [nano](https://github.com/apache/nano).
|
|
266
|
-
- [`fastify-crud-generator`](https://github.com/
|
|
267
|
-
plugin to rapidly generate CRUD routes for any entity.
|
|
270
|
+
- [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator)
|
|
271
|
+
A plugin to rapidly generate CRUD routes for any entity.
|
|
268
272
|
- [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck)
|
|
269
273
|
Fastify plugin to add health route in your server that asserts custom
|
|
270
274
|
functions.
|
|
@@ -453,7 +457,7 @@ section.
|
|
|
453
457
|
Fastify plugin for memoize responses by expressive settings.
|
|
454
458
|
- [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
|
|
455
459
|
thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
|
|
456
|
-
- [`fastify-polyglot`](https://github.com/
|
|
460
|
+
- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to
|
|
457
461
|
handle i18n using
|
|
458
462
|
[node-polyglot](https://www.npmjs.com/package/node-polyglot).
|
|
459
463
|
- [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile)
|
|
@@ -547,7 +551,7 @@ section.
|
|
|
547
551
|
[Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that
|
|
548
552
|
removes the pain of managing authentication tokens, with built-in integration
|
|
549
553
|
for `fastify-auth`.
|
|
550
|
-
- [`fastify-totp`](https://github.com/
|
|
554
|
+
- [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle
|
|
551
555
|
TOTP (e.g. for 2FA).
|
|
552
556
|
- [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools)
|
|
553
557
|
Useful functions for Twitch Extension Backend Services (EBS).
|
|
@@ -606,6 +610,8 @@ section.
|
|
|
606
610
|
and updated Typeorm plugin for use with Fastify.
|
|
607
611
|
|
|
608
612
|
#### [Community Tools](#community-tools)
|
|
613
|
+
- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
|
|
614
|
+
Reusable workflows for use in the Fastify plugin
|
|
609
615
|
- [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration
|
|
610
616
|
generator by directory structure.
|
|
611
617
|
- [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to
|
package/docs/Reference/Reply.md
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
- [.callNotFound()](#callnotfound)
|
|
21
21
|
- [.getResponseTime()](#getresponsetime)
|
|
22
22
|
- [.type(contentType)](#typecontenttype)
|
|
23
|
-
- [.getSerializationFunction(schema | httpStatus)](#getserializationfunctionschema--httpstatus)
|
|
24
|
-
- [.compileSerializationSchema(schema, httpStatus)](#compileserializationschemaschema-httpstatus)
|
|
25
|
-
- [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinputdata-schema--httpstatus-httpstatus)
|
|
23
|
+
- [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
|
|
24
|
+
- [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
|
|
25
|
+
- [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus)
|
|
26
26
|
- [.serializer(func)](#serializerfunc)
|
|
27
27
|
- [.raw](#raw)
|
|
28
28
|
- [.sent](#sent)
|
|
@@ -63,16 +63,17 @@ object that exposes the following functions and properties:
|
|
|
63
63
|
- `.serialize(payload)` - Serializes the specified payload using the default
|
|
64
64
|
JSON serializer or using the custom serializer (if one is set) and returns the
|
|
65
65
|
serialized payload.
|
|
66
|
-
- `.getSerializationFunction(schema | httpStatus)` - Returns the serialization
|
|
66
|
+
- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization
|
|
67
67
|
function for the specified schema or http status, if any of either are set.
|
|
68
|
-
- `.compileSerializationSchema(schema, httpStatus)` - Compiles
|
|
69
|
-
schema and returns a serialization function using the default
|
|
70
|
-
`SerializerCompiler`. The optional `httpStatus` is forwarded
|
|
71
|
-
`SerializerCompiler` if provided, default to `undefined`.
|
|
72
|
-
- `.serializeInput(data, schema, [,httpStatus])` - Serializes
|
|
73
|
-
using the specified schema and returns the serialized payload.
|
|
74
|
-
If the optional `httpStatus`
|
|
75
|
-
function given for that
|
|
68
|
+
- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles
|
|
69
|
+
the specified schema and returns a serialization function using the default
|
|
70
|
+
(or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded
|
|
71
|
+
to the `SerializerCompiler` if provided, default to `undefined`.
|
|
72
|
+
- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes
|
|
73
|
+
the specified data using the specified schema and returns the serialized payload.
|
|
74
|
+
If the optional `httpStatus`, and `contentType` are provided, the function
|
|
75
|
+
will use the serializer function given for that specific content type and
|
|
76
|
+
HTTP Status Code. Default to `undefined`.
|
|
76
77
|
- `.serializer(function)` - Sets a custom serializer for the payload.
|
|
77
78
|
- `.send(payload)` - Sends the payload to the user, could be a plain text, a
|
|
78
79
|
buffer, JSON, stream, or an Error object.
|
|
@@ -339,12 +340,12 @@ reply.type('text/html')
|
|
|
339
340
|
If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
|
|
340
341
|
`utf-8` will be used as the charset by default.
|
|
341
342
|
|
|
342
|
-
### .getSerializationFunction(schema | httpStatus)
|
|
343
|
+
### .getSerializationFunction(schema | httpStatus, [contentType])
|
|
343
344
|
<a id="getserializationfunction"></a>
|
|
344
345
|
|
|
345
346
|
By calling this function using a provided `schema` or `httpStatus`,
|
|
346
|
-
it will return a `serialzation` function
|
|
347
|
-
serialize diverse inputs. It returns `undefined` if no
|
|
347
|
+
and the optional `contentType`, it will return a `serialzation` function
|
|
348
|
+
that can be used to serialize diverse inputs. It returns `undefined` if no
|
|
348
349
|
serialization function was found using either of the provided inputs.
|
|
349
350
|
|
|
350
351
|
This heavily depends of the `schema#responses` attached to the route, or
|
|
@@ -367,12 +368,18 @@ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
|
|
367
368
|
const serialize = reply
|
|
368
369
|
.getSerializationFunction(200)
|
|
369
370
|
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
|
371
|
+
|
|
372
|
+
// or
|
|
373
|
+
|
|
374
|
+
const serialize = reply
|
|
375
|
+
.getSerializationFunction(200, 'application/json')
|
|
376
|
+
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
|
370
377
|
```
|
|
371
378
|
|
|
372
|
-
See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema)
|
|
379
|
+
See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
|
|
373
380
|
for more information on how to compile serialization schemas.
|
|
374
381
|
|
|
375
|
-
### .compileSerializationSchema(schema, httpStatus)
|
|
382
|
+
### .compileSerializationSchema(schema, [httpStatus], [contentType])
|
|
376
383
|
<a id="compileserializationschema"></a>
|
|
377
384
|
|
|
378
385
|
This function will compile a serialization schema and
|
|
@@ -381,9 +388,9 @@ The function returned (a.k.a. _serialization function_) returned is compiled
|
|
|
381
388
|
by using the provided `SerializerCompiler`. Also this is cached by using
|
|
382
389
|
a `WeakMap` for reducing compilation calls.
|
|
383
390
|
|
|
384
|
-
The optional
|
|
385
|
-
the `SerializerCompiler`, so it can be used
|
|
386
|
-
function if a custom `SerializerCompiler` is used.
|
|
391
|
+
The optional paramaters `httpStatus` and `contentType`, if provided,
|
|
392
|
+
are forwarded directly to the `SerializerCompiler`, so it can be used
|
|
393
|
+
to compile the serialization function if a custom `SerializerCompiler` is used.
|
|
387
394
|
|
|
388
395
|
This heavily depends of the `schema#responses` attached to the route, or
|
|
389
396
|
the serialization functions compiled by using `compileSerializationSchema`.
|
|
@@ -412,6 +419,23 @@ const serialize = reply
|
|
|
412
419
|
}
|
|
413
420
|
}, 200)
|
|
414
421
|
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
|
422
|
+
|
|
423
|
+
// or
|
|
424
|
+
|
|
425
|
+
const serialize = reply
|
|
426
|
+
.compileSerializationSchema({
|
|
427
|
+
'3xx': {
|
|
428
|
+
content: {
|
|
429
|
+
'application/json': {
|
|
430
|
+
schema: {
|
|
431
|
+
name: { type: 'string' },
|
|
432
|
+
phone: { type: 'number' }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}, '3xx', 'application/json')
|
|
438
|
+
serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
|
|
415
439
|
```
|
|
416
440
|
|
|
417
441
|
Note that you should be careful when using this function, as it will cache
|
|
@@ -461,14 +485,14 @@ const newSerialize = reply.compileSerializationSchema(newSchema)
|
|
|
461
485
|
console.log(newSerialize === serialize) // false
|
|
462
486
|
```
|
|
463
487
|
|
|
464
|
-
### .serializeInput(data, [schema | httpStatus], [httpStatus])
|
|
488
|
+
### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
|
|
465
489
|
<a id="serializeinput"></a>
|
|
466
490
|
|
|
467
491
|
This function will serialize the input data based on the provided schema,
|
|
468
492
|
or http status code. If both provided, the `httpStatus` will take presedence.
|
|
469
493
|
|
|
470
494
|
If there is not a serialization function for a given `schema`, a new serialization
|
|
471
|
-
function will be compiled forwarding the `httpStatus` if provided.
|
|
495
|
+
function will be compiled forwarding the `httpStatus`, and the `contentType` if provided.
|
|
472
496
|
|
|
473
497
|
```js
|
|
474
498
|
reply
|
|
@@ -497,9 +521,14 @@ reply
|
|
|
497
521
|
|
|
498
522
|
reply
|
|
499
523
|
.serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
|
|
524
|
+
|
|
525
|
+
// or
|
|
526
|
+
|
|
527
|
+
reply
|
|
528
|
+
.serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
|
|
500
529
|
```
|
|
501
530
|
|
|
502
|
-
See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema)
|
|
531
|
+
See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
|
|
503
532
|
for more information on how to compile serialization schemas.
|
|
504
533
|
|
|
505
534
|
### .serializer(func)
|
package/docs/Reference/Routes.md
CHANGED
|
@@ -94,8 +94,8 @@ fastify.route(options)
|
|
|
94
94
|
schemas for request validations. See the [Validation and
|
|
95
95
|
Serialization](./Validation-and-Serialization.md#schema-validator)
|
|
96
96
|
documentation.
|
|
97
|
-
* `serializerCompiler({ { schema, method, url, httpStatus } })`:
|
|
98
|
-
builds schemas for response serialization. See the [Validation and
|
|
97
|
+
* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:
|
|
98
|
+
function that builds schemas for response serialization. See the [Validation and
|
|
99
99
|
Serialization](./Validation-and-Serialization.md#schema-serializer)
|
|
100
100
|
documentation.
|
|
101
101
|
* `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from
|
package/docs/Reference/Server.md
CHANGED
|
@@ -1370,7 +1370,7 @@ const fastify = Fastify({
|
|
|
1370
1370
|
buildSerializer: function factory (externalSchemas, serializerOptsServerOption) {
|
|
1371
1371
|
// This factory function must return a schema serializer compiler.
|
|
1372
1372
|
// See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer) for details.
|
|
1373
|
-
return function serializerCompiler ({ schema, method, url, httpStatus }) {
|
|
1373
|
+
return function serializerCompiler ({ schema, method, url, httpStatus, contentType }) {
|
|
1374
1374
|
return data => JSON.stringify(data)
|
|
1375
1375
|
}
|
|
1376
1376
|
}
|
|
@@ -611,6 +611,44 @@ const schema = {
|
|
|
611
611
|
|
|
612
612
|
fastify.post('/the/url', { schema }, handler)
|
|
613
613
|
```
|
|
614
|
+
You can even have a specific response schema for different content types.
|
|
615
|
+
For example:
|
|
616
|
+
```js
|
|
617
|
+
const schema = {
|
|
618
|
+
response: {
|
|
619
|
+
200: {
|
|
620
|
+
description: 'Response schema that support different content types'
|
|
621
|
+
content: {
|
|
622
|
+
'application/json': {
|
|
623
|
+
schema: {
|
|
624
|
+
name: { type: 'string' },
|
|
625
|
+
image: { type: 'string' },
|
|
626
|
+
address: { type: 'string' }
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
'application/vnd.v1+json': {
|
|
630
|
+
schema: {
|
|
631
|
+
type: 'array',
|
|
632
|
+
items: { $ref: 'test' }
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
'3xx': {
|
|
638
|
+
content: {
|
|
639
|
+
'application/vnd.v2+json': {
|
|
640
|
+
schema: {
|
|
641
|
+
fullName: { type: 'string' },
|
|
642
|
+
phone: { type: 'string' }
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
fastify.post('/url', { schema }, handler)
|
|
651
|
+
```
|
|
614
652
|
|
|
615
653
|
#### Serializer Compiler
|
|
616
654
|
<a id="schema-serializer"></a>
|
|
@@ -621,7 +659,7 @@ change the default serialization method by providing a function to serialize
|
|
|
621
659
|
every route where you do.
|
|
622
660
|
|
|
623
661
|
```js
|
|
624
|
-
fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
|
|
662
|
+
fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
|
|
625
663
|
return data => JSON.stringify(data)
|
|
626
664
|
})
|
|
627
665
|
|
package/fastify.js
CHANGED
package/lib/contentTypeParser.js
CHANGED
|
@@ -29,9 +29,10 @@ const {
|
|
|
29
29
|
|
|
30
30
|
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
|
|
31
31
|
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
|
|
32
|
-
|
|
33
|
-
this.customParsers
|
|
34
|
-
this.customParsers
|
|
32
|
+
// using a map instead of a plain object to avoid prototype hijack attacks
|
|
33
|
+
this.customParsers = new Map()
|
|
34
|
+
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
|
|
35
|
+
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
|
|
35
36
|
this.parserList = ['application/json', 'text/plain']
|
|
36
37
|
this.parserRegExpList = []
|
|
37
38
|
this.cache = lru(100)
|
|
@@ -62,35 +63,35 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
if (contentTypeIsString && contentType === '*') {
|
|
65
|
-
this.customParsers
|
|
66
|
+
this.customParsers.set('', parser)
|
|
66
67
|
} else {
|
|
67
68
|
if (contentTypeIsString) {
|
|
68
69
|
this.parserList.unshift(contentType)
|
|
69
70
|
} else {
|
|
70
71
|
this.parserRegExpList.unshift(contentType)
|
|
71
72
|
}
|
|
72
|
-
this.customParsers
|
|
73
|
+
this.customParsers.set(contentType.toString(), parser)
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
77
|
-
return contentType
|
|
78
|
+
return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
ContentTypeParser.prototype.existingParser = function (contentType) {
|
|
81
|
-
if (contentType === 'application/json') {
|
|
82
|
-
return this.customParsers
|
|
82
|
+
if (contentType === 'application/json' && this.customParsers.has(contentType)) {
|
|
83
|
+
return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
|
|
83
84
|
}
|
|
84
|
-
if (contentType === 'text/plain') {
|
|
85
|
-
return this.customParsers
|
|
85
|
+
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
|
86
|
+
return this.customParsers.get(contentType).fn !== defaultPlainTextParser
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
return
|
|
89
|
+
return this.hasParser(contentType)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
ContentTypeParser.prototype.getParser = function (contentType) {
|
|
92
|
-
if (
|
|
93
|
-
return this.customParsers
|
|
93
|
+
if (this.hasParser(contentType)) {
|
|
94
|
+
return this.customParsers.get(contentType)
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
if (this.cache.has(contentType)) {
|
|
@@ -101,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
101
102
|
for (var i = 0; i !== this.parserList.length; ++i) {
|
|
102
103
|
const parserName = this.parserList[i]
|
|
103
104
|
if (contentType.indexOf(parserName) !== -1) {
|
|
104
|
-
const parser = this.customParsers
|
|
105
|
+
const parser = this.customParsers.get(parserName)
|
|
105
106
|
this.cache.set(contentType, parser)
|
|
106
107
|
return parser
|
|
107
108
|
}
|
|
@@ -111,17 +112,17 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
111
112
|
for (var j = 0; j !== this.parserRegExpList.length; ++j) {
|
|
112
113
|
const parserRegExp = this.parserRegExpList[j]
|
|
113
114
|
if (parserRegExp.test(contentType)) {
|
|
114
|
-
const parser = this.customParsers
|
|
115
|
+
const parser = this.customParsers.get(parserRegExp.toString())
|
|
115
116
|
this.cache.set(contentType, parser)
|
|
116
117
|
return parser
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
return this.customParsers
|
|
121
|
+
return this.customParsers.get('')
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
ContentTypeParser.prototype.removeAll = function () {
|
|
124
|
-
this.customParsers =
|
|
125
|
+
this.customParsers = new Map()
|
|
125
126
|
this.parserRegExpList = []
|
|
126
127
|
this.parserList = []
|
|
127
128
|
this.cache = lru(100)
|
|
@@ -130,7 +131,7 @@ ContentTypeParser.prototype.removeAll = function () {
|
|
|
130
131
|
ContentTypeParser.prototype.remove = function (contentType) {
|
|
131
132
|
if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
this.customParsers.delete(contentType.toString())
|
|
134
135
|
|
|
135
136
|
const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
|
|
136
137
|
|
|
@@ -289,7 +290,7 @@ function Parser (asString, asBuffer, bodyLimit, fn) {
|
|
|
289
290
|
function buildContentTypeParser (c) {
|
|
290
291
|
const contentTypeParser = new ContentTypeParser()
|
|
291
292
|
contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
|
|
292
|
-
|
|
293
|
+
contentTypeParser.customParsers = new Map(c.customParsers.entries())
|
|
293
294
|
contentTypeParser.parserList = c.parserList.slice()
|
|
294
295
|
return contentTypeParser
|
|
295
296
|
}
|
package/lib/error-handler.js
CHANGED
|
@@ -49,6 +49,8 @@ function handleError (reply, error, cb) {
|
|
|
49
49
|
// In case the error handler throws, we set the next errorHandler so we can error again
|
|
50
50
|
reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)
|
|
51
51
|
|
|
52
|
+
// we need to remove content-type to allow content-type guessing for serialization
|
|
53
|
+
delete reply[kReplyHeaders]['content-type']
|
|
52
54
|
delete reply[kReplyHeaders]['content-length']
|
|
53
55
|
|
|
54
56
|
const func = errorHandler.func
|
package/lib/error-serializer.js
CHANGED
package/lib/errors.js
CHANGED
|
@@ -97,7 +97,7 @@ const codes = {
|
|
|
97
97
|
),
|
|
98
98
|
FST_ERR_HOOK_INVALID_HANDLER: createError(
|
|
99
99
|
'FST_ERR_HOOK_INVALID_HANDLER',
|
|
100
|
-
'
|
|
100
|
+
'%s hook should be a function, instead got %s',
|
|
101
101
|
500,
|
|
102
102
|
TypeError
|
|
103
103
|
),
|
|
@@ -165,6 +165,10 @@ const codes = {
|
|
|
165
165
|
'FST_ERR_MISSING_SERIALIZATION_FN',
|
|
166
166
|
'Missing serialization function. Key "%s"'
|
|
167
167
|
),
|
|
168
|
+
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN: createError(
|
|
169
|
+
'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN',
|
|
170
|
+
'Missing serialization function. Key "%s:%s"'
|
|
171
|
+
),
|
|
168
172
|
FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError(
|
|
169
173
|
'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION',
|
|
170
174
|
'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.'
|
|
@@ -181,6 +185,10 @@ const codes = {
|
|
|
181
185
|
'FST_ERR_SCH_ALREADY_PRESENT',
|
|
182
186
|
"Schema with id '%s' already declared!"
|
|
183
187
|
),
|
|
188
|
+
FST_ERR_SCH_CONTENT_MISSING_SCHEMA: createError(
|
|
189
|
+
'FST_ERR_SCH_CONTENT_MISSING_SCHEMA',
|
|
190
|
+
"Schema is missing for the content type '%s'"
|
|
191
|
+
),
|
|
184
192
|
FST_ERR_SCH_DUPLICATE: createError(
|
|
185
193
|
'FST_ERR_SCH_DUPLICATE',
|
|
186
194
|
"Schema with '%s' already present!"
|
package/lib/hooks.js
CHANGED
|
@@ -49,10 +49,10 @@ function Hooks () {
|
|
|
49
49
|
|
|
50
50
|
Hooks.prototype.validate = function (hook, fn) {
|
|
51
51
|
if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
|
|
52
|
-
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER()
|
|
53
52
|
if (supportedHooks.indexOf(hook) === -1) {
|
|
54
53
|
throw new Error(`${hook} hook not supported!`)
|
|
55
54
|
}
|
|
55
|
+
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof fn)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
Hooks.prototype.add = function (hook, fn) {
|
package/lib/reply.js
CHANGED
|
@@ -44,7 +44,8 @@ const {
|
|
|
44
44
|
FST_ERR_BAD_STATUS_CODE,
|
|
45
45
|
FST_ERR_BAD_TRAILER_NAME,
|
|
46
46
|
FST_ERR_BAD_TRAILER_VALUE,
|
|
47
|
-
FST_ERR_MISSING_SERIALIZATION_FN
|
|
47
|
+
FST_ERR_MISSING_SERIALIZATION_FN,
|
|
48
|
+
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
|
|
48
49
|
} = require('./errors')
|
|
49
50
|
const warning = require('./warnings')
|
|
50
51
|
|
|
@@ -312,11 +313,15 @@ Reply.prototype.code = function (code) {
|
|
|
312
313
|
|
|
313
314
|
Reply.prototype.status = Reply.prototype.code
|
|
314
315
|
|
|
315
|
-
Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
|
|
316
|
+
Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType) {
|
|
316
317
|
let serialize
|
|
317
318
|
|
|
318
319
|
if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
|
|
319
|
-
|
|
320
|
+
if (typeof contentType === 'string') {
|
|
321
|
+
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]?.[contentType]
|
|
322
|
+
} else {
|
|
323
|
+
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
|
|
324
|
+
}
|
|
320
325
|
} else if (typeof schemaOrStatus === 'object') {
|
|
321
326
|
serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus)
|
|
322
327
|
}
|
|
@@ -324,7 +329,7 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
|
|
|
324
329
|
return serialize
|
|
325
330
|
}
|
|
326
331
|
|
|
327
|
-
Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null) {
|
|
332
|
+
Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null, contentType = null) {
|
|
328
333
|
const { request } = this
|
|
329
334
|
const { method, url } = request
|
|
330
335
|
|
|
@@ -346,7 +351,8 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
|
|
|
346
351
|
schema,
|
|
347
352
|
method,
|
|
348
353
|
url,
|
|
349
|
-
httpStatus
|
|
354
|
+
httpStatus,
|
|
355
|
+
contentType
|
|
350
356
|
})
|
|
351
357
|
|
|
352
358
|
// We create a WeakMap to compile the schema only once
|
|
@@ -363,22 +369,34 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
|
|
|
363
369
|
return serializeFn
|
|
364
370
|
}
|
|
365
371
|
|
|
366
|
-
Reply.prototype.serializeInput = function (input, schema, httpStatus) {
|
|
372
|
+
Reply.prototype.serializeInput = function (input, schema, httpStatus, contentType) {
|
|
373
|
+
const possibleContentType = httpStatus
|
|
367
374
|
let serialize
|
|
368
375
|
httpStatus = typeof schema === 'string' || typeof schema === 'number'
|
|
369
376
|
? schema
|
|
370
377
|
: httpStatus
|
|
371
378
|
|
|
379
|
+
contentType = httpStatus && possibleContentType !== httpStatus
|
|
380
|
+
? possibleContentType
|
|
381
|
+
: contentType
|
|
382
|
+
|
|
372
383
|
if (httpStatus != null) {
|
|
373
|
-
|
|
384
|
+
if (contentType != null) {
|
|
385
|
+
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]?.[contentType]
|
|
386
|
+
} else {
|
|
387
|
+
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
|
|
388
|
+
}
|
|
374
389
|
|
|
375
|
-
if (serialize == null)
|
|
390
|
+
if (serialize == null) {
|
|
391
|
+
if (contentType) throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(httpStatus, contentType)
|
|
392
|
+
throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
|
|
393
|
+
}
|
|
376
394
|
} else {
|
|
377
395
|
// Check if serialize function already compiled
|
|
378
396
|
if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) {
|
|
379
397
|
serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema)
|
|
380
398
|
} else {
|
|
381
|
-
serialize = this.compileSerializationSchema(schema, httpStatus)
|
|
399
|
+
serialize = this.compileSerializationSchema(schema, httpStatus, contentType)
|
|
382
400
|
}
|
|
383
401
|
}
|
|
384
402
|
|
|
@@ -483,7 +501,7 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
483
501
|
} else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) {
|
|
484
502
|
payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode)
|
|
485
503
|
} else {
|
|
486
|
-
payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode)
|
|
504
|
+
payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type'])
|
|
487
505
|
}
|
|
488
506
|
} catch (e) {
|
|
489
507
|
wrapSeralizationError(e, reply)
|
|
@@ -797,10 +815,11 @@ function notFound (reply) {
|
|
|
797
815
|
* @param {object} context the request context
|
|
798
816
|
* @param {object} data the JSON payload to serialize
|
|
799
817
|
* @param {number} statusCode the http status code
|
|
818
|
+
* @param {string} contentType the reply content type
|
|
800
819
|
* @returns {string} the serialized payload
|
|
801
820
|
*/
|
|
802
|
-
function serialize (context, data, statusCode) {
|
|
803
|
-
const fnSerialize = getSchemaSerializer(context, statusCode)
|
|
821
|
+
function serialize (context, data, statusCode, contentType) {
|
|
822
|
+
const fnSerialize = getSchemaSerializer(context, statusCode, contentType)
|
|
804
823
|
if (fnSerialize) {
|
|
805
824
|
return fnSerialize(data)
|
|
806
825
|
}
|
package/lib/route.js
CHANGED
|
@@ -20,7 +20,8 @@ const {
|
|
|
20
20
|
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
|
|
21
21
|
FST_ERR_DUPLICATED_ROUTE,
|
|
22
22
|
FST_ERR_INVALID_URL,
|
|
23
|
-
FST_ERR_SEND_UNDEFINED_ERR
|
|
23
|
+
FST_ERR_SEND_UNDEFINED_ERR,
|
|
24
|
+
FST_ERR_HOOK_INVALID_HANDLER
|
|
24
25
|
} = require('./errors')
|
|
25
26
|
|
|
26
27
|
const {
|
|
@@ -243,6 +244,20 @@ function buildRouting (options) {
|
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
for (const hook of lifecycleHooks) {
|
|
248
|
+
if (opts && hook in opts) {
|
|
249
|
+
if (Array.isArray(opts[hook])) {
|
|
250
|
+
for (const func of opts[hook]) {
|
|
251
|
+
if (typeof func !== 'function') {
|
|
252
|
+
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof func)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} else if (typeof opts[hook] !== 'function') {
|
|
256
|
+
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof opts[hook])
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
246
261
|
const constraints = opts.constraints || {}
|
|
247
262
|
const config = {
|
|
248
263
|
...opts.config,
|
package/lib/schema-controller.js
CHANGED
|
@@ -15,12 +15,16 @@ function buildSchemaController (parentSchemaCtrl, opts) {
|
|
|
15
15
|
return new SchemaController(parentSchemaCtrl, opts)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
buildValidator:
|
|
20
|
-
buildSerializer:
|
|
18
|
+
const compilersFactory = Object.assign({
|
|
19
|
+
buildValidator: null,
|
|
20
|
+
buildSerializer: null
|
|
21
|
+
}, opts?.compilersFactory)
|
|
22
|
+
|
|
23
|
+
if (!compilersFactory.buildValidator) {
|
|
24
|
+
compilersFactory.buildValidator = ValidatorSelector()
|
|
21
25
|
}
|
|
22
|
-
if (
|
|
23
|
-
compilersFactory =
|
|
26
|
+
if (!compilersFactory.buildSerializer) {
|
|
27
|
+
compilersFactory.buildSerializer = SerializerSelector()
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
const option = {
|