fastify 3.20.2 → 3.21.3
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/.taprc +1 -0
- package/README.md +30 -1
- package/docs/ContentTypeParser.md +2 -2
- package/docs/Ecosystem.md +3 -1
- package/docs/Getting-Started.md +76 -3
- package/docs/Guides/Index.md +6 -0
- package/docs/Hooks.md +42 -0
- package/docs/Lifecycle.md +1 -1
- package/docs/Recommendations.md +80 -1
- package/docs/Reply.md +2 -2
- package/docs/Request.md +4 -4
- package/docs/Server.md +82 -16
- package/docs/Validation-and-Serialization.md +47 -4
- package/fastify.js +11 -0
- package/lib/contentTypeParser.js +3 -5
- package/lib/decorate.js +33 -9
- package/lib/reply.js +17 -1
- package/lib/request.js +8 -0
- package/lib/route.js +4 -3
- package/lib/schema-controller.js +12 -7
- package/package.json +6 -4
- package/test/bundler/README.md +29 -0
- package/test/bundler/webpack/bundler-test.js +24 -0
- package/test/bundler/webpack/package.json +11 -0
- package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
- package/test/bundler/webpack/src/index.js +9 -0
- package/test/bundler/webpack/webpack.config.js +13 -0
- package/test/custom-parser.test.js +111 -0
- package/test/decorator.test.js +27 -0
- package/test/diagnostics-channel.test.js +61 -0
- package/test/internals/decorator.test.js +1 -1
- package/test/reply-error.test.js +71 -0
- package/test/route.test.js +22 -0
- package/test/same-shape.test.js +124 -0
- package/test/schema-feature.test.js +1 -1
- package/test/schema-serialization.test.js +41 -0
- package/test/schema-special-usage.test.js +43 -2
- package/test/schema-validation.test.js +100 -0
- package/test/stream.test.js +37 -1
- package/test/types/instance.test-d.ts +10 -2
- package/test/types/plugin.test-d.ts +1 -1
- package/test/types/request.test-d.ts +4 -1
- package/types/.eslintrc.json +2 -1
- package/types/instance.d.ts +4 -3
- package/types/request.d.ts +1 -1
- package/types/schema.d.ts +1 -1
|
@@ -14,7 +14,7 @@ Fastify uses a schema-based approach, and even if it is not mandatory we recomme
|
|
|
14
14
|
|
|
15
15
|
### Core concepts
|
|
16
16
|
The validation and the serialization tasks are processed by two different, and customizable, actors:
|
|
17
|
-
- [Ajv](https://www.npmjs.com/package/ajv) for the validation of a request
|
|
17
|
+
- [Ajv v6](https://www.npmjs.com/package/ajv/v/6.12.6) for the validation of a request
|
|
18
18
|
- [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for the serialization of a response's body
|
|
19
19
|
|
|
20
20
|
These two separate entities share only the JSON schemas added to Fastify's instance through `.addSchema(schema)`.
|
|
@@ -120,7 +120,7 @@ fastify.register((instance, opts, done) => {
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
### Validation
|
|
123
|
-
The route validation internally relies upon [Ajv](https://www.npmjs.com/package/ajv)
|
|
123
|
+
The route validation internally relies upon [Ajv v6](https://www.npmjs.com/package/ajv/v/6.12.6) which is a high-performance JSON Schema validator.
|
|
124
124
|
Validating the input is very easy: just add the fields that you need inside the route schema, and you are done!
|
|
125
125
|
|
|
126
126
|
The supported validations are:
|
|
@@ -131,6 +131,8 @@ The supported validations are:
|
|
|
131
131
|
|
|
132
132
|
All the validations can be a complete JSON Schema object (with a `type` property of `'object'` and a `'properties'` object containing parameters) or a simpler variation in which the `type` and `properties` attributes are forgone and the parameters are listed at the top level (see the example below).
|
|
133
133
|
|
|
134
|
+
> ℹ If you need to use the lastest version of Ajv (v8) you should read how to do it in the [`schemaController`](Server.md#schema-controller) section. It is explained the easier way to avoid to implement a custom validator.
|
|
135
|
+
|
|
134
136
|
Example:
|
|
135
137
|
```js
|
|
136
138
|
const bodyJsonSchema = {
|
|
@@ -232,7 +234,7 @@ curl -X GET "http://localhost:3000/?ids=1
|
|
|
232
234
|
{"statusCode":400,"error":"Bad Request","message":"querystring/hello should be array"}
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
Using `coerceTypes` as 'array'
|
|
237
|
+
Using `coerceTypes` as 'array' will fix it:
|
|
236
238
|
|
|
237
239
|
```js
|
|
238
240
|
const ajv = new Ajv({
|
|
@@ -253,12 +255,53 @@ curl -X GET "http://localhost:3000/?ids=1
|
|
|
253
255
|
{"params":{"hello":["1"]}}
|
|
254
256
|
```
|
|
255
257
|
|
|
258
|
+
You can also specify a custom schema validator for each parameter type (body, querystring, params, headers).
|
|
259
|
+
|
|
260
|
+
For example, the following code disable type cohercion only for the `body` parameters, changing the ajv default options:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
const schemaCompilers = {
|
|
264
|
+
body: new Ajv({
|
|
265
|
+
removeAdditional: false,
|
|
266
|
+
coerceTypes: false,
|
|
267
|
+
allErrors: true
|
|
268
|
+
}),
|
|
269
|
+
params: new Ajv({
|
|
270
|
+
removeAdditional: false,
|
|
271
|
+
coerceTypes: true,
|
|
272
|
+
allErrors: true
|
|
273
|
+
}),
|
|
274
|
+
querystring: new Ajv({
|
|
275
|
+
removeAdditional: false,
|
|
276
|
+
coerceTypes: true,
|
|
277
|
+
allErrors: true
|
|
278
|
+
}),
|
|
279
|
+
headers: new Ajv({
|
|
280
|
+
removeAdditional: false,
|
|
281
|
+
coerceTypes: true,
|
|
282
|
+
allErrors: true
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
server.setValidatorCompiler(req => {
|
|
287
|
+
if (!req.httpPart) {
|
|
288
|
+
throw new Error('Missing httpPart')
|
|
289
|
+
}
|
|
290
|
+
const compiler = schemaCompilers[req.httpPart]
|
|
291
|
+
if (!compiler) {
|
|
292
|
+
throw new Error(`Missing compiler for ${req.httpPart}`)
|
|
293
|
+
}
|
|
294
|
+
return compiler.compile(req.schema)
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
256
298
|
For further information see [here](https://ajv.js.org/coercion.html)
|
|
257
299
|
|
|
258
300
|
<a name="ajv-plugins"></a>
|
|
259
301
|
#### Ajv Plugins
|
|
260
302
|
|
|
261
|
-
You can provide a list of plugins you want to use with
|
|
303
|
+
You can provide a list of plugins you want to use with the default `ajv` instance.
|
|
304
|
+
Note that the plugin must be **compatible with Ajv v6**.
|
|
262
305
|
|
|
263
306
|
> Refer to [`ajv options`](Server.md#ajv) to check plugins format
|
|
264
307
|
|
package/fastify.js
CHANGED
|
@@ -399,6 +399,17 @@ function fastify (options) {
|
|
|
399
399
|
// Delay configuring clientError handler so that it can access fastify state.
|
|
400
400
|
server.on('clientError', options.clientErrorHandler.bind(fastify))
|
|
401
401
|
|
|
402
|
+
try {
|
|
403
|
+
const dc = require('diagnostics_channel')
|
|
404
|
+
const initChannel = dc.channel('fastify.initialization')
|
|
405
|
+
if (initChannel.hasSubscribers) {
|
|
406
|
+
initChannel.publish({ fastify })
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
// This only happens if `diagnostics_channel` isn't available, i.e. earlier
|
|
410
|
+
// versions of Node.js. In that event, we don't care, so ignore the error.
|
|
411
|
+
}
|
|
412
|
+
|
|
402
413
|
return fastify
|
|
403
414
|
|
|
404
415
|
function throwIfAlreadyStarted (msg) {
|
package/lib/contentTypeParser.js
CHANGED
|
@@ -67,9 +67,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
|
67
67
|
this.customParsers[''] = parser
|
|
68
68
|
} else {
|
|
69
69
|
if (contentTypeIsString) {
|
|
70
|
-
|
|
71
|
-
this.parserList.unshift(contentType)
|
|
72
|
-
}
|
|
70
|
+
this.parserList.unshift(contentType)
|
|
73
71
|
} else {
|
|
74
72
|
this.parserRegExpList.unshift(contentType)
|
|
75
73
|
}
|
|
@@ -83,10 +81,10 @@ ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
|
83
81
|
|
|
84
82
|
ContentTypeParser.prototype.existingParser = function (contentType) {
|
|
85
83
|
if (contentType === 'application/json') {
|
|
86
|
-
return this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
|
|
84
|
+
return this.customParsers['application/json'] && this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
|
|
87
85
|
}
|
|
88
86
|
if (contentType === 'text/plain') {
|
|
89
|
-
return this.customParsers['text/plain'].fn !== defaultPlainTextParser
|
|
87
|
+
return this.customParsers['text/plain'] && this.customParsers['text/plain'].fn !== defaultPlainTextParser
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
return contentType in this.customParsers
|
package/lib/decorate.js
CHANGED
|
@@ -22,21 +22,35 @@ function decorate (instance, name, fn, dependencies) {
|
|
|
22
22
|
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
if (!Array.isArray(dependencies)) {
|
|
27
|
-
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
|
28
|
-
}
|
|
25
|
+
checkDependencies(instance, name, dependencies)
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
|
28
|
+
Object.defineProperty(instance, name, {
|
|
29
|
+
get: fn.getter,
|
|
30
|
+
set: fn.setter
|
|
31
|
+
})
|
|
32
|
+
} else {
|
|
33
|
+
instance[name] = fn
|
|
31
34
|
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function decorateConstructor (konstructor, name, fn, dependencies) {
|
|
38
|
+
const instance = konstructor.prototype
|
|
39
|
+
if (instance.hasOwnProperty(name) || konstructor.props.includes(name)) {
|
|
40
|
+
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkDependencies(instance, name, dependencies)
|
|
32
44
|
|
|
33
45
|
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
|
34
46
|
Object.defineProperty(instance, name, {
|
|
35
47
|
get: fn.getter,
|
|
36
48
|
set: fn.setter
|
|
37
49
|
})
|
|
38
|
-
} else {
|
|
50
|
+
} else if (fn) {
|
|
39
51
|
instance[name] = fn
|
|
52
|
+
} else {
|
|
53
|
+
konstructor.props.push(name)
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
|
|
@@ -61,14 +75,24 @@ function checkExistence (instance, name) {
|
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
function checkRequestExistence (name) {
|
|
78
|
+
if (name && this[kRequest].props.includes(name)) return true
|
|
64
79
|
return checkExistence(this[kRequest].prototype, name)
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
function checkReplyExistence (name) {
|
|
83
|
+
if (name && this[kReply].props.includes(name)) return true
|
|
68
84
|
return checkExistence(this[kReply].prototype, name)
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
function checkDependencies (instance, deps) {
|
|
87
|
+
function checkDependencies (instance, name, deps) {
|
|
88
|
+
if (deps === undefined || deps === null) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!Array.isArray(deps)) {
|
|
93
|
+
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
// eslint-disable-next-line no-var
|
|
73
97
|
for (var i = 0; i !== deps.length; ++i) {
|
|
74
98
|
if (!checkExistence(instance, deps[i])) {
|
|
@@ -80,14 +104,14 @@ function checkDependencies (instance, deps) {
|
|
|
80
104
|
function decorateReply (name, fn, dependencies) {
|
|
81
105
|
assertNotStarted(this, name)
|
|
82
106
|
checkReferenceType(name, fn)
|
|
83
|
-
|
|
107
|
+
decorateConstructor(this[kReply], name, fn, dependencies)
|
|
84
108
|
return this
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
function decorateRequest (name, fn, dependencies) {
|
|
88
112
|
assertNotStarted(this, name)
|
|
89
113
|
checkReferenceType(name, fn)
|
|
90
|
-
|
|
114
|
+
decorateConstructor(this[kRequest], name, fn, dependencies)
|
|
91
115
|
return this
|
|
92
116
|
}
|
|
93
117
|
|
package/lib/reply.js
CHANGED
|
@@ -63,6 +63,7 @@ function Reply (res, request, log) {
|
|
|
63
63
|
this[kReplyStartTime] = undefined
|
|
64
64
|
this.log = log
|
|
65
65
|
}
|
|
66
|
+
Reply.props = []
|
|
66
67
|
|
|
67
68
|
Object.defineProperties(Reply.prototype, {
|
|
68
69
|
context: {
|
|
@@ -377,8 +378,8 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
function onSendHook (reply, payload) {
|
|
380
|
-
reply[kReplySent] = true
|
|
381
381
|
if (reply.context.onSend !== null) {
|
|
382
|
+
reply[kReplySent] = true
|
|
382
383
|
onSendHookRunner(
|
|
383
384
|
reply.context.onSend,
|
|
384
385
|
reply.request,
|
|
@@ -422,6 +423,8 @@ function onSendEnd (reply, payload) {
|
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
if (typeof payload.pipe === 'function') {
|
|
426
|
+
reply[kReplySent] = true
|
|
427
|
+
|
|
425
428
|
sendStream(payload, res, reply)
|
|
426
429
|
return
|
|
427
430
|
}
|
|
@@ -573,12 +576,17 @@ function handleError (reply, error, cb) {
|
|
|
573
576
|
message: { value: error.message || '' },
|
|
574
577
|
statusCode: { value: statusCode }
|
|
575
578
|
}))
|
|
579
|
+
|
|
580
|
+
if (serializerFn !== false && typeof payload !== 'string') {
|
|
581
|
+
throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
|
|
582
|
+
}
|
|
576
583
|
} catch (err) {
|
|
577
584
|
// error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
|
|
578
585
|
reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
|
|
579
586
|
res.statusCode = 500
|
|
580
587
|
payload = serializeError({
|
|
581
588
|
error: statusCodes['500'],
|
|
589
|
+
code: err.code,
|
|
582
590
|
message: err.message,
|
|
583
591
|
statusCode: 500
|
|
584
592
|
})
|
|
@@ -651,6 +659,8 @@ function onResponseCallback (err, request, reply) {
|
|
|
651
659
|
}
|
|
652
660
|
|
|
653
661
|
function buildReply (R) {
|
|
662
|
+
const props = [...R.props]
|
|
663
|
+
|
|
654
664
|
function _Reply (res, request, log) {
|
|
655
665
|
this.raw = res
|
|
656
666
|
this[kReplyIsError] = false
|
|
@@ -662,8 +672,14 @@ function buildReply (R) {
|
|
|
662
672
|
this[kReplyHeaders] = {}
|
|
663
673
|
this[kReplyStartTime] = undefined
|
|
664
674
|
this.log = log
|
|
675
|
+
|
|
676
|
+
// eslint-disable-next-line no-var
|
|
677
|
+
for (var i = 0; i < props.length; i++) {
|
|
678
|
+
this[props[i]] = null
|
|
679
|
+
}
|
|
665
680
|
}
|
|
666
681
|
_Reply.prototype = new R()
|
|
682
|
+
_Reply.props = props
|
|
667
683
|
return _Reply
|
|
668
684
|
}
|
|
669
685
|
|
package/lib/request.js
CHANGED
|
@@ -13,6 +13,7 @@ function Request (id, params, req, query, log, context) {
|
|
|
13
13
|
this.log = log
|
|
14
14
|
this.body = null
|
|
15
15
|
}
|
|
16
|
+
Request.props = []
|
|
16
17
|
|
|
17
18
|
function getTrustProxyFn (tp) {
|
|
18
19
|
if (typeof tp === 'function') {
|
|
@@ -43,6 +44,7 @@ function buildRequest (R, trustProxy) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function buildRegularRequest (R) {
|
|
47
|
+
const props = [...R.props]
|
|
46
48
|
function _Request (id, params, req, query, log, context) {
|
|
47
49
|
this.id = id
|
|
48
50
|
this.context = context
|
|
@@ -51,8 +53,14 @@ function buildRegularRequest (R) {
|
|
|
51
53
|
this.query = query
|
|
52
54
|
this.log = log
|
|
53
55
|
this.body = null
|
|
56
|
+
|
|
57
|
+
// eslint-disable-next-line no-var
|
|
58
|
+
for (var i = 0; i < props.length; i++) {
|
|
59
|
+
this[props[i]] = null
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
_Request.prototype = new R()
|
|
63
|
+
_Request.props = props
|
|
56
64
|
|
|
57
65
|
return _Request
|
|
58
66
|
}
|
package/lib/route.js
CHANGED
|
@@ -143,12 +143,14 @@ function buildRouting (options) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
const path = opts.url || opts.path
|
|
147
|
+
|
|
146
148
|
if (!opts.handler) {
|
|
147
|
-
throw new Error(`Missing handler function for ${opts.method}:${
|
|
149
|
+
throw new Error(`Missing handler function for ${opts.method}:${path} route.`)
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
|
|
151
|
-
throw new Error(`Error Handler for ${opts.method}:${
|
|
153
|
+
throw new Error(`Error Handler for ${opts.method}:${path} route, if defined, must be a function`)
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
validateBodyLimitOption(opts.bodyLimit)
|
|
@@ -156,7 +158,6 @@ function buildRouting (options) {
|
|
|
156
158
|
const prefix = this[kRoutePrefix]
|
|
157
159
|
|
|
158
160
|
this.after((notHandledErr, done) => {
|
|
159
|
-
const path = opts.url || opts.path
|
|
160
161
|
if (path === '/' && prefix.length && opts.method !== 'HEAD') {
|
|
161
162
|
switch (opts.prefixTrailingSlash) {
|
|
162
163
|
case 'slash':
|
package/lib/schema-controller.js
CHANGED
|
@@ -15,13 +15,18 @@ function buildSchemaController (parentSchemaCtrl, opts) {
|
|
|
15
15
|
return new SchemaController(parentSchemaCtrl, opts)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
18
|
+
let compilersFactory = {
|
|
19
|
+
buildValidator: ValidatorSelector(),
|
|
20
|
+
buildSerializer: serializerCompiler
|
|
21
|
+
}
|
|
22
|
+
if (opts && opts.compilersFactory) {
|
|
23
|
+
compilersFactory = Object.assign(compilersFactory, opts.compilersFactory)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const option = {
|
|
27
|
+
bucket: (opts && opts.bucket) || buildSchemas,
|
|
28
|
+
compilersFactory: compilersFactory
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
return new SchemaController(undefined, option)
|
|
27
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.3",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
},
|
|
116
116
|
"homepage": "https://www.fastify.io/",
|
|
117
117
|
"devDependencies": {
|
|
118
|
-
"@fastify/ajv-compiler-8": "
|
|
118
|
+
"@fastify/ajv-compiler-8": "npm:@fastify/ajv-compiler@^2.0.0",
|
|
119
119
|
"@fastify/pre-commit": "^2.0.1",
|
|
120
120
|
"@hapi/joi": "^17.1.1",
|
|
121
121
|
"@sinonjs/fake-timers": "^7.0.0",
|
|
@@ -123,9 +123,9 @@
|
|
|
123
123
|
"@types/pino": "^6.0.1",
|
|
124
124
|
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
|
125
125
|
"@typescript-eslint/parser": "^4.5.0",
|
|
126
|
-
"JSONStream": "^1.3.5",
|
|
127
126
|
"ajv": "^6.0.0",
|
|
128
127
|
"ajv-errors": "^1.0.1",
|
|
128
|
+
"ajv-formats": "^2.1.1",
|
|
129
129
|
"ajv-i18n": "^3.5.0",
|
|
130
130
|
"ajv-merge-patch": "^4.1.0",
|
|
131
131
|
"ajv-pack": "^0.3.1",
|
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"hsts": "^2.2.0",
|
|
152
152
|
"http-errors": "^1.7.1",
|
|
153
153
|
"ienoopen": "^1.1.0",
|
|
154
|
+
"JSONStream": "^1.3.5",
|
|
154
155
|
"license-checker": "^25.0.1",
|
|
155
156
|
"pem": "^1.14.4",
|
|
156
157
|
"proxyquire": "^2.1.3",
|
|
@@ -193,7 +194,8 @@
|
|
|
193
194
|
"lib/configValidator.js",
|
|
194
195
|
"fastify.d.ts",
|
|
195
196
|
"types/*",
|
|
196
|
-
"test/types/*"
|
|
197
|
+
"test/types/*",
|
|
198
|
+
"test/same-shape.test.js"
|
|
197
199
|
]
|
|
198
200
|
},
|
|
199
201
|
"tsd": {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Bundlers test stack
|
|
2
|
+
|
|
3
|
+
In some cases developers bundle their apps for several targets, eg: serveless applications.
|
|
4
|
+
Even if it's not recommended by Fastify team; we need to ensure we do not break the build process.
|
|
5
|
+
Please note this might result in feature behaving differently like the version handling check for plugins.
|
|
6
|
+
|
|
7
|
+
## Test bundlers
|
|
8
|
+
|
|
9
|
+
The bundler test stack has been set appart than the rest of the Unit testing stack because it's not a
|
|
10
|
+
part of the fastify lib itself. Note that the tests run in CI only on NodeJs LTS version.
|
|
11
|
+
Developers does not need to install every bundler to run unit tests.
|
|
12
|
+
|
|
13
|
+
To run the bundler tests you'll need to first install the repository dependencies and after the bundler
|
|
14
|
+
stack dependencies. See:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# path: root of repository /fastify
|
|
18
|
+
npm i
|
|
19
|
+
cd test/bundler/webpack
|
|
20
|
+
npm i
|
|
21
|
+
npm run test # test command runs bundle before of starting the test
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Bundler test development
|
|
25
|
+
|
|
26
|
+
To not break the fastify unit testing stack please name test files like this `*-test.js` and not `*.test.js`,
|
|
27
|
+
otherwise it can be catched by unit-test regex of fastify.
|
|
28
|
+
Test need to ensure the build process works and the fastify application can be run,
|
|
29
|
+
no need to go in deep testing unless an issue is raised.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const test = t.test
|
|
5
|
+
const fastifySuccess = require('./dist/success')
|
|
6
|
+
const fastifyFailPlugin = require('./dist/failPlugin')
|
|
7
|
+
|
|
8
|
+
test('Bundled package should work', t => {
|
|
9
|
+
t.plan(1)
|
|
10
|
+
fastifySuccess.ready((err) => {
|
|
11
|
+
t.error(err)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// In the webpack bundle context the fastify package.json is not read
|
|
16
|
+
// Because of this the version is set to `undefined`, this makes the plugin
|
|
17
|
+
// version check not able to work properly. By then this test shouldn't work
|
|
18
|
+
// in non-bundled environment but works in bundled environment
|
|
19
|
+
test('Bundled package should work with bad plugin version, undefined version fallback', t => {
|
|
20
|
+
t.plan(1)
|
|
21
|
+
fastifyFailPlugin.ready((err) => {
|
|
22
|
+
t.error(err)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const fp = require('fastify-plugin')
|
|
2
|
+
const fastify = require('../../../../')({
|
|
3
|
+
logger: true
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
fastify.get('/', function (request, reply) {
|
|
7
|
+
reply.send({ hello: 'world' })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
fastify.register(fp((instance, opts, done) => {
|
|
11
|
+
done()
|
|
12
|
+
}, { fastify: '9.x' }))
|
|
13
|
+
|
|
14
|
+
module.exports = fastify
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
entry: { success: './src/index.js', failPlugin: './src/fail-plugin-version.js' },
|
|
5
|
+
target: 'node',
|
|
6
|
+
output: {
|
|
7
|
+
path: path.resolve(__dirname, 'dist'),
|
|
8
|
+
filename: '[name].js',
|
|
9
|
+
library: {
|
|
10
|
+
type: 'commonjs2'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1707,3 +1707,114 @@ test('cannot remove content type parsers after binding', t => {
|
|
|
1707
1707
|
t.throws(() => fastify.removeContentTypeParser('application/json'))
|
|
1708
1708
|
})
|
|
1709
1709
|
})
|
|
1710
|
+
|
|
1711
|
+
test('should be able to override the default json parser after removeAllContentTypeParsers', t => {
|
|
1712
|
+
t.plan(5)
|
|
1713
|
+
|
|
1714
|
+
const fastify = Fastify()
|
|
1715
|
+
|
|
1716
|
+
fastify.post('/', (req, reply) => {
|
|
1717
|
+
reply.send(req.body)
|
|
1718
|
+
})
|
|
1719
|
+
|
|
1720
|
+
fastify.removeAllContentTypeParsers()
|
|
1721
|
+
|
|
1722
|
+
fastify.addContentTypeParser('application/json', function (req, payload, done) {
|
|
1723
|
+
t.ok('called')
|
|
1724
|
+
jsonParser(payload, function (err, body) {
|
|
1725
|
+
done(err, body)
|
|
1726
|
+
})
|
|
1727
|
+
})
|
|
1728
|
+
|
|
1729
|
+
fastify.listen(0, err => {
|
|
1730
|
+
t.error(err)
|
|
1731
|
+
|
|
1732
|
+
sget({
|
|
1733
|
+
method: 'POST',
|
|
1734
|
+
url: 'http://localhost:' + fastify.server.address().port,
|
|
1735
|
+
body: '{"hello":"world"}',
|
|
1736
|
+
headers: {
|
|
1737
|
+
'Content-Type': 'application/json'
|
|
1738
|
+
}
|
|
1739
|
+
}, (err, response, body) => {
|
|
1740
|
+
t.error(err)
|
|
1741
|
+
t.equal(response.statusCode, 200)
|
|
1742
|
+
t.same(body.toString(), JSON.stringify({ hello: 'world' }))
|
|
1743
|
+
fastify.close()
|
|
1744
|
+
})
|
|
1745
|
+
})
|
|
1746
|
+
})
|
|
1747
|
+
|
|
1748
|
+
test('should be able to override the default plain text parser after removeAllContentTypeParsers', t => {
|
|
1749
|
+
t.plan(5)
|
|
1750
|
+
|
|
1751
|
+
const fastify = Fastify()
|
|
1752
|
+
|
|
1753
|
+
fastify.post('/', (req, reply) => {
|
|
1754
|
+
reply.send(req.body)
|
|
1755
|
+
})
|
|
1756
|
+
|
|
1757
|
+
fastify.removeAllContentTypeParsers()
|
|
1758
|
+
|
|
1759
|
+
fastify.addContentTypeParser('text/plain', function (req, payload, done) {
|
|
1760
|
+
t.ok('called')
|
|
1761
|
+
plainTextParser(payload, function (err, body) {
|
|
1762
|
+
done(err, body)
|
|
1763
|
+
})
|
|
1764
|
+
})
|
|
1765
|
+
|
|
1766
|
+
fastify.listen(0, err => {
|
|
1767
|
+
t.error(err)
|
|
1768
|
+
|
|
1769
|
+
sget({
|
|
1770
|
+
method: 'POST',
|
|
1771
|
+
url: 'http://localhost:' + fastify.server.address().port,
|
|
1772
|
+
body: 'hello world',
|
|
1773
|
+
headers: {
|
|
1774
|
+
'Content-Type': 'text/plain'
|
|
1775
|
+
}
|
|
1776
|
+
}, (err, response, body) => {
|
|
1777
|
+
t.error(err)
|
|
1778
|
+
t.equal(response.statusCode, 200)
|
|
1779
|
+
t.equal(body.toString(), 'hello world')
|
|
1780
|
+
fastify.close()
|
|
1781
|
+
})
|
|
1782
|
+
})
|
|
1783
|
+
})
|
|
1784
|
+
|
|
1785
|
+
test('should be able to add a custom content type parser after removeAllContentTypeParsers', t => {
|
|
1786
|
+
t.plan(5)
|
|
1787
|
+
|
|
1788
|
+
const fastify = Fastify()
|
|
1789
|
+
|
|
1790
|
+
fastify.post('/', (req, reply) => {
|
|
1791
|
+
reply.send(req.body)
|
|
1792
|
+
})
|
|
1793
|
+
|
|
1794
|
+
fastify.removeAllContentTypeParsers()
|
|
1795
|
+
|
|
1796
|
+
fastify.addContentTypeParser('application/jsoff', function (req, payload, done) {
|
|
1797
|
+
t.ok('called')
|
|
1798
|
+
jsonParser(payload, function (err, body) {
|
|
1799
|
+
done(err, body)
|
|
1800
|
+
})
|
|
1801
|
+
})
|
|
1802
|
+
|
|
1803
|
+
fastify.listen(0, err => {
|
|
1804
|
+
t.error(err)
|
|
1805
|
+
|
|
1806
|
+
sget({
|
|
1807
|
+
method: 'POST',
|
|
1808
|
+
url: 'http://localhost:' + fastify.server.address().port,
|
|
1809
|
+
body: '{"hello":"world"}',
|
|
1810
|
+
headers: {
|
|
1811
|
+
'Content-Type': 'application/jsoff'
|
|
1812
|
+
}
|
|
1813
|
+
}, (err, response, body) => {
|
|
1814
|
+
t.error(err)
|
|
1815
|
+
t.equal(response.statusCode, 200)
|
|
1816
|
+
t.same(body.toString(), JSON.stringify({ hello: 'world' }))
|
|
1817
|
+
fastify.close()
|
|
1818
|
+
})
|
|
1819
|
+
})
|
|
1820
|
+
})
|
package/test/decorator.test.js
CHANGED
|
@@ -17,6 +17,15 @@ test('server methods should exist', t => {
|
|
|
17
17
|
t.ok(fastify.hasDecorator)
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
+
test('should check if the given decoration already exist when null', t => {
|
|
21
|
+
t.plan(1)
|
|
22
|
+
const fastify = Fastify()
|
|
23
|
+
fastify.decorate('null', null)
|
|
24
|
+
fastify.ready(() => {
|
|
25
|
+
t.ok(fastify.hasDecorator('null'))
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
20
29
|
test('server methods should be encapsulated via .register', t => {
|
|
21
30
|
t.plan(2)
|
|
22
31
|
const fastify = Fastify()
|
|
@@ -425,6 +434,15 @@ test('hasRequestDecorator', t => {
|
|
|
425
434
|
t.ok(fastify.hasRequestDecorator(requestDecoratorName))
|
|
426
435
|
})
|
|
427
436
|
|
|
437
|
+
t.test('should check if the given request decoration already exist when null', t => {
|
|
438
|
+
t.plan(2)
|
|
439
|
+
const fastify = Fastify()
|
|
440
|
+
|
|
441
|
+
t.notOk(fastify.hasRequestDecorator(requestDecoratorName))
|
|
442
|
+
fastify.decorateRequest(requestDecoratorName, null)
|
|
443
|
+
t.ok(fastify.hasRequestDecorator(requestDecoratorName))
|
|
444
|
+
})
|
|
445
|
+
|
|
428
446
|
t.test('should be plugin encapsulable', t => {
|
|
429
447
|
t.plan(4)
|
|
430
448
|
const fastify = Fastify()
|
|
@@ -481,6 +499,15 @@ test('hasReplyDecorator', t => {
|
|
|
481
499
|
t.ok(fastify.hasReplyDecorator(replyDecoratorName))
|
|
482
500
|
})
|
|
483
501
|
|
|
502
|
+
t.test('should check if the given reply decoration already exist when null', t => {
|
|
503
|
+
t.plan(2)
|
|
504
|
+
const fastify = Fastify()
|
|
505
|
+
|
|
506
|
+
t.notOk(fastify.hasReplyDecorator(replyDecoratorName))
|
|
507
|
+
fastify.decorateReply(replyDecoratorName, null)
|
|
508
|
+
t.ok(fastify.hasReplyDecorator(replyDecoratorName))
|
|
509
|
+
})
|
|
510
|
+
|
|
484
511
|
t.test('should be plugin encapsulable', t => {
|
|
485
512
|
t.plan(4)
|
|
486
513
|
const fastify = Fastify()
|