fastify 4.0.2 → 4.2.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/.eslintrc +1 -0
- package/README.md +3 -3
- package/docs/Guides/Database.md +5 -5
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +13 -0
- package/docs/Guides/Migration-Guide-V4.md +58 -2
- package/docs/Guides/Serverless.md +23 -10
- package/docs/Reference/Hooks.md +52 -0
- package/docs/Reference/LTS.md +2 -2
- package/docs/Reference/Plugins.md +1 -1
- package/docs/Reference/Server.md +1 -1
- package/docs/Reference/Type-Providers.md +4 -3
- package/docs/Reference/TypeScript.md +38 -25
- package/docs/Reference/Validation-and-Serialization.md +11 -0
- package/docs/index.md +1 -1
- package/fastify.d.ts +3 -3
- package/fastify.js +19 -19
- package/integration/server.js +27 -0
- package/integration/test.sh +23 -0
- package/lib/context.js +5 -2
- package/lib/error-serializer.js +173 -8
- package/lib/handleRequest.js +1 -1
- package/lib/reply.js +2 -0
- package/lib/route.js +39 -29
- package/lib/server.js +9 -1
- package/lib/symbols.js +2 -1
- package/lib/validation.js +2 -0
- package/lib/wrapThenable.js +8 -3
- package/package.json +6 -6
- package/test/404s.test.js +2 -2
- package/test/build/error-serializer.test.js +6 -1
- package/test/hooks.test.js +21 -0
- package/test/internals/reply.test.js +12 -0
- package/test/listen.test.js +16 -2
- package/test/pretty-print.test.js +3 -3
- package/test/reply-error.test.js +1 -1
- package/test/schema-feature.test.js +2 -2
- package/test/stream.test.js +73 -0
- package/test/types/fastify.test-d.ts +12 -1
- package/test/types/instance.test-d.ts +1 -1
- package/test/types/register.test-d.ts +77 -2
- package/test/types/request.test-d.ts +8 -4
- package/test/types/type-provider.test-d.ts +11 -2
- package/test/validation-error-handling.test.js +32 -0
- package/types/register.d.ts +9 -7
- package/types/route.d.ts +10 -12
- package/types/schema.d.ts +1 -1
- package/types/type-provider.d.ts +12 -5
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
NUMBER=$RANDOM
|
|
6
|
+
curl -i -X GET -H 'Content-Type: application/json' localhost:3000/ > GET
|
|
7
|
+
if [[ ! $(cat GET | head -1| cut -f2 -d" ") == "200" || ! $(cat GET | tail -1| cut -f4 -d"\"") == "home page" ]] ; then
|
|
8
|
+
exit 1
|
|
9
|
+
fi;
|
|
10
|
+
curl -i -X POST -H 'Content-Type: application/json' localhost:3000/post/$NUMBER --data {} > POST
|
|
11
|
+
if [[ ! $(cat POST | head -1| cut -f2 -d" ") == "201" || ! $(cat POST | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
|
12
|
+
exit 1
|
|
13
|
+
fi;
|
|
14
|
+
curl -i -X PUT -H 'Content-Type: application/json' localhost:3000/put/$NUMBER --data {} > PUT
|
|
15
|
+
if [[ ! $(cat PUT | head -1| cut -f2 -d" ") == "200" || ! $(cat PUT | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
|
16
|
+
exit 1
|
|
17
|
+
fi;
|
|
18
|
+
curl -i -X DELETE -H 'Content-Type: application/json' localhost:3000/delete/$NUMBER --data {} > DELETE
|
|
19
|
+
if [[ ! $(cat DELETE | head -1| cut -f2 -d" ") == "204" ]]; then
|
|
20
|
+
exit 1
|
|
21
|
+
fi;
|
|
22
|
+
|
|
23
|
+
rm -f GET POST PUT DELETE
|
package/lib/context.js
CHANGED
|
@@ -9,7 +9,8 @@ const {
|
|
|
9
9
|
kRequest,
|
|
10
10
|
kBodyLimit,
|
|
11
11
|
kLogLevel,
|
|
12
|
-
kContentTypeParser
|
|
12
|
+
kContentTypeParser,
|
|
13
|
+
kRouteByFastify
|
|
13
14
|
} = require('./symbols.js')
|
|
14
15
|
|
|
15
16
|
// Objects that holds the context of every request
|
|
@@ -25,7 +26,8 @@ function Context ({
|
|
|
25
26
|
attachValidation,
|
|
26
27
|
replySerializer,
|
|
27
28
|
schemaErrorFormatter,
|
|
28
|
-
server
|
|
29
|
+
server,
|
|
30
|
+
isFastify
|
|
29
31
|
}) {
|
|
30
32
|
this.schema = schema
|
|
31
33
|
this.handler = handler
|
|
@@ -50,6 +52,7 @@ function Context ({
|
|
|
50
52
|
this.attachValidation = attachValidation
|
|
51
53
|
this[kReplySerializerDefault] = replySerializer
|
|
52
54
|
this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
|
|
55
|
+
this[kRouteByFastify] = isFastify
|
|
53
56
|
|
|
54
57
|
this.server = server
|
|
55
58
|
}
|
package/lib/error-serializer.js
CHANGED
|
@@ -1,15 +1,180 @@
|
|
|
1
1
|
// This file is autogenerated by build/build-error-serializer.js, do not edit
|
|
2
2
|
/* istanbul ignore file */
|
|
3
3
|
|
|
4
|
-
'use strict'
|
|
4
|
+
'use strict'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Serializer {
|
|
9
|
+
constructor (options = {}) {
|
|
10
|
+
switch (options.rounding) {
|
|
11
|
+
case 'floor':
|
|
12
|
+
this.parseInteger = Math.floor
|
|
13
|
+
break
|
|
14
|
+
case 'ceil':
|
|
15
|
+
this.parseInteger = Math.ceil
|
|
16
|
+
break
|
|
17
|
+
case 'round':
|
|
18
|
+
this.parseInteger = Math.round
|
|
19
|
+
break
|
|
20
|
+
default:
|
|
21
|
+
this.parseInteger = Math.trunc
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
asAny (i) {
|
|
27
|
+
return JSON.stringify(i)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
asNull () {
|
|
31
|
+
return 'null'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
asInteger (i) {
|
|
35
|
+
if (typeof i === 'bigint') {
|
|
36
|
+
return i.toString()
|
|
37
|
+
} else if (Number.isInteger(i)) {
|
|
38
|
+
return '' + i
|
|
39
|
+
} else {
|
|
40
|
+
/* eslint no-undef: "off" */
|
|
41
|
+
const integer = this.parseInteger(i)
|
|
42
|
+
if (Number.isNaN(integer) || !Number.isFinite(integer)) {
|
|
43
|
+
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
44
|
+
} else {
|
|
45
|
+
return '' + integer
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
asIntegerNullable (i) {
|
|
51
|
+
return i === null ? 'null' : this.asInteger(i)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
asNumber (i) {
|
|
55
|
+
const num = Number(i)
|
|
56
|
+
if (Number.isNaN(num)) {
|
|
57
|
+
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
58
|
+
} else if (!Number.isFinite(num)) {
|
|
59
|
+
return null
|
|
60
|
+
} else {
|
|
61
|
+
return '' + num
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
asNumberNullable (i) {
|
|
66
|
+
return i === null ? 'null' : this.asNumber(i)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
asBoolean (bool) {
|
|
70
|
+
return bool && 'true' || 'false' // eslint-disable-line
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
asBooleanNullable (bool) {
|
|
74
|
+
return bool === null ? 'null' : this.asBoolean(bool)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
asDatetime (date) {
|
|
78
|
+
const quotes = '"'
|
|
79
|
+
if (date instanceof Date) {
|
|
80
|
+
return quotes + date.toISOString() + quotes
|
|
81
|
+
}
|
|
82
|
+
return this.asString(date)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
asDatetimeNullable (date) {
|
|
86
|
+
return date === null ? 'null' : this.asDatetime(date)
|
|
87
|
+
}
|
|
8
88
|
|
|
9
|
-
|
|
10
|
-
const
|
|
89
|
+
asDate (date) {
|
|
90
|
+
const quotes = '"'
|
|
91
|
+
if (date instanceof Date) {
|
|
92
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
|
|
93
|
+
}
|
|
94
|
+
return this.asString(date)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
asDateNullable (date) {
|
|
98
|
+
return date === null ? 'null' : this.asDate(date)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
asTime (date) {
|
|
102
|
+
const quotes = '"'
|
|
103
|
+
if (date instanceof Date) {
|
|
104
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
105
|
+
}
|
|
106
|
+
return this.asString(date)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
asTimeNullable (date) {
|
|
110
|
+
return date === null ? 'null' : this.asTime(date)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
asString (str) {
|
|
114
|
+
const quotes = '"'
|
|
115
|
+
if (str instanceof Date) {
|
|
116
|
+
return quotes + str.toISOString() + quotes
|
|
117
|
+
} else if (str === null) {
|
|
118
|
+
return quotes + quotes
|
|
119
|
+
} else if (str instanceof RegExp) {
|
|
120
|
+
str = str.source
|
|
121
|
+
} else if (typeof str !== 'string') {
|
|
122
|
+
str = str.toString()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (str.length < 42) {
|
|
126
|
+
return this.asStringSmall(str)
|
|
127
|
+
} else {
|
|
128
|
+
return JSON.stringify(str)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
11
131
|
|
|
132
|
+
asStringNullable (str) {
|
|
133
|
+
return str === null ? 'null' : this.asString(str)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// magically escape strings for json
|
|
137
|
+
// relying on their charCodeAt
|
|
138
|
+
// everything below 32 needs JSON.stringify()
|
|
139
|
+
// every string that contain surrogate needs JSON.stringify()
|
|
140
|
+
// 34 and 92 happens all the time, so we
|
|
141
|
+
// have a fast case for them
|
|
142
|
+
asStringSmall (str) {
|
|
143
|
+
const l = str.length
|
|
144
|
+
let result = ''
|
|
145
|
+
let last = 0
|
|
146
|
+
let found = false
|
|
147
|
+
let surrogateFound = false
|
|
148
|
+
let point = 255
|
|
149
|
+
// eslint-disable-next-line
|
|
150
|
+
for (var i = 0; i < l && point >= 32; i++) {
|
|
151
|
+
point = str.charCodeAt(i)
|
|
152
|
+
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
153
|
+
// The current character is a surrogate.
|
|
154
|
+
surrogateFound = true
|
|
155
|
+
}
|
|
156
|
+
if (point === 34 || point === 92) {
|
|
157
|
+
result += str.slice(last, i) + '\\'
|
|
158
|
+
last = i
|
|
159
|
+
found = true
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!found) {
|
|
164
|
+
result = str
|
|
165
|
+
} else {
|
|
166
|
+
result += str.slice(last)
|
|
167
|
+
}
|
|
168
|
+
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
12
173
|
|
|
174
|
+
const serializer = new Serializer({"mode":"standalone"})
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
13
178
|
function main (input) {
|
|
14
179
|
let json = ''
|
|
15
180
|
json += anonymous0(input)
|
|
@@ -17,7 +182,7 @@ const ajv = buildAjv({})
|
|
|
17
182
|
}
|
|
18
183
|
|
|
19
184
|
function anonymous0 (input) {
|
|
20
|
-
//
|
|
185
|
+
// #
|
|
21
186
|
|
|
22
187
|
var obj = (input && typeof input.toJSON === 'function')
|
|
23
188
|
? input.toJSON()
|
|
@@ -81,5 +246,5 @@ const ajv = buildAjv({})
|
|
|
81
246
|
|
|
82
247
|
|
|
83
248
|
|
|
84
|
-
module.exports = main
|
|
85
|
-
|
|
249
|
+
module.exports = main
|
|
250
|
+
|
package/lib/handleRequest.js
CHANGED
package/lib/reply.js
CHANGED
|
@@ -331,10 +331,12 @@ Reply.prototype.redirect = function (code, url) {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
this.header('location', url).code(code).send()
|
|
334
|
+
return this
|
|
334
335
|
}
|
|
335
336
|
|
|
336
337
|
Reply.prototype.callNotFound = function () {
|
|
337
338
|
notFound(this)
|
|
339
|
+
return this
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
Reply.prototype.getResponseTime = function () {
|
package/lib/route.js
CHANGED
|
@@ -8,7 +8,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
|
|
|
8
8
|
const { normalizeSchema } = require('./schemas')
|
|
9
9
|
const { parseHeadOnSendHandlers } = require('./headRoute')
|
|
10
10
|
const warning = require('./warnings')
|
|
11
|
-
const { kRequestAcceptVersion } = require('./symbols')
|
|
11
|
+
const { kRequestAcceptVersion, kRouteByFastify } = require('./symbols')
|
|
12
12
|
|
|
13
13
|
const {
|
|
14
14
|
compileSchemasForValidation,
|
|
@@ -113,7 +113,7 @@ function buildRouting (options) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Convert shorthand to extended route declaration
|
|
116
|
-
function prepareRoute (method, url, options, handler) {
|
|
116
|
+
function prepareRoute ({ method, url, options, handler, isFastify }) {
|
|
117
117
|
if (typeof url !== 'string') {
|
|
118
118
|
throw new FST_ERR_INVALID_URL(typeof url)
|
|
119
119
|
}
|
|
@@ -140,11 +140,11 @@ function buildRouting (options) {
|
|
|
140
140
|
handler: handler || (options && options.handler)
|
|
141
141
|
})
|
|
142
142
|
|
|
143
|
-
return route.call(this, options)
|
|
143
|
+
return route.call(this, { options, isFastify })
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Route management
|
|
147
|
-
function route (options) {
|
|
147
|
+
function route ({ options, isFastify }) {
|
|
148
148
|
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
|
|
149
149
|
const opts = { ...options }
|
|
150
150
|
|
|
@@ -176,30 +176,30 @@ function buildRouting (options) {
|
|
|
176
176
|
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
|
|
177
177
|
switch (opts.prefixTrailingSlash) {
|
|
178
178
|
case 'slash':
|
|
179
|
-
addNewRoute.call(this, path)
|
|
179
|
+
addNewRoute.call(this, { path, isFastify })
|
|
180
180
|
break
|
|
181
181
|
case 'no-slash':
|
|
182
|
-
addNewRoute.call(this, '')
|
|
182
|
+
addNewRoute.call(this, { path: '', isFastify })
|
|
183
183
|
break
|
|
184
184
|
case 'both':
|
|
185
185
|
default:
|
|
186
|
-
addNewRoute.call(this, '')
|
|
186
|
+
addNewRoute.call(this, { path: '', isFastify })
|
|
187
187
|
// If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
|
|
188
188
|
if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
|
|
189
|
-
addNewRoute.call(this, path, true)
|
|
189
|
+
addNewRoute.call(this, { path, prefixing: true, isFastify })
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
} else if (path[0] === '/' && prefix.endsWith('/')) {
|
|
193
193
|
// Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
|
|
194
|
-
addNewRoute.call(this, path.slice(1))
|
|
194
|
+
addNewRoute.call(this, { path: path.slice(1), isFastify })
|
|
195
195
|
} else {
|
|
196
|
-
addNewRoute.call(this, path)
|
|
196
|
+
addNewRoute.call(this, { path, isFastify })
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
// chainable api
|
|
200
200
|
return this
|
|
201
201
|
|
|
202
|
-
function addNewRoute (path, prefixing = false) {
|
|
202
|
+
function addNewRoute ({ path, prefixing = false, isFastify = false }) {
|
|
203
203
|
const url = prefix + path
|
|
204
204
|
|
|
205
205
|
opts.url = url
|
|
@@ -241,7 +241,8 @@ function buildRouting (options) {
|
|
|
241
241
|
attachValidation: opts.attachValidation,
|
|
242
242
|
schemaErrorFormatter: opts.schemaErrorFormatter,
|
|
243
243
|
replySerializer: this[kReplySerializerDefault],
|
|
244
|
-
server: this
|
|
244
|
+
server: this,
|
|
245
|
+
isFastify
|
|
245
246
|
})
|
|
246
247
|
|
|
247
248
|
if (opts.version) {
|
|
@@ -249,13 +250,20 @@ function buildRouting (options) {
|
|
|
249
250
|
constraints.version = opts.version
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
const
|
|
253
|
+
const headHandler = router.find('HEAD', opts.url, constraints)
|
|
254
|
+
const hasHEADHandler = headHandler != null
|
|
253
255
|
|
|
254
|
-
//
|
|
255
|
-
if (!
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
// remove the head route created by fastify
|
|
257
|
+
if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
|
|
258
|
+
router.off(opts.method, opts.url, { constraints })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// any route insertion error created by fastify can be safely ignore
|
|
265
|
+
// because it only duplicate route for head
|
|
266
|
+
if (!context[kRouteByFastify]) {
|
|
259
267
|
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`)
|
|
260
268
|
if (isDuplicatedRoute) {
|
|
261
269
|
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
|
|
@@ -320,19 +328,21 @@ function buildRouting (options) {
|
|
|
320
328
|
}
|
|
321
329
|
})
|
|
322
330
|
|
|
323
|
-
const { exposeHeadRoute } = opts
|
|
324
|
-
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
|
|
325
|
-
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
|
|
326
|
-
|
|
327
|
-
if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
|
|
328
|
-
const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
|
|
329
|
-
prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
|
|
330
|
-
} else if (headRouteExists && exposeHeadRoute) {
|
|
331
|
-
warning.emit('FSTDEP007')
|
|
332
|
-
}
|
|
333
|
-
|
|
334
331
|
done(notHandledErr)
|
|
335
332
|
})
|
|
333
|
+
|
|
334
|
+
// register head route in sync
|
|
335
|
+
// we must place it after the `this.after`
|
|
336
|
+
const { exposeHeadRoute } = opts
|
|
337
|
+
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
|
|
338
|
+
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
|
|
339
|
+
|
|
340
|
+
if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) {
|
|
341
|
+
const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
|
|
342
|
+
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers }, isFastify: true })
|
|
343
|
+
} else if (hasHEADHandler && exposeHeadRoute) {
|
|
344
|
+
warning.emit('FSTDEP007')
|
|
345
|
+
}
|
|
336
346
|
}
|
|
337
347
|
}
|
|
338
348
|
|
package/lib/server.js
CHANGED
|
@@ -41,7 +41,15 @@ function createServer (options, httpHandler) {
|
|
|
41
41
|
listenOptions.cb = cb
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// If we have a path specified, don't default host to 'localhost' so we don't end up listening
|
|
45
|
+
// on both path and host
|
|
46
|
+
// See https://github.com/fastify/fastify/issues/4007
|
|
47
|
+
let host
|
|
48
|
+
if (listenOptions.path == null) {
|
|
49
|
+
host = listenOptions.host ?? 'localhost'
|
|
50
|
+
} else {
|
|
51
|
+
host = listenOptions.host
|
|
52
|
+
}
|
|
45
53
|
if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) {
|
|
46
54
|
listenOptions.host = host
|
|
47
55
|
}
|
package/lib/symbols.js
CHANGED
|
@@ -47,7 +47,8 @@ const keys = {
|
|
|
47
47
|
kTestInternals: Symbol('fastify.testInternals'),
|
|
48
48
|
kErrorHandler: Symbol('fastify.errorHandler'),
|
|
49
49
|
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
|
50
|
-
kKeepAliveConnections: Symbol('fastify.keepAliveConnections')
|
|
50
|
+
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
|
51
|
+
kRouteByFastify: Symbol('fastify.routeByFastify')
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
module.exports = keys
|
package/lib/validation.js
CHANGED
|
@@ -106,11 +106,13 @@ function validate (context, request) {
|
|
|
106
106
|
|
|
107
107
|
function wrapValidationError (result, dataVar, schemaErrorFormatter) {
|
|
108
108
|
if (result instanceof Error) {
|
|
109
|
+
result.statusCode = result.statusCode || 400
|
|
109
110
|
result.validationContext = result.validationContext || dataVar
|
|
110
111
|
return result
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const error = schemaErrorFormatter(result, dataVar)
|
|
115
|
+
error.statusCode = error.statusCode || 400
|
|
114
116
|
error.validation = result
|
|
115
117
|
error.validationContext = dataVar
|
|
116
118
|
return error
|
package/lib/wrapThenable.js
CHANGED
|
@@ -11,9 +11,14 @@ function wrapThenable (thenable, reply) {
|
|
|
11
11
|
return
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// this is for async functions that
|
|
15
|
-
//
|
|
16
|
-
|
|
14
|
+
// this is for async functions that are using reply.send directly
|
|
15
|
+
//
|
|
16
|
+
// since wrap-thenable will be called when using reply.send directly
|
|
17
|
+
// without actual return. the response can be sent already or
|
|
18
|
+
// the request may be terminated during the reply. in this situation,
|
|
19
|
+
// it require an extra checking of request.aborted to see whether
|
|
20
|
+
// the request is killed by client.
|
|
21
|
+
if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
|
|
17
22
|
// we use a try-catch internally to avoid adding a catch to another
|
|
18
23
|
// promise, increase promise perf by 10%
|
|
19
24
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"@fastify/pre-commit": "^2.0.2",
|
|
129
129
|
"@sinclair/typebox": "^0.23.5",
|
|
130
130
|
"@sinonjs/fake-timers": "^9.1.2",
|
|
131
|
-
"@types/node": "^
|
|
131
|
+
"@types/node": "^18.0.0",
|
|
132
132
|
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
|
133
133
|
"@typescript-eslint/parser": "^5.27.0",
|
|
134
134
|
"ajv": "^8.11.0",
|
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
"eslint-plugin-n": "^15.2.0",
|
|
147
147
|
"eslint-plugin-promise": "^6.0.0",
|
|
148
148
|
"fast-json-body": "^1.1.0",
|
|
149
|
+
"fast-json-stringify": "^5.0.0",
|
|
149
150
|
"fastify-plugin": "^3.0.1",
|
|
150
151
|
"fluent-json-schema": "^3.1.0",
|
|
151
152
|
"form-data": "^4.0.0",
|
|
@@ -169,7 +170,7 @@
|
|
|
169
170
|
"split2": "^4.1.0",
|
|
170
171
|
"standard": "^17.0.0-2",
|
|
171
172
|
"tap": "^16.2.0",
|
|
172
|
-
"tsd": "^0.
|
|
173
|
+
"tsd": "^0.21.0",
|
|
173
174
|
"typescript": "^4.7.2",
|
|
174
175
|
"undici": "^5.4.0",
|
|
175
176
|
"x-xss-protection": "^2.0.0",
|
|
@@ -178,11 +179,10 @@
|
|
|
178
179
|
"dependencies": {
|
|
179
180
|
"@fastify/ajv-compiler": "^3.1.0",
|
|
180
181
|
"@fastify/error": "^3.0.0",
|
|
181
|
-
"@fastify/fast-json-stringify-compiler": "^
|
|
182
|
+
"@fastify/fast-json-stringify-compiler": "^4.0.0",
|
|
182
183
|
"abstract-logging": "^2.0.1",
|
|
183
184
|
"avvio": "^8.1.3",
|
|
184
|
-
"
|
|
185
|
-
"find-my-way": "^6.3.0",
|
|
185
|
+
"find-my-way": "^7.0.0",
|
|
186
186
|
"light-my-request": "^5.0.0",
|
|
187
187
|
"pino": "^8.0.0",
|
|
188
188
|
"process-warning": "^2.0.0",
|
package/test/404s.test.js
CHANGED
|
@@ -1320,7 +1320,7 @@ test('preHandler option for setNotFoundHandler', t => {
|
|
|
1320
1320
|
|
|
1321
1321
|
// https://github.com/fastify/fastify/issues/2229
|
|
1322
1322
|
t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, t => {
|
|
1323
|
-
t.plan(
|
|
1323
|
+
t.plan(3)
|
|
1324
1324
|
const fastify = Fastify()
|
|
1325
1325
|
|
|
1326
1326
|
fastify.setNotFoundHandler({
|
|
@@ -1333,7 +1333,7 @@ test('preHandler option for setNotFoundHandler', t => {
|
|
|
1333
1333
|
})
|
|
1334
1334
|
|
|
1335
1335
|
fastify.post('/', function (req, reply) {
|
|
1336
|
-
reply.callNotFound()
|
|
1336
|
+
t.equal(reply.callNotFound(), reply)
|
|
1337
1337
|
})
|
|
1338
1338
|
|
|
1339
1339
|
fastify.inject({
|
|
@@ -7,6 +7,10 @@ const path = require('path')
|
|
|
7
7
|
|
|
8
8
|
const { code } = require('../../build/build-error-serializer')
|
|
9
9
|
|
|
10
|
+
function unifyLineBreak (str) {
|
|
11
|
+
return str.toString().replace(/\r\n/g, '\n')
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
test('check generated code syntax', async (t) => {
|
|
11
15
|
t.plan(1)
|
|
12
16
|
|
|
@@ -24,5 +28,6 @@ test('ensure the current error serializer is latest', async (t) => {
|
|
|
24
28
|
|
|
25
29
|
const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js'))
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
// line break should not be a problem depends on system
|
|
32
|
+
t.equal(unifyLineBreak(current), unifyLineBreak(code))
|
|
28
33
|
})
|
package/test/hooks.test.js
CHANGED
|
@@ -799,6 +799,27 @@ test('onRoute hook with many prefix', t => {
|
|
|
799
799
|
fastify.ready(err => { t.error(err) })
|
|
800
800
|
})
|
|
801
801
|
|
|
802
|
+
test('onRoute hook should not be called when it registered after route', t => {
|
|
803
|
+
t.plan(3)
|
|
804
|
+
const fastify = Fastify()
|
|
805
|
+
|
|
806
|
+
fastify.addHook('onRoute', () => {
|
|
807
|
+
t.pass()
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
fastify.get('/', function (req, reply) {
|
|
811
|
+
reply.send()
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
fastify.addHook('onRoute', () => {
|
|
815
|
+
t.fail('should not be called')
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
fastify.ready(err => {
|
|
819
|
+
t.error(err)
|
|
820
|
+
})
|
|
821
|
+
})
|
|
822
|
+
|
|
802
823
|
test('onResponse hook should log request error', t => {
|
|
803
824
|
t.plan(4)
|
|
804
825
|
|
|
@@ -233,6 +233,10 @@ test('within an instance', t => {
|
|
|
233
233
|
reply.redirect('/')
|
|
234
234
|
})
|
|
235
235
|
|
|
236
|
+
fastify.get('/redirect-async', async function (req, reply) {
|
|
237
|
+
return reply.redirect('/')
|
|
238
|
+
})
|
|
239
|
+
|
|
236
240
|
fastify.get('/redirect-code', function (req, reply) {
|
|
237
241
|
reply.redirect(301, '/')
|
|
238
242
|
})
|
|
@@ -412,6 +416,14 @@ test('within an instance', t => {
|
|
|
412
416
|
})
|
|
413
417
|
})
|
|
414
418
|
|
|
419
|
+
test('redirect with async function to `/` - 10', t => {
|
|
420
|
+
t.plan(1)
|
|
421
|
+
|
|
422
|
+
http.get('http://localhost:' + fastify.server.address().port + '/redirect-async', function (response) {
|
|
423
|
+
t.equal(response.statusCode, 302)
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
|
|
415
427
|
t.end()
|
|
416
428
|
})
|
|
417
429
|
})
|
package/test/listen.test.js
CHANGED
|
@@ -196,14 +196,28 @@ if (os.platform() !== 'win32') {
|
|
|
196
196
|
const fastify = Fastify()
|
|
197
197
|
t.teardown(fastify.close.bind(fastify))
|
|
198
198
|
|
|
199
|
-
const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').
|
|
199
|
+
const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`)
|
|
200
200
|
try {
|
|
201
201
|
fs.unlinkSync(sockFile)
|
|
202
202
|
} catch (e) { }
|
|
203
203
|
|
|
204
204
|
fastify.listen({ path: sockFile }, (err, address) => {
|
|
205
205
|
t.error(err)
|
|
206
|
-
t.
|
|
206
|
+
t.strictSame(fastify.addresses(), [sockFile])
|
|
207
|
+
t.equal(address, sockFile)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
} else {
|
|
211
|
+
test('listen on socket', t => {
|
|
212
|
+
t.plan(3)
|
|
213
|
+
const fastify = Fastify()
|
|
214
|
+
t.teardown(fastify.close.bind(fastify))
|
|
215
|
+
|
|
216
|
+
const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock`
|
|
217
|
+
|
|
218
|
+
fastify.listen({ path: sockFile }, (err, address) => {
|
|
219
|
+
t.error(err)
|
|
220
|
+
t.strictSame(fastify.addresses(), [sockFile])
|
|
207
221
|
t.equal(address, sockFile)
|
|
208
222
|
})
|
|
209
223
|
})
|
|
@@ -144,7 +144,7 @@ test('pretty print - commonPrefix', t => {
|
|
|
144
144
|
`
|
|
145
145
|
const flatExpected = `└── / (-)
|
|
146
146
|
├── helicopter (GET, HEAD)
|
|
147
|
-
└── hello (GET,
|
|
147
|
+
└── hello (GET, HEAD, PUT)
|
|
148
148
|
`
|
|
149
149
|
t.equal(typeof radixTree, 'string')
|
|
150
150
|
t.equal(typeof flatTree, 'string')
|
|
@@ -200,7 +200,7 @@ test('pretty print - includeMeta, includeHooks', t => {
|
|
|
200
200
|
│ • (onTimeout) ["onTimeout()"]
|
|
201
201
|
│ • (onRequest) ["anonymous()"]
|
|
202
202
|
│ • (errorHandler) "defaultErrorHandler()"
|
|
203
|
-
└── hello (GET,
|
|
203
|
+
└── hello (GET, HEAD, PUT)
|
|
204
204
|
• (onTimeout) ["onTimeout()"]
|
|
205
205
|
• (onRequest) ["anonymous()"]
|
|
206
206
|
• (errorHandler) "defaultErrorHandler()"
|
|
@@ -210,7 +210,7 @@ test('pretty print - includeMeta, includeHooks', t => {
|
|
|
210
210
|
├── helicopter (GET, HEAD)
|
|
211
211
|
│ • (onTimeout) ["onTimeout()"]
|
|
212
212
|
│ • (onRequest) ["anonymous()"]
|
|
213
|
-
└── hello (GET,
|
|
213
|
+
└── hello (GET, HEAD, PUT)
|
|
214
214
|
• (onTimeout) ["onTimeout()"]
|
|
215
215
|
• (onRequest) ["anonymous()"]
|
|
216
216
|
`
|