node-fastify 5.8.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.
Files changed (354) hide show
  1. package/.borp.yaml +3 -0
  2. package/.markdownlint-cli2.yaml +22 -0
  3. package/.prettierignore +1 -0
  4. package/GOVERNANCE.md +4 -0
  5. package/LICENSE +21 -0
  6. package/PROJECT_CHARTER.md +126 -0
  7. package/README.md +423 -0
  8. package/SECURITY.md +220 -0
  9. package/SPONSORS.md +24 -0
  10. package/build/build-error-serializer.js +35 -0
  11. package/build/build-validation.js +169 -0
  12. package/build/sync-version.js +11 -0
  13. package/docs/Guides/Benchmarking.md +60 -0
  14. package/docs/Guides/Database.md +321 -0
  15. package/docs/Guides/Delay-Accepting-Requests.md +608 -0
  16. package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
  17. package/docs/Guides/Ecosystem.md +726 -0
  18. package/docs/Guides/Fluent-Schema.md +127 -0
  19. package/docs/Guides/Getting-Started.md +620 -0
  20. package/docs/Guides/Index.md +43 -0
  21. package/docs/Guides/Migration-Guide-V3.md +287 -0
  22. package/docs/Guides/Migration-Guide-V4.md +267 -0
  23. package/docs/Guides/Migration-Guide-V5.md +727 -0
  24. package/docs/Guides/Plugins-Guide.md +520 -0
  25. package/docs/Guides/Prototype-Poisoning.md +383 -0
  26. package/docs/Guides/Recommendations.md +378 -0
  27. package/docs/Guides/Serverless.md +604 -0
  28. package/docs/Guides/Style-Guide.md +246 -0
  29. package/docs/Guides/Testing.md +481 -0
  30. package/docs/Guides/Write-Plugin.md +103 -0
  31. package/docs/Guides/Write-Type-Provider.md +34 -0
  32. package/docs/Reference/ContentTypeParser.md +271 -0
  33. package/docs/Reference/Decorators.md +436 -0
  34. package/docs/Reference/Encapsulation.md +194 -0
  35. package/docs/Reference/Errors.md +377 -0
  36. package/docs/Reference/HTTP2.md +94 -0
  37. package/docs/Reference/Hooks.md +958 -0
  38. package/docs/Reference/Index.md +73 -0
  39. package/docs/Reference/LTS.md +86 -0
  40. package/docs/Reference/Lifecycle.md +99 -0
  41. package/docs/Reference/Logging.md +268 -0
  42. package/docs/Reference/Middleware.md +79 -0
  43. package/docs/Reference/Plugins.md +245 -0
  44. package/docs/Reference/Principles.md +73 -0
  45. package/docs/Reference/Reply.md +1001 -0
  46. package/docs/Reference/Request.md +295 -0
  47. package/docs/Reference/Routes.md +802 -0
  48. package/docs/Reference/Server.md +2389 -0
  49. package/docs/Reference/Type-Providers.md +256 -0
  50. package/docs/Reference/TypeScript.md +1729 -0
  51. package/docs/Reference/Validation-and-Serialization.md +1130 -0
  52. package/docs/Reference/Warnings.md +58 -0
  53. package/docs/index.md +24 -0
  54. package/docs/resources/encapsulation_context.drawio +1 -0
  55. package/docs/resources/encapsulation_context.svg +3 -0
  56. package/eslint.config.js +35 -0
  57. package/examples/asyncawait.js +38 -0
  58. package/examples/benchmark/body.json +3 -0
  59. package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
  60. package/examples/benchmark/hooks-benchmark.js +52 -0
  61. package/examples/benchmark/parser.js +47 -0
  62. package/examples/benchmark/simple.js +30 -0
  63. package/examples/benchmark/webstream.js +27 -0
  64. package/examples/hooks.js +91 -0
  65. package/examples/http2.js +39 -0
  66. package/examples/https.js +38 -0
  67. package/examples/parser.js +53 -0
  68. package/examples/plugin.js +12 -0
  69. package/examples/route-prefix.js +38 -0
  70. package/examples/shared-schema.js +38 -0
  71. package/examples/simple-stream.js +20 -0
  72. package/examples/simple.js +32 -0
  73. package/examples/simple.mjs +27 -0
  74. package/examples/typescript-server.ts +79 -0
  75. package/examples/use-plugin.js +29 -0
  76. package/fastify.d.ts +253 -0
  77. package/fastify.js +985 -0
  78. package/integration/server.js +29 -0
  79. package/integration/test.sh +23 -0
  80. package/lib/config-validator.js +1266 -0
  81. package/lib/content-type-parser.js +413 -0
  82. package/lib/content-type.js +160 -0
  83. package/lib/context.js +98 -0
  84. package/lib/decorate.js +152 -0
  85. package/lib/error-handler.js +173 -0
  86. package/lib/error-serializer.js +134 -0
  87. package/lib/error-status.js +14 -0
  88. package/lib/errors.js +516 -0
  89. package/lib/four-oh-four.js +190 -0
  90. package/lib/handle-request.js +195 -0
  91. package/lib/head-route.js +45 -0
  92. package/lib/hooks.js +429 -0
  93. package/lib/initial-config-validation.js +37 -0
  94. package/lib/logger-factory.js +136 -0
  95. package/lib/logger-pino.js +68 -0
  96. package/lib/noop-set.js +10 -0
  97. package/lib/plugin-override.js +90 -0
  98. package/lib/plugin-utils.js +169 -0
  99. package/lib/promise.js +23 -0
  100. package/lib/reply.js +1030 -0
  101. package/lib/req-id-gen-factory.js +52 -0
  102. package/lib/request.js +391 -0
  103. package/lib/route.js +686 -0
  104. package/lib/schema-controller.js +164 -0
  105. package/lib/schemas.js +207 -0
  106. package/lib/server.js +441 -0
  107. package/lib/symbols.js +71 -0
  108. package/lib/validation.js +280 -0
  109. package/lib/warnings.js +57 -0
  110. package/lib/wrap-thenable.js +84 -0
  111. package/package.json +225 -0
  112. package/scripts/validate-ecosystem-links.js +179 -0
  113. package/test/404s.test.js +2035 -0
  114. package/test/500s.test.js +422 -0
  115. package/test/allow-unsafe-regex.test.js +92 -0
  116. package/test/als.test.js +65 -0
  117. package/test/async-await.test.js +705 -0
  118. package/test/async-dispose.test.js +20 -0
  119. package/test/async_hooks.test.js +52 -0
  120. package/test/body-limit.test.js +224 -0
  121. package/test/buffer.test.js +74 -0
  122. package/test/build/error-serializer.test.js +36 -0
  123. package/test/build/version.test.js +14 -0
  124. package/test/build-certificate.js +109 -0
  125. package/test/bundler/README.md +29 -0
  126. package/test/bundler/esbuild/bundler-test.js +32 -0
  127. package/test/bundler/esbuild/package.json +10 -0
  128. package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
  129. package/test/bundler/esbuild/src/index.js +9 -0
  130. package/test/bundler/webpack/bundler-test.js +32 -0
  131. package/test/bundler/webpack/package.json +11 -0
  132. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  133. package/test/bundler/webpack/src/index.js +9 -0
  134. package/test/bundler/webpack/webpack.config.js +15 -0
  135. package/test/case-insensitive.test.js +102 -0
  136. package/test/chainable.test.js +40 -0
  137. package/test/child-logger-factory.test.js +128 -0
  138. package/test/client-timeout.test.js +38 -0
  139. package/test/close-pipelining.test.js +78 -0
  140. package/test/close.test.js +706 -0
  141. package/test/conditional-pino.test.js +47 -0
  142. package/test/connection-timeout.test.js +42 -0
  143. package/test/constrained-routes.test.js +1138 -0
  144. package/test/content-length.test.js +174 -0
  145. package/test/content-parser.test.js +739 -0
  146. package/test/content-type.test.js +181 -0
  147. package/test/context-config.test.js +164 -0
  148. package/test/custom-http-server.test.js +118 -0
  149. package/test/custom-parser-async.test.js +59 -0
  150. package/test/custom-parser.0.test.js +701 -0
  151. package/test/custom-parser.1.test.js +266 -0
  152. package/test/custom-parser.2.test.js +91 -0
  153. package/test/custom-parser.3.test.js +208 -0
  154. package/test/custom-parser.4.test.js +218 -0
  155. package/test/custom-parser.5.test.js +130 -0
  156. package/test/custom-querystring-parser.test.js +129 -0
  157. package/test/decorator.test.js +1330 -0
  158. package/test/delete.test.js +344 -0
  159. package/test/diagnostics-channel/404.test.js +49 -0
  160. package/test/diagnostics-channel/async-delay-request.test.js +65 -0
  161. package/test/diagnostics-channel/async-request.test.js +64 -0
  162. package/test/diagnostics-channel/error-before-handler.test.js +35 -0
  163. package/test/diagnostics-channel/error-request.test.js +53 -0
  164. package/test/diagnostics-channel/error-status.test.js +123 -0
  165. package/test/diagnostics-channel/init.test.js +50 -0
  166. package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
  167. package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
  168. package/test/diagnostics-channel/sync-request.test.js +54 -0
  169. package/test/encapsulated-child-logger-factory.test.js +69 -0
  170. package/test/encapsulated-error-handler.test.js +237 -0
  171. package/test/esm/errorCodes.test.mjs +10 -0
  172. package/test/esm/esm.test.mjs +13 -0
  173. package/test/esm/index.test.js +8 -0
  174. package/test/esm/named-exports.mjs +14 -0
  175. package/test/esm/other.mjs +8 -0
  176. package/test/esm/plugin.mjs +8 -0
  177. package/test/fastify-instance.test.js +300 -0
  178. package/test/find-route.test.js +152 -0
  179. package/test/fluent-schema.test.js +209 -0
  180. package/test/genReqId.test.js +426 -0
  181. package/test/handler-context.test.js +45 -0
  182. package/test/handler-timeout.test.js +367 -0
  183. package/test/has-route.test.js +88 -0
  184. package/test/header-overflow.test.js +55 -0
  185. package/test/helper.js +496 -0
  186. package/test/hooks-async.test.js +1099 -0
  187. package/test/hooks.on-listen.test.js +1162 -0
  188. package/test/hooks.on-ready.test.js +421 -0
  189. package/test/hooks.test.js +3578 -0
  190. package/test/http-methods/copy.test.js +35 -0
  191. package/test/http-methods/custom-http-methods.test.js +114 -0
  192. package/test/http-methods/get.test.js +412 -0
  193. package/test/http-methods/head.test.js +263 -0
  194. package/test/http-methods/lock.test.js +108 -0
  195. package/test/http-methods/mkcalendar.test.js +143 -0
  196. package/test/http-methods/mkcol.test.js +35 -0
  197. package/test/http-methods/move.test.js +42 -0
  198. package/test/http-methods/propfind.test.js +136 -0
  199. package/test/http-methods/proppatch.test.js +105 -0
  200. package/test/http-methods/report.test.js +142 -0
  201. package/test/http-methods/search.test.js +233 -0
  202. package/test/http-methods/trace.test.js +21 -0
  203. package/test/http-methods/unlock.test.js +38 -0
  204. package/test/http2/closing.test.js +270 -0
  205. package/test/http2/constraint.test.js +109 -0
  206. package/test/http2/head.test.js +34 -0
  207. package/test/http2/plain.test.js +68 -0
  208. package/test/http2/secure-with-fallback.test.js +113 -0
  209. package/test/http2/secure.test.js +67 -0
  210. package/test/http2/unknown-http-method.test.js +34 -0
  211. package/test/https/custom-https-server.test.js +58 -0
  212. package/test/https/https.test.js +136 -0
  213. package/test/imports.test.js +17 -0
  214. package/test/inject.test.js +502 -0
  215. package/test/input-validation.js +335 -0
  216. package/test/internals/all.test.js +38 -0
  217. package/test/internals/content-type-parser.test.js +111 -0
  218. package/test/internals/context.test.js +31 -0
  219. package/test/internals/decorator.test.js +156 -0
  220. package/test/internals/errors.test.js +982 -0
  221. package/test/internals/handle-request.test.js +270 -0
  222. package/test/internals/hook-runner.test.js +449 -0
  223. package/test/internals/hooks.test.js +96 -0
  224. package/test/internals/initial-config.test.js +383 -0
  225. package/test/internals/logger.test.js +163 -0
  226. package/test/internals/plugin.test.js +170 -0
  227. package/test/internals/promise.test.js +63 -0
  228. package/test/internals/reply-serialize.test.js +714 -0
  229. package/test/internals/reply.test.js +1920 -0
  230. package/test/internals/req-id-gen-factory.test.js +133 -0
  231. package/test/internals/request-validate.test.js +1402 -0
  232. package/test/internals/request.test.js +506 -0
  233. package/test/internals/schema-controller-perf.test.js +40 -0
  234. package/test/internals/server.test.js +91 -0
  235. package/test/internals/validation.test.js +352 -0
  236. package/test/issue-4959.test.js +118 -0
  237. package/test/keep-alive-timeout.test.js +42 -0
  238. package/test/listen.1.test.js +154 -0
  239. package/test/listen.2.test.js +113 -0
  240. package/test/listen.3.test.js +83 -0
  241. package/test/listen.4.test.js +168 -0
  242. package/test/listen.5.test.js +122 -0
  243. package/test/logger/instantiation.test.js +341 -0
  244. package/test/logger/logger-test-utils.js +47 -0
  245. package/test/logger/logging.test.js +460 -0
  246. package/test/logger/options.test.js +579 -0
  247. package/test/logger/request.test.js +292 -0
  248. package/test/logger/response.test.js +183 -0
  249. package/test/logger/tap-parallel-not-ok +0 -0
  250. package/test/max-requests-per-socket.test.js +113 -0
  251. package/test/middleware.test.js +37 -0
  252. package/test/noop-set.test.js +19 -0
  253. package/test/nullable-validation.test.js +187 -0
  254. package/test/options.error-handler.test.js +5 -0
  255. package/test/options.test.js +5 -0
  256. package/test/output-validation.test.js +140 -0
  257. package/test/patch.error-handler.test.js +5 -0
  258. package/test/patch.test.js +5 -0
  259. package/test/plugin.1.test.js +230 -0
  260. package/test/plugin.2.test.js +314 -0
  261. package/test/plugin.3.test.js +287 -0
  262. package/test/plugin.4.test.js +504 -0
  263. package/test/plugin.helper.js +8 -0
  264. package/test/plugin.name.display.js +10 -0
  265. package/test/post-empty-body.test.js +38 -0
  266. package/test/pretty-print.test.js +366 -0
  267. package/test/promises.test.js +125 -0
  268. package/test/proto-poisoning.test.js +145 -0
  269. package/test/put.error-handler.test.js +5 -0
  270. package/test/put.test.js +5 -0
  271. package/test/register.test.js +184 -0
  272. package/test/reply-code.test.js +148 -0
  273. package/test/reply-early-hints.test.js +100 -0
  274. package/test/reply-error.test.js +815 -0
  275. package/test/reply-trailers.test.js +445 -0
  276. package/test/reply-web-stream-locked.test.js +37 -0
  277. package/test/request-error.test.js +624 -0
  278. package/test/request-header-host.test.js +339 -0
  279. package/test/request-id.test.js +118 -0
  280. package/test/request-timeout.test.js +53 -0
  281. package/test/route-hooks.test.js +635 -0
  282. package/test/route-prefix.test.js +904 -0
  283. package/test/route-shorthand.test.js +48 -0
  284. package/test/route.1.test.js +259 -0
  285. package/test/route.2.test.js +100 -0
  286. package/test/route.3.test.js +213 -0
  287. package/test/route.4.test.js +127 -0
  288. package/test/route.5.test.js +211 -0
  289. package/test/route.6.test.js +306 -0
  290. package/test/route.7.test.js +406 -0
  291. package/test/route.8.test.js +225 -0
  292. package/test/router-options.test.js +1108 -0
  293. package/test/same-shape.test.js +124 -0
  294. package/test/schema-examples.test.js +661 -0
  295. package/test/schema-feature.test.js +2198 -0
  296. package/test/schema-serialization.test.js +1171 -0
  297. package/test/schema-special-usage.test.js +1348 -0
  298. package/test/schema-validation.test.js +1572 -0
  299. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  300. package/test/serialize-response.test.js +186 -0
  301. package/test/server.test.js +347 -0
  302. package/test/set-error-handler.test.js +69 -0
  303. package/test/skip-reply-send.test.js +317 -0
  304. package/test/stream-serializers.test.js +40 -0
  305. package/test/stream.1.test.js +94 -0
  306. package/test/stream.2.test.js +129 -0
  307. package/test/stream.3.test.js +198 -0
  308. package/test/stream.4.test.js +176 -0
  309. package/test/stream.5.test.js +188 -0
  310. package/test/sync-routes.test.js +32 -0
  311. package/test/throw.test.js +359 -0
  312. package/test/toolkit.js +63 -0
  313. package/test/trust-proxy.test.js +162 -0
  314. package/test/type-provider.test.js +22 -0
  315. package/test/types/content-type-parser.test-d.ts +72 -0
  316. package/test/types/decorate-request-reply.test-d.ts +18 -0
  317. package/test/types/dummy-plugin.ts +9 -0
  318. package/test/types/errors.test-d.ts +90 -0
  319. package/test/types/fastify.test-d.ts +352 -0
  320. package/test/types/hooks.test-d.ts +550 -0
  321. package/test/types/import.ts +2 -0
  322. package/test/types/instance.test-d.ts +588 -0
  323. package/test/types/logger.test-d.ts +277 -0
  324. package/test/types/plugin.test-d.ts +97 -0
  325. package/test/types/register.test-d.ts +237 -0
  326. package/test/types/reply.test-d.ts +254 -0
  327. package/test/types/request.test-d.ts +188 -0
  328. package/test/types/route.test-d.ts +553 -0
  329. package/test/types/schema.test-d.ts +135 -0
  330. package/test/types/serverFactory.test-d.ts +37 -0
  331. package/test/types/type-provider.test-d.ts +1213 -0
  332. package/test/types/using.test-d.ts +17 -0
  333. package/test/upgrade.test.js +52 -0
  334. package/test/url-rewriting.test.js +122 -0
  335. package/test/use-semicolon-delimiter.test.js +168 -0
  336. package/test/validation-error-handling.test.js +900 -0
  337. package/test/versioned-routes.test.js +603 -0
  338. package/test/web-api.test.js +616 -0
  339. package/test/wrap-thenable.test.js +30 -0
  340. package/types/content-type-parser.d.ts +75 -0
  341. package/types/context.d.ts +22 -0
  342. package/types/errors.d.ts +92 -0
  343. package/types/hooks.d.ts +875 -0
  344. package/types/instance.d.ts +609 -0
  345. package/types/logger.d.ts +107 -0
  346. package/types/plugin.d.ts +44 -0
  347. package/types/register.d.ts +42 -0
  348. package/types/reply.d.ts +81 -0
  349. package/types/request.d.ts +95 -0
  350. package/types/route.d.ts +199 -0
  351. package/types/schema.d.ts +61 -0
  352. package/types/server-factory.d.ts +19 -0
  353. package/types/type-provider.d.ts +130 -0
  354. package/types/utils.d.ts +98 -0
@@ -0,0 +1,815 @@
1
+ 'use strict'
2
+
3
+ const { test, describe } = require('node:test')
4
+ const net = require('node:net')
5
+ const Fastify = require('..')
6
+ const statusCodes = require('node:http').STATUS_CODES
7
+ const split = require('split2')
8
+ const fs = require('node:fs')
9
+ const path = require('node:path')
10
+
11
+ const codes = Object.keys(statusCodes)
12
+ codes.forEach(code => {
13
+ if (Number(code) >= 400) helper(code)
14
+ })
15
+
16
+ function helper (code) {
17
+ test('Reply error handling - code: ' + code, (t, testDone) => {
18
+ t.plan(4)
19
+ const fastify = Fastify()
20
+ t.after(() => fastify.close())
21
+ const err = new Error('winter is coming')
22
+
23
+ fastify.get('/', (req, reply) => {
24
+ reply
25
+ .code(Number(code))
26
+ .send(err)
27
+ })
28
+
29
+ fastify.inject({
30
+ method: 'GET',
31
+ url: '/'
32
+ }, (error, res) => {
33
+ t.assert.ifError(error)
34
+ t.assert.strictEqual(res.statusCode, Number(code))
35
+ t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
36
+ t.assert.deepStrictEqual(
37
+ {
38
+ error: statusCodes[code],
39
+ message: err.message,
40
+ statusCode: Number(code)
41
+ },
42
+ JSON.parse(res.payload)
43
+ )
44
+ testDone()
45
+ })
46
+ })
47
+ }
48
+
49
+ test('preHandler hook error handling with external code', (t, testDone) => {
50
+ t.plan(3)
51
+ const fastify = Fastify()
52
+ t.after(() => fastify.close())
53
+ const err = new Error('winter is coming')
54
+
55
+ fastify.addHook('preHandler', (req, reply, done) => {
56
+ reply.code(400)
57
+ done(err)
58
+ })
59
+
60
+ fastify.get('/', () => {})
61
+
62
+ fastify.inject({
63
+ method: 'GET',
64
+ url: '/'
65
+ }, (error, res) => {
66
+ t.assert.ifError(error)
67
+ t.assert.strictEqual(res.statusCode, 400)
68
+ t.assert.deepStrictEqual(
69
+ {
70
+ error: statusCodes['400'],
71
+ message: err.message,
72
+ statusCode: 400
73
+ },
74
+ JSON.parse(res.payload)
75
+ )
76
+ testDone()
77
+ })
78
+ })
79
+
80
+ test('onRequest hook error handling with external done', (t, testDone) => {
81
+ t.plan(3)
82
+ const fastify = Fastify()
83
+ t.after(() => fastify.close())
84
+ const err = new Error('winter is coming')
85
+
86
+ fastify.addHook('onRequest', (req, reply, done) => {
87
+ reply.code(400)
88
+ done(err)
89
+ })
90
+
91
+ fastify.get('/', () => {})
92
+
93
+ fastify.inject({
94
+ method: 'GET',
95
+ url: '/'
96
+ }, (error, res) => {
97
+ t.assert.ifError(error)
98
+ t.assert.strictEqual(res.statusCode, 400)
99
+ t.assert.deepStrictEqual(
100
+ {
101
+ error: statusCodes['400'],
102
+ message: err.message,
103
+ statusCode: 400
104
+ },
105
+ JSON.parse(res.payload)
106
+ )
107
+ testDone()
108
+ })
109
+ })
110
+
111
+ test('Should reply 400 on client error', (t, testDone) => {
112
+ t.plan(2)
113
+
114
+ const fastify = Fastify()
115
+ t.after(() => fastify.close())
116
+ fastify.listen({ port: 0, host: '127.0.0.1' }, err => {
117
+ t.assert.ifError(err)
118
+
119
+ const client = net.connect(fastify.server.address().port, '127.0.0.1')
120
+ client.end('oooops!')
121
+
122
+ let chunks = ''
123
+ client.on('data', chunk => {
124
+ chunks += chunk
125
+ })
126
+
127
+ client.once('end', () => {
128
+ const body = JSON.stringify({
129
+ error: 'Bad Request',
130
+ message: 'Client Error',
131
+ statusCode: 400
132
+ })
133
+ t.assert.strictEqual(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks)
134
+ testDone()
135
+ })
136
+ })
137
+ })
138
+
139
+ test('Should set the response from client error handler', (t, testDone) => {
140
+ t.plan(5)
141
+
142
+ const responseBody = JSON.stringify({
143
+ error: 'Ended Request',
144
+ message: 'Serious Client Error',
145
+ statusCode: 400
146
+ })
147
+ const response = `HTTP/1.1 400 Bad Request\r\nContent-Length: ${responseBody.length}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${responseBody}`
148
+
149
+ function clientErrorHandler (err, socket) {
150
+ t.assert.ok(err instanceof Error)
151
+
152
+ this.log.warn({ err }, 'Handled client error')
153
+ socket.end(response)
154
+ }
155
+
156
+ const logStream = split(JSON.parse)
157
+ const fastify = Fastify({
158
+ clientErrorHandler,
159
+ logger: {
160
+ stream: logStream,
161
+ level: 'warn'
162
+ }
163
+ })
164
+
165
+ fastify.listen({ port: 0, host: '127.0.0.1' }, err => {
166
+ t.assert.ifError(err)
167
+ t.after(() => fastify.close())
168
+
169
+ const client = net.connect(fastify.server.address().port, '127.0.0.1')
170
+ client.end('oooops!')
171
+
172
+ let chunks = ''
173
+ client.on('data', chunk => {
174
+ chunks += chunk
175
+ })
176
+
177
+ client.once('end', () => {
178
+ t.assert.strictEqual(response, chunks)
179
+
180
+ testDone()
181
+ })
182
+ })
183
+
184
+ logStream.once('data', line => {
185
+ t.assert.strictEqual('Handled client error', line.msg)
186
+ t.assert.strictEqual(40, line.level, 'Log level is not warn')
187
+ })
188
+ })
189
+
190
+ test('Error instance sets HTTP status code', (t, testDone) => {
191
+ t.plan(3)
192
+ const fastify = Fastify()
193
+ t.after(() => fastify.close())
194
+ const err = new Error('winter is coming')
195
+ err.statusCode = 418
196
+
197
+ fastify.get('/', () => {
198
+ return Promise.reject(err)
199
+ })
200
+
201
+ fastify.inject({
202
+ method: 'GET',
203
+ url: '/'
204
+ }, (error, res) => {
205
+ t.assert.ifError(error)
206
+ t.assert.strictEqual(res.statusCode, 418)
207
+ t.assert.deepStrictEqual(
208
+ {
209
+ error: statusCodes['418'],
210
+ message: err.message,
211
+ statusCode: 418
212
+ },
213
+ JSON.parse(res.payload)
214
+ )
215
+ testDone()
216
+ })
217
+ })
218
+
219
+ test('Error status code below 400 defaults to 500', (t, testDone) => {
220
+ t.plan(3)
221
+ const fastify = Fastify()
222
+ t.after(() => fastify.close())
223
+ const err = new Error('winter is coming')
224
+ err.statusCode = 399
225
+
226
+ fastify.get('/', () => {
227
+ return Promise.reject(err)
228
+ })
229
+
230
+ fastify.inject({
231
+ method: 'GET',
232
+ url: '/'
233
+ }, (error, res) => {
234
+ t.assert.ifError(error)
235
+ t.assert.strictEqual(res.statusCode, 500)
236
+ t.assert.deepStrictEqual(
237
+ {
238
+ error: statusCodes['500'],
239
+ message: err.message,
240
+ statusCode: 500
241
+ },
242
+ JSON.parse(res.payload)
243
+ )
244
+ testDone()
245
+ })
246
+ })
247
+
248
+ test('Error.status property support', (t, testDone) => {
249
+ t.plan(3)
250
+ const fastify = Fastify()
251
+ t.after(() => fastify.close())
252
+ const err = new Error('winter is coming')
253
+ err.status = 418
254
+
255
+ fastify.get('/', () => {
256
+ return Promise.reject(err)
257
+ })
258
+
259
+ fastify.inject({
260
+ method: 'GET',
261
+ url: '/'
262
+ }, (error, res) => {
263
+ t.assert.ifError(error)
264
+ t.assert.strictEqual(res.statusCode, 418)
265
+ t.assert.deepStrictEqual(
266
+ {
267
+ error: statusCodes['418'],
268
+ message: err.message,
269
+ statusCode: 418
270
+ },
271
+ JSON.parse(res.payload)
272
+ )
273
+ testDone()
274
+ })
275
+ })
276
+
277
+ describe('Support rejection with values that are not Error instances', () => {
278
+ const objs = [
279
+ 0,
280
+ '',
281
+ [],
282
+ {},
283
+ null,
284
+ undefined,
285
+ 123,
286
+ 'abc',
287
+ new RegExp(),
288
+ new Date(),
289
+ new Uint8Array()
290
+ ]
291
+ for (const nonErr of objs) {
292
+ test('Type: ' + typeof nonErr, (t, testDone) => {
293
+ t.plan(4)
294
+ const fastify = Fastify()
295
+ t.after(() => fastify.close())
296
+
297
+ fastify.get('/', () => {
298
+ return Promise.reject(nonErr)
299
+ })
300
+
301
+ fastify.setErrorHandler((err, request, reply) => {
302
+ if (typeof err === 'object') {
303
+ t.assert.deepStrictEqual(err, nonErr)
304
+ } else {
305
+ t.assert.strictEqual(err, nonErr)
306
+ }
307
+ reply.code(500).send('error')
308
+ })
309
+
310
+ fastify.inject({
311
+ method: 'GET',
312
+ url: '/'
313
+ }, (error, res) => {
314
+ t.assert.ifError(error)
315
+ t.assert.strictEqual(res.statusCode, 500)
316
+ t.assert.strictEqual(res.payload, 'error')
317
+ testDone()
318
+ })
319
+ })
320
+ }
321
+ })
322
+
323
+ test('invalid schema - ajv', (t, testDone) => {
324
+ t.plan(4)
325
+
326
+ const fastify = Fastify()
327
+ t.after(() => fastify.close())
328
+ fastify.get('/', {
329
+ schema: {
330
+ querystring: {
331
+ type: 'object',
332
+ properties: {
333
+ id: { type: 'number' }
334
+ }
335
+ }
336
+ }
337
+ }, (req, reply) => {
338
+ t.assert.fail('we should not be here')
339
+ })
340
+
341
+ fastify.setErrorHandler((err, request, reply) => {
342
+ t.assert.ok(Array.isArray(err.validation))
343
+ reply.code(400).send('error')
344
+ })
345
+
346
+ fastify.inject({
347
+ url: '/?id=abc',
348
+ method: 'GET'
349
+ }, (err, res) => {
350
+ t.assert.ifError(err)
351
+ t.assert.strictEqual(res.statusCode, 400)
352
+ t.assert.strictEqual(res.payload, 'error')
353
+ testDone()
354
+ })
355
+ })
356
+
357
+ test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', (t, testDone) => {
358
+ t.plan(4)
359
+ const fastify = Fastify()
360
+ t.after(() => fastify.close())
361
+
362
+ fastify.get('/', (req, reply) => {
363
+ const error = new Error('kaboom')
364
+ error.headers = { hello: 'world' }
365
+ error.statusCode = 400
366
+ reply.send(error)
367
+ })
368
+
369
+ fastify.inject({
370
+ url: '/',
371
+ method: 'GET'
372
+ }, (err, res) => {
373
+ t.assert.ifError(err)
374
+ t.assert.strictEqual(res.statusCode, 400)
375
+ t.assert.strictEqual(res.headers.hello, 'world')
376
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
377
+ error: 'Bad Request',
378
+ message: 'kaboom',
379
+ statusCode: 400
380
+ })
381
+
382
+ testDone()
383
+ })
384
+ })
385
+
386
+ test('should set the status code and the headers from the error object (from custom error handler)', (t, testDone) => {
387
+ t.plan(6)
388
+ const fastify = Fastify()
389
+ t.after(() => fastify.close())
390
+
391
+ fastify.get('/', (req, reply) => {
392
+ const error = new Error('ouch')
393
+ error.statusCode = 401
394
+ reply.send(error)
395
+ })
396
+
397
+ fastify.setErrorHandler((err, request, reply) => {
398
+ t.assert.strictEqual(err.message, 'ouch')
399
+ t.assert.strictEqual(reply.raw.statusCode, 200)
400
+ const error = new Error('kaboom')
401
+ error.headers = { hello: 'world' }
402
+ error.statusCode = 400
403
+ reply.send(error)
404
+ })
405
+
406
+ fastify.inject({
407
+ url: '/',
408
+ method: 'GET'
409
+ }, (err, res) => {
410
+ t.assert.ifError(err)
411
+ t.assert.strictEqual(res.statusCode, 400)
412
+ t.assert.strictEqual(res.headers.hello, 'world')
413
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
414
+ error: 'Bad Request',
415
+ message: 'kaboom',
416
+ statusCode: 400
417
+ })
418
+ testDone()
419
+ })
420
+ })
421
+
422
+ // Issue 595 https://github.com/fastify/fastify/issues/595
423
+ test('\'*\' should throw an error due to serializer can not handle the payload type', (t, testDone) => {
424
+ t.plan(3)
425
+ const fastify = Fastify()
426
+ t.after(() => fastify.close())
427
+
428
+ fastify.get('/', (req, reply) => {
429
+ reply.type('text/html')
430
+ try {
431
+ reply.send({})
432
+ } catch (err) {
433
+ t.assert.ok(err instanceof TypeError)
434
+ t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE')
435
+ t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")
436
+ testDone()
437
+ }
438
+ })
439
+
440
+ fastify.inject({
441
+ url: '/',
442
+ method: 'GET'
443
+ }, (e, res) => {
444
+ t.assert.fail('should not be called')
445
+ })
446
+ })
447
+
448
+ test('should throw an error if the custom serializer does not serialize the payload to a valid type', (t, testDone) => {
449
+ t.plan(3)
450
+ const fastify = Fastify()
451
+ t.after(() => fastify.close())
452
+
453
+ fastify.get('/', (req, reply) => {
454
+ try {
455
+ reply
456
+ .type('text/html')
457
+ .serializer(payload => payload)
458
+ .send({})
459
+ } catch (err) {
460
+ t.assert.ok(err instanceof TypeError)
461
+ t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE')
462
+ t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")
463
+ testDone()
464
+ }
465
+ })
466
+
467
+ fastify.inject({
468
+ url: '/',
469
+ method: 'GET'
470
+ }, (e, res) => {
471
+ t.assert.fail('should not be called')
472
+ })
473
+ })
474
+
475
+ test('should not set headers or status code for custom error handler', (t, testDone) => {
476
+ t.plan(7)
477
+
478
+ const fastify = Fastify()
479
+ t.after(() => fastify.close())
480
+ fastify.get('/', function (req, reply) {
481
+ const err = new Error('kaboom')
482
+ err.headers = {
483
+ 'fake-random-header': 'abc'
484
+ }
485
+ reply.send(err)
486
+ })
487
+
488
+ fastify.setErrorHandler(async (err, req, res) => {
489
+ t.assert.strictEqual(res.statusCode, 200)
490
+ t.assert.strictEqual('fake-random-header' in res.headers, false)
491
+ return res.code(500).send(err.message)
492
+ })
493
+
494
+ fastify.inject({
495
+ method: 'GET',
496
+ url: '/'
497
+ }, (err, res) => {
498
+ t.assert.ifError(err)
499
+ t.assert.strictEqual(res.statusCode, 500)
500
+ t.assert.strictEqual('fake-random-header' in res.headers, false)
501
+ t.assert.strictEqual(res.headers['content-length'], ('kaboom'.length).toString())
502
+ t.assert.deepStrictEqual(res.payload, 'kaboom')
503
+ testDone()
504
+ })
505
+ })
506
+
507
+ test('error thrown by custom error handler routes to default error handler', (t, testDone) => {
508
+ t.plan(6)
509
+
510
+ const fastify = Fastify()
511
+ t.after(() => fastify.close())
512
+
513
+ const error = new Error('kaboom')
514
+ error.headers = {
515
+ 'fake-random-header': 'abc'
516
+ }
517
+
518
+ fastify.get('/', function (req, reply) {
519
+ reply.send(error)
520
+ })
521
+
522
+ const newError = new Error('kabong')
523
+
524
+ fastify.setErrorHandler(async (err, req, res) => {
525
+ t.assert.strictEqual(res.statusCode, 200)
526
+ t.assert.strictEqual('fake-random-header' in res.headers, false)
527
+ t.assert.deepStrictEqual(err.headers, error.headers)
528
+
529
+ return res.send(newError)
530
+ })
531
+
532
+ fastify.inject({
533
+ method: 'GET',
534
+ url: '/'
535
+ }, (err, res) => {
536
+ t.assert.ifError(err)
537
+ t.assert.strictEqual(res.statusCode, 500)
538
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
539
+ error: statusCodes['500'],
540
+ message: newError.message,
541
+ statusCode: 500
542
+ })
543
+ testDone()
544
+ })
545
+ })
546
+
547
+ // Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750
548
+ test('allow re-thrown error to default error handler when route handler is async and error handler is sync', (t, testDone) => {
549
+ t.plan(4)
550
+ const fastify = Fastify()
551
+ t.after(() => fastify.close())
552
+
553
+ fastify.setErrorHandler(function (error) {
554
+ t.assert.strictEqual(error.message, 'kaboom')
555
+ throw Error('kabong')
556
+ })
557
+
558
+ fastify.get('/', async function () {
559
+ throw Error('kaboom')
560
+ })
561
+
562
+ fastify.inject({
563
+ url: '/',
564
+ method: 'GET'
565
+ }, (err, res) => {
566
+ t.assert.ifError(err)
567
+ t.assert.strictEqual(res.statusCode, 500)
568
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
569
+ error: statusCodes['500'],
570
+ message: 'kabong',
571
+ statusCode: 500
572
+ })
573
+ testDone()
574
+ })
575
+ })
576
+
577
+ // Issue 2078 https://github.com/fastify/fastify/issues/2078
578
+ // Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
579
+ const invalidErrorCodes = [
580
+ undefined,
581
+ null,
582
+ 'error_code',
583
+
584
+ // out of the 100-599 range:
585
+ 0,
586
+ 1,
587
+ 99,
588
+ 600,
589
+ 700
590
+ ]
591
+ invalidErrorCodes.forEach((invalidCode) => {
592
+ test(`should throw error if error code is ${invalidCode}`, (t, testDone) => {
593
+ t.plan(2)
594
+ const fastify = Fastify()
595
+ t.after(() => fastify.close())
596
+ fastify.get('/', (request, reply) => {
597
+ try {
598
+ return reply.code(invalidCode).send('You should not read this')
599
+ } catch (err) {
600
+ t.assert.strictEqual(err.code, 'FST_ERR_BAD_STATUS_CODE')
601
+ t.assert.strictEqual(err.message, 'Called reply with an invalid status code: ' + invalidCode)
602
+ testDone()
603
+ }
604
+ })
605
+
606
+ fastify.inject({
607
+ url: '/',
608
+ method: 'GET'
609
+ }, (e, res) => {
610
+ t.assert.fail('should not be called')
611
+ })
612
+ })
613
+ })
614
+
615
+ test('error handler is triggered when a string is thrown from sync handler', (t, testDone) => {
616
+ t.plan(3)
617
+
618
+ const fastify = Fastify()
619
+ t.after(() => fastify.close())
620
+
621
+ const throwable = 'test'
622
+ const payload = 'error'
623
+
624
+ fastify.get('/', function (req, reply) {
625
+ throw throwable
626
+ })
627
+
628
+ fastify.setErrorHandler((err, req, res) => {
629
+ t.assert.strictEqual(err, throwable)
630
+
631
+ res.send(payload)
632
+ })
633
+
634
+ fastify.inject({
635
+ method: 'GET',
636
+ url: '/'
637
+ }, (err, res) => {
638
+ t.assert.ifError(err)
639
+ t.assert.strictEqual(res.payload, payload)
640
+ testDone()
641
+ })
642
+ })
643
+
644
+ test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => {
645
+ t.plan(2)
646
+ const fastify = Fastify()
647
+ t.after(() => fastify.close())
648
+
649
+ fastify.get('/', () => {
650
+ /* eslint-disable-next-line */
651
+ throw { foo: 'bar' }
652
+ })
653
+
654
+ // ----
655
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
656
+ t.assert.strictEqual(reply.statusCode, 500)
657
+ t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar')
658
+ })
659
+
660
+ test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => {
661
+ t.plan(2)
662
+ const fastify = Fastify()
663
+ t.after(() => fastify.close())
664
+
665
+ fastify.get('/', (_, rep) => {
666
+ rep.status(501)
667
+
668
+ /* eslint-disable-next-line */
669
+ throw { foo: 'bar' }
670
+ })
671
+
672
+ // ----
673
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
674
+ t.assert.strictEqual(reply.statusCode, 501)
675
+ t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar')
676
+ })
677
+
678
+ test('should trigger error handlers if a sync route throws any non-error object', async t => {
679
+ t.plan(2)
680
+
681
+ const fastify = Fastify()
682
+ t.after(() => fastify.close())
683
+
684
+ const throwable = 'test'
685
+ const payload = 'error'
686
+
687
+ fastify.get('/', function async (req, reply) {
688
+ throw throwable
689
+ })
690
+
691
+ fastify.setErrorHandler((err, req, res) => {
692
+ t.assert.strictEqual(err, throwable)
693
+ res.code(500).send(payload)
694
+ })
695
+
696
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
697
+ t.assert.strictEqual(reply.statusCode, 500)
698
+ })
699
+
700
+ test('should trigger error handlers if a sync route throws undefined', async t => {
701
+ t.plan(1)
702
+
703
+ const fastify = Fastify()
704
+ t.after(() => fastify.close())
705
+
706
+ fastify.get('/', function async (req, reply) {
707
+ // eslint-disable-next-line no-throw-literal
708
+ throw undefined
709
+ })
710
+
711
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
712
+ t.assert.strictEqual(reply.statusCode, 500)
713
+ })
714
+
715
+ test('setting content-type on reply object should not hang the server case 1', (t, testDone) => {
716
+ t.plan(2)
717
+ const fastify = Fastify()
718
+ t.after(() => fastify.close())
719
+
720
+ fastify.get('/', (req, reply) => {
721
+ reply
722
+ .code(200)
723
+ .headers({ 'content-type': 'text/plain; charset=utf-32' })
724
+ .send(JSON.stringify({ bar: 'foo', baz: 'foobar' }))
725
+ })
726
+
727
+ fastify.inject({
728
+ url: '/',
729
+ method: 'GET'
730
+ }, (err, res) => {
731
+ t.assert.ifError(err)
732
+ t.assert.strictEqual(res.statusCode, 200)
733
+ testDone()
734
+ })
735
+ })
736
+
737
+ test('setting content-type on reply object should not hang the server case 2', async t => {
738
+ t.plan(1)
739
+ const fastify = Fastify()
740
+ t.after(() => fastify.close())
741
+
742
+ fastify.get('/', (req, reply) => {
743
+ reply
744
+ .code(200)
745
+ .headers({ 'content-type': 'text/plain; charset=utf-8' })
746
+ .send({ bar: 'foo', baz: 'foobar' })
747
+ })
748
+
749
+ try {
750
+ await fastify.ready()
751
+ const res = await fastify.inject({
752
+ url: '/',
753
+ method: 'GET'
754
+ })
755
+ t.assert.deepStrictEqual({
756
+ error: 'Internal Server Error',
757
+ message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
758
+ statusCode: 500,
759
+ code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE'
760
+ },
761
+ res.json())
762
+ } catch (error) {
763
+ t.assert.ifError(error)
764
+ }
765
+ })
766
+
767
+ test('setting content-type on reply object should not hang the server case 3', (t, testDone) => {
768
+ t.plan(2)
769
+ const fastify = Fastify()
770
+ t.after(() => fastify.close())
771
+
772
+ fastify.get('/', (req, reply) => {
773
+ reply
774
+ .code(200)
775
+ .headers({ 'content-type': 'application/json' })
776
+ .send({ bar: 'foo', baz: 'foobar' })
777
+ })
778
+
779
+ fastify.inject({
780
+ url: '/',
781
+ method: 'GET'
782
+ }, (err, res) => {
783
+ t.assert.ifError(err)
784
+ t.assert.strictEqual(res.statusCode, 200)
785
+ testDone()
786
+ })
787
+ })
788
+
789
+ test('pipe stream inside error handler should not cause error', (t, testDone) => {
790
+ t.plan(3)
791
+ const location = path.join(__dirname, '..', 'package.json')
792
+ const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8'))
793
+
794
+ const fastify = Fastify()
795
+ t.after(() => fastify.close())
796
+
797
+ fastify.setErrorHandler((_error, _request, reply) => {
798
+ const stream = fs.createReadStream(location)
799
+ reply.code(400).type('application/json; charset=utf-8').send(stream)
800
+ })
801
+
802
+ fastify.get('/', (request, reply) => {
803
+ throw new Error('This is an error.')
804
+ })
805
+
806
+ fastify.inject({
807
+ url: '/',
808
+ method: 'GET'
809
+ }, (err, res) => {
810
+ t.assert.ifError(err)
811
+ t.assert.strictEqual(res.statusCode, 400)
812
+ t.assert.deepStrictEqual(JSON.parse(res.payload), json)
813
+ testDone()
814
+ })
815
+ })