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,900 @@
1
+ 'use strict'
2
+
3
+ const { describe, test } = require('node:test')
4
+ const Joi = require('joi')
5
+ const Fastify = require('..')
6
+
7
+ const schema = {
8
+ body: {
9
+ type: 'object',
10
+ properties: {
11
+ name: { type: 'string' },
12
+ work: { type: 'string' }
13
+ },
14
+ required: ['name', 'work']
15
+ }
16
+ }
17
+
18
+ function echoBody (req, reply) {
19
+ reply.code(200).send(req.body.name)
20
+ }
21
+
22
+ test('should work with valid payload', async (t) => {
23
+ t.plan(2)
24
+
25
+ const fastify = Fastify()
26
+
27
+ fastify.post('/', { schema }, echoBody)
28
+
29
+ const response = await fastify.inject({
30
+ method: 'POST',
31
+ payload: {
32
+ name: 'michelangelo',
33
+ work: 'sculptor, painter, architect and poet'
34
+ },
35
+ url: '/'
36
+ })
37
+ t.assert.deepStrictEqual(response.payload, 'michelangelo')
38
+ t.assert.strictEqual(response.statusCode, 200)
39
+ })
40
+
41
+ test('should fail immediately with invalid payload', async (t) => {
42
+ t.plan(2)
43
+
44
+ const fastify = Fastify()
45
+
46
+ fastify.post('/', { schema }, echoBody)
47
+
48
+ const response = await fastify.inject({
49
+ method: 'POST',
50
+ payload: {
51
+ hello: 'michelangelo'
52
+ },
53
+ url: '/'
54
+ })
55
+
56
+ t.assert.deepStrictEqual(response.json(), {
57
+ statusCode: 400,
58
+ code: 'FST_ERR_VALIDATION',
59
+ error: 'Bad Request',
60
+ message: "body must have required property 'name'"
61
+ })
62
+ t.assert.strictEqual(response.statusCode, 400)
63
+ })
64
+
65
+ test('should be able to use setErrorHandler specify custom validation error', async (t) => {
66
+ t.plan(2)
67
+
68
+ const fastify = Fastify()
69
+
70
+ fastify.post('/', { schema }, function (req, reply) {
71
+ t.assert.fail('should not be here')
72
+ reply.code(200).send(req.body.name)
73
+ })
74
+
75
+ fastify.setErrorHandler(function (error, request, reply) {
76
+ if (error.validation) {
77
+ reply.status(422).send(new Error('validation failed'))
78
+ }
79
+ })
80
+
81
+ const response = await fastify.inject({
82
+ method: 'POST',
83
+ payload: {
84
+ hello: 'michelangelo'
85
+ },
86
+ url: '/'
87
+ })
88
+
89
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
90
+ statusCode: 422,
91
+ error: 'Unprocessable Entity',
92
+ message: 'validation failed'
93
+ })
94
+ t.assert.strictEqual(response.statusCode, 422)
95
+ })
96
+
97
+ test('validation error has 400 statusCode set', async (t) => {
98
+ t.plan(2)
99
+
100
+ const fastify = Fastify()
101
+
102
+ fastify.setErrorHandler((error, request, reply) => {
103
+ const errorResponse = {
104
+ message: error.message,
105
+ statusCode: error.statusCode || 500
106
+ }
107
+
108
+ reply.code(errorResponse.statusCode).send(errorResponse)
109
+ })
110
+
111
+ fastify.post('/', { schema }, echoBody)
112
+
113
+ const response = await fastify.inject({
114
+ method: 'POST',
115
+ payload: {
116
+ hello: 'michelangelo'
117
+ },
118
+ url: '/'
119
+ })
120
+
121
+ t.assert.deepStrictEqual(response.json(), {
122
+ statusCode: 400,
123
+ message: "body must have required property 'name'"
124
+ })
125
+ t.assert.strictEqual(response.statusCode, 400)
126
+ })
127
+
128
+ test('error inside custom error handler should have validationContext', async (t) => {
129
+ t.plan(1)
130
+
131
+ const fastify = Fastify()
132
+
133
+ fastify.post('/', {
134
+ schema,
135
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
136
+ return function (data) {
137
+ return { error: new Error('this failed') }
138
+ }
139
+ }
140
+ }, function (req, reply) {
141
+ t.assert.fail('should not be here')
142
+ reply.code(200).send(req.body.name)
143
+ })
144
+
145
+ fastify.setErrorHandler(function (error, request, reply) {
146
+ t.assert.strictEqual(error.validationContext, 'body')
147
+ reply.status(500).send(error)
148
+ })
149
+
150
+ await fastify.inject({
151
+ method: 'POST',
152
+ payload: {
153
+ name: 'michelangelo',
154
+ work: 'artist'
155
+ },
156
+ url: '/'
157
+ })
158
+ })
159
+
160
+ test('error inside custom error handler should have validationContext if specified by custom error handler', async (t) => {
161
+ t.plan(1)
162
+
163
+ const fastify = Fastify()
164
+
165
+ fastify.post('/', {
166
+ schema,
167
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
168
+ return function (data) {
169
+ const error = new Error('this failed')
170
+ error.validationContext = 'customContext'
171
+ return { error }
172
+ }
173
+ }
174
+ }, function (req, reply) {
175
+ t.assert.fail('should not be here')
176
+ reply.code(200).send(req.body.name)
177
+ })
178
+
179
+ fastify.setErrorHandler(function (error, request, reply) {
180
+ t.assert.strictEqual(error.validationContext, 'customContext')
181
+ reply.status(500).send(error)
182
+ })
183
+
184
+ await fastify.inject({
185
+ method: 'POST',
186
+ payload: {
187
+ name: 'michelangelo',
188
+ work: 'artist'
189
+ },
190
+ url: '/'
191
+ })
192
+ })
193
+
194
+ test('should be able to attach validation to request', async (t) => {
195
+ t.plan(2)
196
+
197
+ const fastify = Fastify()
198
+
199
+ fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
200
+ reply.code(400).send(req.validationError.validation)
201
+ })
202
+
203
+ const response = await fastify.inject({
204
+ method: 'POST',
205
+ payload: {
206
+ hello: 'michelangelo'
207
+ },
208
+ url: '/'
209
+ })
210
+
211
+ t.assert.deepStrictEqual(response.json(), [{
212
+ keyword: 'required',
213
+ instancePath: '',
214
+ schemaPath: '#/required',
215
+ params: { missingProperty: 'name' },
216
+ message: 'must have required property \'name\''
217
+ }])
218
+ t.assert.strictEqual(response.statusCode, 400)
219
+ })
220
+
221
+ test('should respect when attachValidation is explicitly set to false', async (t) => {
222
+ t.plan(2)
223
+
224
+ const fastify = Fastify()
225
+
226
+ fastify.post('/', { schema, attachValidation: false }, function (req, reply) {
227
+ t.assert.fail('should not be here')
228
+ reply.code(200).send(req.validationError.validation)
229
+ })
230
+
231
+ const response = await fastify.inject({
232
+ method: 'POST',
233
+ payload: {
234
+ hello: 'michelangelo'
235
+ },
236
+ url: '/'
237
+ })
238
+
239
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
240
+ statusCode: 400,
241
+ code: 'FST_ERR_VALIDATION',
242
+ error: 'Bad Request',
243
+ message: "body must have required property 'name'"
244
+ })
245
+ t.assert.strictEqual(response.statusCode, 400)
246
+ })
247
+
248
+ test('Attached validation error should take precedence over setErrorHandler', async (t) => {
249
+ t.plan(2)
250
+
251
+ const fastify = Fastify()
252
+
253
+ fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
254
+ reply.code(400).send('Attached: ' + req.validationError)
255
+ })
256
+
257
+ fastify.setErrorHandler(function (error, request, reply) {
258
+ t.assert.fail('should not be here')
259
+ if (error.validation) {
260
+ reply.status(422).send(new Error('validation failed'))
261
+ }
262
+ })
263
+
264
+ const response = await fastify.inject({
265
+ method: 'POST',
266
+ payload: {
267
+ hello: 'michelangelo'
268
+ },
269
+ url: '/'
270
+ })
271
+
272
+ t.assert.deepStrictEqual(response.payload, "Attached: Error: body must have required property 'name'")
273
+ t.assert.strictEqual(response.statusCode, 400)
274
+ })
275
+
276
+ test('should handle response validation error', async (t) => {
277
+ t.plan(2)
278
+
279
+ const response = {
280
+ 200: {
281
+ type: 'object',
282
+ required: ['name', 'work'],
283
+ properties: {
284
+ name: { type: 'string' },
285
+ work: { type: 'string' }
286
+ }
287
+ }
288
+ }
289
+
290
+ const fastify = Fastify()
291
+
292
+ fastify.get('/', { schema: { response } }, function (req, reply) {
293
+ try {
294
+ reply.code(200).send({ work: 'actor' })
295
+ } catch (error) {
296
+ reply.code(500).send(error)
297
+ }
298
+ })
299
+
300
+ const injectResponse = await fastify.inject({
301
+ method: 'GET',
302
+ payload: { },
303
+ url: '/'
304
+ })
305
+
306
+ t.assert.strictEqual(injectResponse.statusCode, 500)
307
+ t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')
308
+ })
309
+
310
+ test('should handle response validation error with promises', async (t) => {
311
+ t.plan(2)
312
+
313
+ const response = {
314
+ 200: {
315
+ type: 'object',
316
+ required: ['name', 'work'],
317
+ properties: {
318
+ name: { type: 'string' },
319
+ work: { type: 'string' }
320
+ }
321
+ }
322
+ }
323
+
324
+ const fastify = Fastify()
325
+
326
+ fastify.get('/', { schema: { response } }, function (req, reply) {
327
+ return Promise.resolve({ work: 'actor' })
328
+ })
329
+
330
+ const injectResponse = await fastify.inject({
331
+ method: 'GET',
332
+ payload: { },
333
+ url: '/'
334
+ })
335
+
336
+ t.assert.strictEqual(injectResponse.statusCode, 500)
337
+ t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')
338
+ })
339
+
340
+ test('should return a defined output message parsing AJV errors', async (t) => {
341
+ t.plan(2)
342
+
343
+ const body = {
344
+ type: 'object',
345
+ required: ['name', 'work'],
346
+ properties: {
347
+ name: { type: 'string' },
348
+ work: { type: 'string' }
349
+ }
350
+ }
351
+
352
+ const fastify = Fastify()
353
+
354
+ fastify.post('/', { schema: { body } }, function (req, reply) {
355
+ t.assert.fail()
356
+ })
357
+
358
+ const response = await fastify.inject({
359
+ method: 'POST',
360
+ payload: { },
361
+ url: '/'
362
+ })
363
+
364
+ t.assert.strictEqual(response.statusCode, 400)
365
+ t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}')
366
+ })
367
+
368
+ test('should return a defined output message parsing JOI errors', async (t) => {
369
+ t.plan(2)
370
+
371
+ const body = Joi.object().keys({
372
+ name: Joi.string().required(),
373
+ work: Joi.string().required()
374
+ }).required()
375
+
376
+ const fastify = Fastify()
377
+
378
+ fastify.post('/', {
379
+ schema: { body },
380
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
381
+ return data => schema.validate(data)
382
+ }
383
+ },
384
+ function (req, reply) {
385
+ t.assert.fail()
386
+ })
387
+
388
+ const response = await fastify.inject({
389
+ method: 'POST',
390
+ payload: {},
391
+ url: '/'
392
+ })
393
+
394
+ t.assert.strictEqual(response.statusCode, 400)
395
+ t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}')
396
+ })
397
+
398
+ test('should return a defined output message parsing JOI error details', async (t) => {
399
+ t.plan(2)
400
+
401
+ const body = Joi.object().keys({
402
+ name: Joi.string().required(),
403
+ work: Joi.string().required()
404
+ }).required()
405
+
406
+ const fastify = Fastify()
407
+
408
+ fastify.post('/', {
409
+ schema: { body },
410
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
411
+ return data => {
412
+ const validation = schema.validate(data)
413
+ return { error: validation.error.details }
414
+ }
415
+ }
416
+ },
417
+ function (req, reply) {
418
+ t.assert.fail()
419
+ })
420
+
421
+ const response = await fastify.inject({
422
+ method: 'POST',
423
+ payload: {},
424
+ url: '/'
425
+ })
426
+
427
+ t.assert.strictEqual(response.statusCode, 400)
428
+ t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}')
429
+ })
430
+
431
+ test('the custom error formatter context must be the server instance', async (t) => {
432
+ t.plan(3)
433
+
434
+ const fastify = Fastify()
435
+
436
+ fastify.setSchemaErrorFormatter(function (errors, dataVar) {
437
+ t.assert.deepStrictEqual(this, fastify)
438
+ return new Error('my error')
439
+ })
440
+
441
+ fastify.post('/', { schema }, echoBody)
442
+
443
+ const response = await fastify.inject({
444
+ method: 'POST',
445
+ payload: {
446
+ hello: 'michelangelo'
447
+ },
448
+ url: '/'
449
+ })
450
+
451
+ t.assert.deepStrictEqual(response.json(), {
452
+ statusCode: 400,
453
+ code: 'FST_ERR_VALIDATION',
454
+ error: 'Bad Request',
455
+ message: 'my error'
456
+ })
457
+ t.assert.strictEqual(response.statusCode, 400)
458
+ })
459
+
460
+ test('the custom error formatter context must be the server instance in options', async (t) => {
461
+ t.plan(3)
462
+
463
+ const fastify = Fastify({
464
+ schemaErrorFormatter: function (errors, dataVar) {
465
+ t.assert.deepStrictEqual(this, fastify)
466
+ return new Error('my error')
467
+ }
468
+ })
469
+
470
+ fastify.post('/', { schema }, echoBody)
471
+
472
+ const response = await fastify.inject({
473
+ method: 'POST',
474
+ payload: {
475
+ hello: 'michelangelo'
476
+ },
477
+ url: '/'
478
+ })
479
+
480
+ t.assert.deepStrictEqual(response.json(), {
481
+ statusCode: 400,
482
+ code: 'FST_ERR_VALIDATION',
483
+ error: 'Bad Request',
484
+ message: 'my error'
485
+ })
486
+ t.assert.strictEqual(response.statusCode, 400)
487
+ })
488
+
489
+ test('should call custom error formatter', async (t) => {
490
+ t.plan(8)
491
+
492
+ const fastify = Fastify({
493
+ schemaErrorFormatter: (errors, dataVar) => {
494
+ t.assert.strictEqual(errors.length, 1)
495
+ t.assert.strictEqual(errors[0].message, "must have required property 'name'")
496
+ t.assert.strictEqual(errors[0].keyword, 'required')
497
+ t.assert.strictEqual(errors[0].schemaPath, '#/required')
498
+ t.assert.deepStrictEqual(errors[0].params, {
499
+ missingProperty: 'name'
500
+ })
501
+ t.assert.strictEqual(dataVar, 'body')
502
+ return new Error('my error')
503
+ }
504
+ })
505
+
506
+ fastify.post('/', { schema }, echoBody)
507
+
508
+ const response = await fastify.inject({
509
+ method: 'POST',
510
+ payload: {
511
+ hello: 'michelangelo'
512
+ },
513
+ url: '/'
514
+ })
515
+
516
+ t.assert.deepStrictEqual(response.json(), {
517
+ statusCode: 400,
518
+ code: 'FST_ERR_VALIDATION',
519
+ error: 'Bad Request',
520
+ message: 'my error'
521
+ })
522
+ t.assert.strictEqual(response.statusCode, 400)
523
+ })
524
+
525
+ test('should catch error inside formatter and return message', async (t) => {
526
+ t.plan(2)
527
+
528
+ const fastify = Fastify({
529
+ schemaErrorFormatter: (errors, dataVar) => {
530
+ throw new Error('abc')
531
+ }
532
+ })
533
+
534
+ fastify.post('/', { schema }, echoBody)
535
+
536
+ const response = await fastify.inject({
537
+ method: 'POST',
538
+ payload: {
539
+ hello: 'michelangelo'
540
+ },
541
+ url: '/'
542
+ })
543
+
544
+ t.assert.deepStrictEqual(response.json(), {
545
+ statusCode: 500,
546
+ error: 'Internal Server Error',
547
+ message: 'abc'
548
+ })
549
+ t.assert.strictEqual(response.statusCode, 500)
550
+ })
551
+
552
+ test('cannot create a fastify instance with wrong type of errorFormatter', async (t) => {
553
+ t.plan(3)
554
+
555
+ try {
556
+ Fastify({
557
+ schemaErrorFormatter: async (errors, dataVar) => {
558
+ return new Error('should not execute')
559
+ }
560
+ })
561
+ } catch (err) {
562
+ t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')
563
+ }
564
+
565
+ try {
566
+ Fastify({
567
+ schemaErrorFormatter: 500
568
+ })
569
+ } catch (err) {
570
+ t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')
571
+ }
572
+
573
+ try {
574
+ const fastify = Fastify()
575
+ fastify.setSchemaErrorFormatter(500)
576
+ } catch (err) {
577
+ t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')
578
+ }
579
+ })
580
+
581
+ test('should register a route based schema error formatter', async (t) => {
582
+ t.plan(2)
583
+
584
+ const fastify = Fastify()
585
+
586
+ fastify.post('/', {
587
+ schema,
588
+ schemaErrorFormatter: (errors, dataVar) => {
589
+ return new Error('abc')
590
+ }
591
+ }, echoBody)
592
+
593
+ const response = await fastify.inject({
594
+ method: 'POST',
595
+ payload: {
596
+ hello: 'michelangelo'
597
+ },
598
+ url: '/'
599
+ })
600
+
601
+ t.assert.deepStrictEqual(response.json(), {
602
+ statusCode: 400,
603
+ code: 'FST_ERR_VALIDATION',
604
+ error: 'Bad Request',
605
+ message: 'abc'
606
+ })
607
+ t.assert.strictEqual(response.statusCode, 400)
608
+ })
609
+
610
+ test('prefer route based error formatter over global one', async (t) => {
611
+ t.plan(6)
612
+
613
+ const fastify = Fastify({
614
+ schemaErrorFormatter: (errors, dataVar) => {
615
+ return new Error('abc123')
616
+ }
617
+ })
618
+
619
+ fastify.post('/', {
620
+ schema,
621
+ schemaErrorFormatter: (errors, dataVar) => {
622
+ return new Error('123')
623
+ }
624
+ }, echoBody)
625
+
626
+ fastify.post('/abc', {
627
+ schema,
628
+ schemaErrorFormatter: (errors, dataVar) => {
629
+ return new Error('abc')
630
+ }
631
+ }, echoBody)
632
+
633
+ fastify.post('/test', { schema }, echoBody)
634
+
635
+ const response1 = await fastify.inject({
636
+ method: 'POST',
637
+ payload: {
638
+ hello: 'michelangelo'
639
+ },
640
+ url: '/'
641
+ })
642
+
643
+ t.assert.deepStrictEqual(response1.json(), {
644
+ statusCode: 400,
645
+ code: 'FST_ERR_VALIDATION',
646
+ error: 'Bad Request',
647
+ message: '123'
648
+ })
649
+ t.assert.strictEqual(response1.statusCode, 400)
650
+
651
+ const response2 = await fastify.inject({
652
+ method: 'POST',
653
+ payload: {
654
+ hello: 'michelangelo'
655
+ },
656
+ url: '/abc'
657
+ })
658
+
659
+ t.assert.deepStrictEqual(response2.json(), {
660
+ statusCode: 400,
661
+ code: 'FST_ERR_VALIDATION',
662
+ error: 'Bad Request',
663
+ message: 'abc'
664
+ })
665
+ t.assert.strictEqual(response2.statusCode, 400)
666
+
667
+ const response3 = await fastify.inject({
668
+ method: 'POST',
669
+ payload: {
670
+ hello: 'michelangelo'
671
+ },
672
+ url: '/test'
673
+ })
674
+
675
+ t.assert.deepStrictEqual(response3.json(), {
676
+ statusCode: 400,
677
+ code: 'FST_ERR_VALIDATION',
678
+ error: 'Bad Request',
679
+ message: 'abc123'
680
+ })
681
+ t.assert.strictEqual(response3.statusCode, 400)
682
+ })
683
+
684
+ test('adding schemaErrorFormatter', async (t) => {
685
+ t.plan(2)
686
+
687
+ const fastify = Fastify()
688
+
689
+ fastify.setSchemaErrorFormatter((errors, dataVar) => {
690
+ return new Error('abc')
691
+ })
692
+
693
+ fastify.post('/', { schema }, echoBody)
694
+
695
+ const response = await fastify.inject({
696
+ method: 'POST',
697
+ payload: {
698
+ hello: 'michelangelo'
699
+ },
700
+ url: '/'
701
+ })
702
+
703
+ t.assert.deepStrictEqual(response.json(), {
704
+ statusCode: 400,
705
+ code: 'FST_ERR_VALIDATION',
706
+ error: 'Bad Request',
707
+ message: 'abc'
708
+ })
709
+ t.assert.strictEqual(response.statusCode, 400)
710
+ })
711
+
712
+ test('plugin override', async (t) => {
713
+ t.plan(10)
714
+
715
+ const fastify = Fastify({
716
+ schemaErrorFormatter: (errors, dataVar) => {
717
+ return new Error('B')
718
+ }
719
+ })
720
+
721
+ fastify.register((instance, opts, done) => {
722
+ instance.setSchemaErrorFormatter((errors, dataVar) => {
723
+ return new Error('C')
724
+ })
725
+
726
+ instance.post('/d', {
727
+ schema,
728
+ schemaErrorFormatter: (errors, dataVar) => {
729
+ return new Error('D')
730
+ }
731
+ }, function (req, reply) {
732
+ reply.code(200).send(req.body.name)
733
+ })
734
+
735
+ instance.post('/c', { schema }, echoBody)
736
+
737
+ instance.register((subinstance, opts, done) => {
738
+ subinstance.post('/stillC', { schema }, echoBody)
739
+ done()
740
+ })
741
+
742
+ done()
743
+ })
744
+
745
+ fastify.post('/b', { schema }, echoBody)
746
+
747
+ fastify.post('/', {
748
+ schema,
749
+ schemaErrorFormatter: (errors, dataVar) => {
750
+ return new Error('A')
751
+ }
752
+ }, echoBody)
753
+
754
+ const response1 = await fastify.inject({
755
+ method: 'POST',
756
+ payload: {
757
+ hello: 'michelangelo'
758
+ },
759
+ url: '/'
760
+ })
761
+
762
+ t.assert.deepStrictEqual(response1.json(), {
763
+ statusCode: 400,
764
+ code: 'FST_ERR_VALIDATION',
765
+ error: 'Bad Request',
766
+ message: 'A'
767
+ })
768
+ t.assert.strictEqual(response1.statusCode, 400)
769
+
770
+ const response2 = await fastify.inject({
771
+ method: 'POST',
772
+ payload: {
773
+ hello: 'michelangelo'
774
+ },
775
+ url: '/b'
776
+ })
777
+
778
+ t.assert.deepStrictEqual(response2.json(), {
779
+ statusCode: 400,
780
+ code: 'FST_ERR_VALIDATION',
781
+ error: 'Bad Request',
782
+ message: 'B'
783
+ })
784
+ t.assert.strictEqual(response2.statusCode, 400)
785
+
786
+ const response3 = await fastify.inject({
787
+ method: 'POST',
788
+ payload: {
789
+ hello: 'michelangelo'
790
+ },
791
+ url: '/c'
792
+ })
793
+
794
+ t.assert.deepStrictEqual(response3.json(), {
795
+ statusCode: 400,
796
+ code: 'FST_ERR_VALIDATION',
797
+ error: 'Bad Request',
798
+ message: 'C'
799
+ })
800
+ t.assert.strictEqual(response3.statusCode, 400)
801
+
802
+ const response4 = await fastify.inject({
803
+ method: 'POST',
804
+ payload: {
805
+ hello: 'michelangelo'
806
+ },
807
+ url: '/d'
808
+ })
809
+
810
+ t.assert.deepStrictEqual(response4.json(), {
811
+ statusCode: 400,
812
+ code: 'FST_ERR_VALIDATION',
813
+ error: 'Bad Request',
814
+ message: 'D'
815
+ })
816
+ t.assert.strictEqual(response4.statusCode, 400)
817
+
818
+ const response5 = await fastify.inject({
819
+ method: 'POST',
820
+ payload: {
821
+ hello: 'michelangelo'
822
+ },
823
+ url: '/stillC'
824
+ })
825
+
826
+ t.assert.deepStrictEqual(response5.json(), {
827
+ statusCode: 400,
828
+ code: 'FST_ERR_VALIDATION',
829
+ error: 'Bad Request',
830
+ message: 'C'
831
+ })
832
+ t.assert.strictEqual(response5.statusCode, 400)
833
+ })
834
+
835
+ describe('sync and async must work in the same way', () => {
836
+ // Route with custom validator that throws
837
+ const throwingRouteValidator = {
838
+ schema: {
839
+ body: {
840
+ type: 'object',
841
+ properties: { name: { type: 'string' } }
842
+ }
843
+ },
844
+ validatorCompiler: () => {
845
+ return function (inputData) {
846
+ // This custom validator throws a sync error instead of returning `{ error }`
847
+ throw new Error('Custom validation failed')
848
+ }
849
+ },
850
+ handler (request, reply) { reply.send({ success: true }) }
851
+ }
852
+
853
+ test('async preValidation with custom validator should trigger error handler when validator throws', async (t) => {
854
+ t.plan(4)
855
+
856
+ const fastify = Fastify()
857
+ fastify.setErrorHandler((error, request, reply) => {
858
+ t.assert.ok(error instanceof Error, 'error should be an Error instance')
859
+ t.assert.strictEqual(error.message, 'Custom validation failed')
860
+ reply.status(500).send({ error: error.message })
861
+ })
862
+
863
+ // Add async preValidation hook
864
+ fastify.addHook('preValidation', async (request, reply) => {
865
+ await Promise.resolve('ok')
866
+ })
867
+ fastify.post('/async', throwingRouteValidator)
868
+
869
+ const response = await fastify.inject({
870
+ method: 'POST',
871
+ url: '/async',
872
+ payload: { name: 'test' }
873
+ })
874
+ t.assert.strictEqual(response.statusCode, 500)
875
+ t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' })
876
+ })
877
+
878
+ test('sync preValidation with custom validator should trigger error handler when validator throws', async (t) => {
879
+ t.plan(4)
880
+
881
+ const fastify = Fastify()
882
+ fastify.setErrorHandler((error, request, reply) => {
883
+ t.assert.ok(error instanceof Error, 'error should be an Error instance')
884
+ t.assert.strictEqual(error.message, 'Custom validation failed')
885
+ reply.status(500).send({ error: error.message })
886
+ })
887
+
888
+ // Add sync preValidation hook
889
+ fastify.addHook('preValidation', (request, reply, next) => { next() })
890
+ fastify.post('/sync', throwingRouteValidator)
891
+
892
+ const response = await fastify.inject({
893
+ method: 'POST',
894
+ url: '/sync',
895
+ payload: { name: 'test' }
896
+ })
897
+ t.assert.strictEqual(response.statusCode, 500)
898
+ t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' })
899
+ })
900
+ })