fastify 6.0.0-alpha.5 → 6.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/build/build-validation.js +2 -0
- package/docs/Guides/Ecosystem.md +4 -0
- package/docs/Guides/Migration-Guide-V5.md +2 -2
- package/docs/Guides/Serverless.md +33 -10
- package/docs/Reference/Errors.md +13 -0
- package/docs/Reference/Hooks.md +13 -0
- package/docs/Reference/Reply.md +786 -697
- package/docs/Reference/Request.md +303 -107
- package/docs/Reference/Server.md +39 -2
- package/docs/Reference/Validation-and-Serialization.md +15 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +35 -1
- package/lib/config-validator.js +102 -73
- package/lib/content-type-parser.js +14 -2
- package/lib/context.js +2 -1
- package/lib/decorate.js +4 -3
- package/lib/error-handler.js +21 -4
- package/lib/error-status.js +4 -2
- package/lib/errors.js +15 -9
- package/lib/reply.js +12 -11
- package/lib/request.js +3 -2
- package/lib/route.js +34 -5
- package/lib/schemas.js +44 -32
- package/package.json +8 -3
- package/test/close-disposable.test.js +154 -0
- package/test/constrained-routes.test.js +4 -2
- package/test/content-parser.test.js +39 -0
- package/test/decorator.test.js +29 -0
- package/test/diagnostics-channel/error-status.test.js +35 -2
- package/test/handler-timeout.test.js +224 -0
- package/test/internals/all.test.js +5 -0
- package/test/internals/errors.test.js +18 -9
- package/test/internals/initial-config.test.js +2 -0
- package/test/internals/server.test.js +6 -4
- package/test/reply-error.test.js +82 -0
- package/test/reply-trailers.test.js +159 -1
- package/test/router-compile.test.js +241 -0
- package/test/schema-serialization.test.js +120 -7
- package/test/types/fastify.tst.ts +4 -0
- package/test/types/instance.tst.ts +1 -1
- package/test/types/route.tst.ts +16 -0
- package/test/types/using.tst.ts +20 -0
- package/test/validation-error-handling.test.js +21 -1
- package/types/instance.d.ts +5 -1
- package/types/route.d.ts +1 -0
package/lib/schemas.js
CHANGED
|
@@ -8,7 +8,8 @@ const {
|
|
|
8
8
|
FST_ERR_SCH_MISSING_ID,
|
|
9
9
|
FST_ERR_SCH_ALREADY_PRESENT,
|
|
10
10
|
FST_ERR_SCH_DUPLICATE,
|
|
11
|
-
FST_ERR_SCH_CONTENT_MISSING_SCHEMA
|
|
11
|
+
FST_ERR_SCH_CONTENT_MISSING_SCHEMA,
|
|
12
|
+
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
|
|
12
13
|
} = require('./errors')
|
|
13
14
|
const ContentType = require('./content-type')
|
|
14
15
|
|
|
@@ -134,6 +135,40 @@ function generateFluentSchema (schema) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Resolve the right JSON Schema serialization function for a per-media-type
|
|
140
|
+
* (`content`) response schema map, given the response media type.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} contentSchema the compiled per-content-type schema map
|
|
143
|
+
* @param {string} mediaType the response media type (lowercased)
|
|
144
|
+
* @param {string|number} statusCode the resolved http status key
|
|
145
|
+
* @param {boolean} failClosed whether to throw when no schema matches (used
|
|
146
|
+
* for route response serialization) or return false (used by the error
|
|
147
|
+
* handler, which falls back to the default error serializer)
|
|
148
|
+
* @returns {function|false} the serializer function, or false when no schema
|
|
149
|
+
* applies and failClosed is false
|
|
150
|
+
*/
|
|
151
|
+
function getContentSerializer (contentSchema, mediaType, statusCode, failClosed) {
|
|
152
|
+
if (contentSchema[mediaType]) {
|
|
153
|
+
return contentSchema[mediaType]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// fallback to match all media-types
|
|
157
|
+
if (contentSchema['*/*']) {
|
|
158
|
+
return contentSchema['*/*']
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// The response content-type is not declared in the route's content map. When
|
|
162
|
+
// serializing a route response, do not fall back to a raw JSON dump (which
|
|
163
|
+
// would bypass the response schema and expose every field); surface the
|
|
164
|
+
// misconfiguration instead.
|
|
165
|
+
if (failClosed) {
|
|
166
|
+
throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(statusCode, mediaType)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
|
|
137
172
|
/**
|
|
138
173
|
* Search for the right JSON schema compiled function in the request context
|
|
139
174
|
* setup by the route configuration `schema.response`.
|
|
@@ -142,11 +177,15 @@ function generateFluentSchema (schema) {
|
|
|
142
177
|
* @param {object} context the request context
|
|
143
178
|
* @param {number} statusCode the http status code
|
|
144
179
|
* @param {string} [contentType] the reply content type
|
|
180
|
+
* @param {object} [opts] serialization options
|
|
181
|
+
* @param {boolean} [opts.failClosed] when true, throw when the response
|
|
182
|
+
* content-type is not declared in the content map; otherwise return false
|
|
145
183
|
* @returns {function|false} the right JSON Schema function to serialize
|
|
146
184
|
* the reply or false if it is not set
|
|
147
185
|
*/
|
|
148
|
-
function getSchemaSerializer (context, statusCode, contentType) {
|
|
186
|
+
function getSchemaSerializer (context, statusCode, contentType, opts = {}) {
|
|
149
187
|
const responseSchemaDef = context[kSchemaResponse]
|
|
188
|
+
const failClosed = opts.failClosed === true
|
|
150
189
|
if (!responseSchemaDef) {
|
|
151
190
|
return false
|
|
152
191
|
}
|
|
@@ -154,16 +193,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
154
193
|
if (responseSchemaDef[statusCode].constructor === Object) {
|
|
155
194
|
const ct = ContentType.from(contentType)
|
|
156
195
|
if (ct.isValid) {
|
|
157
|
-
|
|
158
|
-
return responseSchemaDef[statusCode][ct.mediaType]
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// fallback to match all media-type
|
|
162
|
-
if (responseSchemaDef[statusCode]['*/*']) {
|
|
163
|
-
return responseSchemaDef[statusCode]['*/*']
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return false
|
|
196
|
+
return getContentSerializer(responseSchemaDef[statusCode], ct.mediaType, statusCode, failClosed)
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
return responseSchemaDef[statusCode]
|
|
@@ -173,16 +203,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
173
203
|
if (responseSchemaDef[fallbackStatusCode].constructor === Object) {
|
|
174
204
|
const ct = ContentType.from(contentType)
|
|
175
205
|
if (ct.isValid) {
|
|
176
|
-
|
|
177
|
-
return responseSchemaDef[fallbackStatusCode][ct.mediaType]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// fallback to match all media-type
|
|
181
|
-
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
|
|
182
|
-
return responseSchemaDef[fallbackStatusCode]['*/*']
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return false
|
|
206
|
+
return getContentSerializer(responseSchemaDef[fallbackStatusCode], ct.mediaType, fallbackStatusCode, failClosed)
|
|
186
207
|
}
|
|
187
208
|
}
|
|
188
209
|
|
|
@@ -192,16 +213,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
192
213
|
if (responseSchemaDef.default.constructor === Object) {
|
|
193
214
|
const ct = ContentType.from(contentType)
|
|
194
215
|
if (ct.isValid) {
|
|
195
|
-
|
|
196
|
-
return responseSchemaDef.default[ct.mediaType]
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// fallback to match all media-type
|
|
200
|
-
if (responseSchemaDef.default['*/*']) {
|
|
201
|
-
return responseSchemaDef.default['*/*']
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return false
|
|
216
|
+
return getContentSerializer(responseSchemaDef.default, ct.mediaType, 'default', failClosed)
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.6",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"lint:fix": "eslint --fix",
|
|
19
19
|
"lint:markdown": "markdownlint-cli2",
|
|
20
20
|
"lint:eslint": "eslint",
|
|
21
|
-
"prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity && npm run build:sync-version",
|
|
21
|
+
"prepublishOnly": "npm run test:version && cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity && npm run build:sync-version",
|
|
22
22
|
"test": "npm run lint && npm run unit && npm run test:types",
|
|
23
23
|
"test:ci": "npm run unit && npm run test:types",
|
|
24
24
|
"test:report": "npm run lint && npm run unit:report && npm run test:types",
|
|
25
25
|
"test:validator:integrity": "npm run build:validation && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/error-serializer.js && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/config-validator.js",
|
|
26
26
|
"test:types": "tstyche",
|
|
27
|
+
"test:version": "node --test test/build/version.test.js",
|
|
27
28
|
"test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse",
|
|
28
29
|
"unit": "borp",
|
|
29
30
|
"unit:report": "c8 --reporter html borp --reporter=@jsumners/line-reporter",
|
|
@@ -148,6 +149,10 @@
|
|
|
148
149
|
"name": "Jean Michelet",
|
|
149
150
|
"email": "jean.antoine.michelet@gmail.com",
|
|
150
151
|
"url": "https://github.com/jean-michelet"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "Antonio Tripodi",
|
|
155
|
+
"url": "https://github.com/Tony133"
|
|
151
156
|
}
|
|
152
157
|
],
|
|
153
158
|
"license": "MIT",
|
|
@@ -210,7 +215,7 @@
|
|
|
210
215
|
"abstract-logging": "^2.0.1",
|
|
211
216
|
"avvio": "^9.0.0",
|
|
212
217
|
"fast-json-stringify": "^7.0.0",
|
|
213
|
-
"find-my-way": "^9.
|
|
218
|
+
"find-my-way": "^9.10.0",
|
|
214
219
|
"light-my-request": "^6.0.0",
|
|
215
220
|
"pino": "^9.14.0 || ^10.1.0",
|
|
216
221
|
"process-warning": "^5.1.0",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const Fastify = require('../fastify')
|
|
5
|
+
|
|
6
|
+
test('addHook onClose supports AsyncDisposable objects', async t => {
|
|
7
|
+
const app = Fastify()
|
|
8
|
+
let disposed = false
|
|
9
|
+
|
|
10
|
+
const resource = {
|
|
11
|
+
async [Symbol.asyncDispose] () {
|
|
12
|
+
disposed = true
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
app.addHook('onClose', resource)
|
|
17
|
+
await app.ready()
|
|
18
|
+
t.assert.equal(disposed, false)
|
|
19
|
+
|
|
20
|
+
await app.close()
|
|
21
|
+
t.assert.equal(disposed, true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('addHook onClose supports synchronous Disposable objects', async t => {
|
|
25
|
+
const app = Fastify()
|
|
26
|
+
let disposed = false
|
|
27
|
+
|
|
28
|
+
const resource = {
|
|
29
|
+
[Symbol.dispose] () {
|
|
30
|
+
disposed = true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
app.addHook('onClose', resource)
|
|
35
|
+
await app.ready()
|
|
36
|
+
t.assert.equal(disposed, false)
|
|
37
|
+
|
|
38
|
+
await app.close()
|
|
39
|
+
t.assert.equal(disposed, true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('addHook onClose prefers Symbol.asyncDispose over Symbol.dispose when both are present', async t => {
|
|
43
|
+
const app = Fastify()
|
|
44
|
+
let asyncDisposed = false
|
|
45
|
+
let syncDisposed = false
|
|
46
|
+
|
|
47
|
+
const resource = {
|
|
48
|
+
async [Symbol.asyncDispose] () {
|
|
49
|
+
asyncDisposed = true
|
|
50
|
+
},
|
|
51
|
+
[Symbol.dispose] () {
|
|
52
|
+
syncDisposed = true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
app.addHook('onClose', resource)
|
|
57
|
+
await app.ready()
|
|
58
|
+
await app.close()
|
|
59
|
+
|
|
60
|
+
t.assert.equal(asyncDisposed, true)
|
|
61
|
+
t.assert.equal(syncDisposed, false)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('addHook onClose propagates rejection from Symbol.asyncDispose', async t => {
|
|
65
|
+
const app = Fastify()
|
|
66
|
+
const customError = new Error('dispose async failed')
|
|
67
|
+
|
|
68
|
+
const resource = {
|
|
69
|
+
async [Symbol.asyncDispose] () {
|
|
70
|
+
throw customError
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
app.addHook('onClose', resource)
|
|
75
|
+
await app.ready()
|
|
76
|
+
|
|
77
|
+
await t.assert.rejects(app.close(), {
|
|
78
|
+
message: 'dispose async failed'
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('addHook onClose ignores fulfillment value from Symbol.asyncDispose', async t => {
|
|
83
|
+
const app = Fastify()
|
|
84
|
+
|
|
85
|
+
const resource = {
|
|
86
|
+
async [Symbol.asyncDispose] () {
|
|
87
|
+
return 'OK'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
app.addHook('onClose', resource)
|
|
92
|
+
await app.ready()
|
|
93
|
+
|
|
94
|
+
await t.assert.doesNotReject(app.close())
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('addHook onClose propagates thrown error from Symbol.dispose', async t => {
|
|
98
|
+
const app = Fastify()
|
|
99
|
+
const customError = new Error('dispose sync failed')
|
|
100
|
+
|
|
101
|
+
const resource = {
|
|
102
|
+
[Symbol.dispose] () {
|
|
103
|
+
throw customError
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
app.addHook('onClose', resource)
|
|
108
|
+
await app.ready()
|
|
109
|
+
|
|
110
|
+
await t.assert.rejects(app.close(), {
|
|
111
|
+
message: 'dispose sync failed'
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('addHook onClose respects plugin execution order for disposables', async t => {
|
|
116
|
+
const app = Fastify()
|
|
117
|
+
const order = []
|
|
118
|
+
|
|
119
|
+
app.register(function childPlugin (instance, opts, done) {
|
|
120
|
+
instance.addHook('onClose', {
|
|
121
|
+
async [Symbol.asyncDispose] () {
|
|
122
|
+
order.push('child')
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
done()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
app.addHook('onClose', {
|
|
129
|
+
async [Symbol.asyncDispose] () {
|
|
130
|
+
order.push('parent')
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await app.ready()
|
|
135
|
+
await app.close()
|
|
136
|
+
|
|
137
|
+
t.assert.deepStrictEqual(order, ['child', 'parent'])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('addHook onClose throws FST_ERR_HOOK_INVALID_HANDLER when non-function non-disposable is passed', t => {
|
|
141
|
+
const app = Fastify()
|
|
142
|
+
|
|
143
|
+
t.assert.throws(() => {
|
|
144
|
+
app.addHook('onClose', 'not-a-function-or-disposable')
|
|
145
|
+
}, {
|
|
146
|
+
code: 'FST_ERR_HOOK_INVALID_HANDLER'
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
t.assert.throws(() => {
|
|
150
|
+
app.addHook('onClose', { someProp: 123 })
|
|
151
|
+
}, {
|
|
152
|
+
code: 'FST_ERR_HOOK_INVALID_HANDLER'
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -607,7 +607,7 @@ test('Should allow registering constrained routes in a prefixed plugin', (t, don
|
|
|
607
607
|
})
|
|
608
608
|
|
|
609
609
|
test('Should allow registering a constrained GET route after a constrained HEAD route', (t, done) => {
|
|
610
|
-
t.plan(
|
|
610
|
+
t.plan(4)
|
|
611
611
|
const fastify = Fastify()
|
|
612
612
|
|
|
613
613
|
fastify.route({
|
|
@@ -637,7 +637,9 @@ test('Should allow registering a constrained GET route after a constrained HEAD
|
|
|
637
637
|
}
|
|
638
638
|
}, (err, res) => {
|
|
639
639
|
t.assert.ifError(err)
|
|
640
|
-
|
|
640
|
+
// A HEAD response has no body, so check the headers set by the custom HEAD handler
|
|
641
|
+
t.assert.strictEqual(res.headers['content-type'], 'text/plain')
|
|
642
|
+
t.assert.strictEqual(res.headers['content-length'], String(Buffer.byteLength('custom HEAD response')))
|
|
641
643
|
t.assert.strictEqual(res.statusCode, 200)
|
|
642
644
|
done()
|
|
643
645
|
})
|
|
@@ -827,3 +827,42 @@ test('string body with invalid UTF-8 is not treated as a content-length mismatch
|
|
|
827
827
|
t.assert.strictEqual(res.statusCode, 200)
|
|
828
828
|
t.assert.strictEqual(res.body, 'a�b')
|
|
829
829
|
})
|
|
830
|
+
|
|
831
|
+
test('a synchronous throw in a custom parser is handled like a route error, not an uncaught exception', async t => {
|
|
832
|
+
await t.test('stream-mode parser', async t => {
|
|
833
|
+
t.plan(2)
|
|
834
|
+
const fastify = Fastify()
|
|
835
|
+
fastify.addContentTypeParser('application/json', function (req, payload, done) {
|
|
836
|
+
throw new Error('parser boom')
|
|
837
|
+
})
|
|
838
|
+
fastify.post('/', (req, reply) => reply.send('ok'))
|
|
839
|
+
const res = await fastify.inject({ method: 'POST', url: '/', headers: { 'content-type': 'application/json' }, payload: '{}' })
|
|
840
|
+
t.assert.strictEqual(res.statusCode, 500)
|
|
841
|
+
t.assert.strictEqual(res.json().message, 'parser boom')
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
await t.test('parseAs string parser', async t => {
|
|
845
|
+
t.plan(2)
|
|
846
|
+
const fastify = Fastify()
|
|
847
|
+
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
|
|
848
|
+
throw new Error('parser boom')
|
|
849
|
+
})
|
|
850
|
+
fastify.post('/', (req, reply) => reply.send('ok'))
|
|
851
|
+
const res = await fastify.inject({ method: 'POST', url: '/', headers: { 'content-type': 'application/json' }, payload: '{}' })
|
|
852
|
+
t.assert.strictEqual(res.statusCode, 500)
|
|
853
|
+
t.assert.strictEqual(res.json().message, 'parser boom')
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
await t.test('a thrown error with a statusCode is preserved', async t => {
|
|
857
|
+
t.plan(1)
|
|
858
|
+
const fastify = Fastify()
|
|
859
|
+
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
|
|
860
|
+
const err = new Error('too big')
|
|
861
|
+
err.statusCode = 413
|
|
862
|
+
throw err
|
|
863
|
+
})
|
|
864
|
+
fastify.post('/', (req, reply) => reply.send('ok'))
|
|
865
|
+
const res = await fastify.inject({ method: 'POST', url: '/', headers: { 'content-type': 'application/json' }, payload: '{}' })
|
|
866
|
+
t.assert.strictEqual(res.statusCode, 413)
|
|
867
|
+
})
|
|
868
|
+
})
|
package/test/decorator.test.js
CHANGED
|
@@ -1328,3 +1328,32 @@ test('Request.setDecorator should update an existing decorator', (t, done) => {
|
|
|
1328
1328
|
})
|
|
1329
1329
|
})
|
|
1330
1330
|
})
|
|
1331
|
+
|
|
1332
|
+
test('getDecorator should evaluate a getter once', async t => {
|
|
1333
|
+
const fastify = Fastify()
|
|
1334
|
+
t.after(() => fastify.close())
|
|
1335
|
+
let calls = 0
|
|
1336
|
+
fastify.decorate('value', {
|
|
1337
|
+
getter () {
|
|
1338
|
+
return ++calls
|
|
1339
|
+
}
|
|
1340
|
+
})
|
|
1341
|
+
|
|
1342
|
+
t.assert.strictEqual(fastify.getDecorator('value'), 1)
|
|
1343
|
+
t.assert.strictEqual(calls, 1)
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
test('getDecorator should bind the function returned by a getter once', async t => {
|
|
1347
|
+
const fastify = Fastify()
|
|
1348
|
+
t.after(() => fastify.close())
|
|
1349
|
+
let calls = 0
|
|
1350
|
+
fastify.decorate('method', {
|
|
1351
|
+
getter () {
|
|
1352
|
+
calls++
|
|
1353
|
+
return function () { return this }
|
|
1354
|
+
}
|
|
1355
|
+
})
|
|
1356
|
+
|
|
1357
|
+
t.assert.strictEqual(fastify.getDecorator('method')(), fastify)
|
|
1358
|
+
t.assert.strictEqual(calls, 1)
|
|
1359
|
+
})
|
|
@@ -96,9 +96,12 @@ test('Error.status property support', (t, done) => {
|
|
|
96
96
|
const err = new Error('winter is coming')
|
|
97
97
|
err.status = 418
|
|
98
98
|
|
|
99
|
-
diagnostics.
|
|
99
|
+
const channel = diagnostics.channel('tracing:fastify.request.handler:error')
|
|
100
|
+
const handler = (msg) => {
|
|
100
101
|
t.assert.strictEqual(msg.error.message, 'winter is coming')
|
|
101
|
-
}
|
|
102
|
+
}
|
|
103
|
+
channel.subscribe(handler)
|
|
104
|
+
t.after(() => channel.unsubscribe(handler))
|
|
102
105
|
|
|
103
106
|
fastify.get('/', () => {
|
|
104
107
|
return Promise.reject(err)
|
|
@@ -121,3 +124,33 @@ test('Error.status property support', (t, done) => {
|
|
|
121
124
|
done()
|
|
122
125
|
})
|
|
123
126
|
})
|
|
127
|
+
|
|
128
|
+
test('diagnostics channel error event should report 500 for error with statusCode >= 600', async (t) => {
|
|
129
|
+
t.plan(4)
|
|
130
|
+
const fastify = Fastify()
|
|
131
|
+
t.after(() => fastify.close())
|
|
132
|
+
|
|
133
|
+
let diagnosticsStatusCode
|
|
134
|
+
let diagnosticsError
|
|
135
|
+
|
|
136
|
+
const channel = diagnostics.channel('tracing:fastify.request.handler:error')
|
|
137
|
+
const handler = (msg) => {
|
|
138
|
+
diagnosticsStatusCode = msg.reply.statusCode
|
|
139
|
+
diagnosticsError = msg.error
|
|
140
|
+
}
|
|
141
|
+
channel.subscribe(handler)
|
|
142
|
+
t.after(() => channel.unsubscribe(handler))
|
|
143
|
+
|
|
144
|
+
fastify.get('/', async () => {
|
|
145
|
+
const err = new Error('custom invalid status')
|
|
146
|
+
err.statusCode = 600
|
|
147
|
+
throw err
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const res = await fastify.inject('/')
|
|
151
|
+
|
|
152
|
+
t.assert.strictEqual(res.statusCode, 500)
|
|
153
|
+
t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel should report 500 when statusCode >= 600')
|
|
154
|
+
t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status')
|
|
155
|
+
t.assert.strictEqual(diagnosticsError.message, 'custom invalid status')
|
|
156
|
+
})
|