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,1402 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Ajv = require('ajv')
5
+ const { kRequestCacheValidateFns, kRouteContext } = require('../../lib/symbols')
6
+ const Fastify = require('../../fastify')
7
+
8
+ const defaultSchema = {
9
+ type: 'object',
10
+ required: ['hello'],
11
+ properties: {
12
+ hello: { type: 'string' },
13
+ world: { type: 'string' }
14
+ }
15
+ }
16
+
17
+ const requestSchema = {
18
+ params: {
19
+ type: 'object',
20
+ properties: {
21
+ id: {
22
+ type: 'integer',
23
+ minimum: 1
24
+ }
25
+ }
26
+ },
27
+ querystring: {
28
+ type: 'object',
29
+ properties: {
30
+ foo: {
31
+ type: 'string',
32
+ enum: ['bar']
33
+ }
34
+ }
35
+ },
36
+ body: defaultSchema,
37
+ headers: {
38
+ type: 'object',
39
+ properties: {
40
+ 'x-foo': {
41
+ type: 'string'
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ test('#compileValidationSchema', async subtest => {
48
+ subtest.plan(7)
49
+
50
+ await subtest.test('Should return a function - Route without schema', async t => {
51
+ const fastify = Fastify()
52
+
53
+ t.plan(3)
54
+
55
+ fastify.get('/', (req, reply) => {
56
+ const validate = req.compileValidationSchema(defaultSchema)
57
+
58
+ t.assert.ok(validate instanceof Function)
59
+ t.assert.ok(validate({ hello: 'world' }))
60
+ t.assert.ok(!validate({ world: 'foo' }))
61
+
62
+ reply.send({ hello: 'world' })
63
+ })
64
+
65
+ await fastify.inject({
66
+ path: '/',
67
+ method: 'GET'
68
+ })
69
+ })
70
+
71
+ await subtest.test('Validate function errors property should be null after validation when input is valid', async t => {
72
+ const fastify = Fastify()
73
+
74
+ t.plan(3)
75
+
76
+ fastify.get('/', (req, reply) => {
77
+ const validate = req.compileValidationSchema(defaultSchema)
78
+
79
+ t.assert.ok(validate({ hello: 'world' }))
80
+ t.assert.ok(Object.hasOwn(validate, 'errors'))
81
+ t.assert.strictEqual(validate.errors, null)
82
+
83
+ reply.send({ hello: 'world' })
84
+ })
85
+
86
+ await fastify.inject({
87
+ path: '/',
88
+ method: 'GET'
89
+ })
90
+ })
91
+
92
+ await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => {
93
+ const fastify = Fastify()
94
+
95
+ t.plan(4)
96
+
97
+ fastify.get('/', (req, reply) => {
98
+ const validate = req.compileValidationSchema(defaultSchema)
99
+
100
+ t.assert.ok(!validate({ world: 'foo' }))
101
+ t.assert.ok(Object.hasOwn(validate, 'errors'))
102
+ t.assert.ok(Array.isArray(validate.errors))
103
+ t.assert.ok(validate.errors.length > 0)
104
+
105
+ reply.send({ hello: 'world' })
106
+ })
107
+
108
+ await fastify.inject({
109
+ path: '/',
110
+ method: 'GET'
111
+ })
112
+ })
113
+
114
+ await subtest.test(
115
+ 'Should reuse the validate fn across multiple invocations - Route without schema',
116
+ async t => {
117
+ const fastify = Fastify()
118
+ let validate = null
119
+ let counter = 0
120
+
121
+ t.plan(16)
122
+
123
+ fastify.get('/', (req, reply) => {
124
+ counter++
125
+ if (counter > 1) {
126
+ const newValidate = req.compileValidationSchema(defaultSchema)
127
+ t.assert.strictEqual(validate, newValidate, 'Are the same validate function')
128
+ validate = newValidate
129
+ } else {
130
+ validate = req.compileValidationSchema(defaultSchema)
131
+ }
132
+
133
+ t.assert.ok(validate instanceof Function)
134
+ t.assert.ok(validate({ hello: 'world' }))
135
+ t.assert.ok(!validate({ world: 'foo' }))
136
+
137
+ reply.send({ hello: 'world' })
138
+ })
139
+
140
+ await Promise.all([
141
+ fastify.inject({
142
+ path: '/',
143
+ method: 'GET'
144
+ }),
145
+ fastify.inject({
146
+ path: '/',
147
+ method: 'GET'
148
+ }),
149
+ fastify.inject({
150
+ path: '/',
151
+ method: 'GET'
152
+ }),
153
+ fastify.inject({
154
+ path: '/',
155
+ method: 'GET'
156
+ })
157
+ ])
158
+
159
+ t.assert.strictEqual(counter, 4)
160
+ }
161
+ )
162
+
163
+ await subtest.test('Should return a function - Route with schema', async t => {
164
+ const fastify = Fastify()
165
+
166
+ t.plan(3)
167
+
168
+ fastify.post(
169
+ '/',
170
+ {
171
+ schema: {
172
+ body: defaultSchema
173
+ }
174
+ },
175
+ (req, reply) => {
176
+ const validate = req.compileValidationSchema(defaultSchema)
177
+
178
+ t.assert.ok(validate instanceof Function)
179
+ t.assert.ok(validate({ hello: 'world' }))
180
+ t.assert.ok(!validate({ world: 'foo' }))
181
+
182
+ reply.send({ hello: 'world' })
183
+ }
184
+ )
185
+
186
+ await fastify.inject({
187
+ path: '/',
188
+ method: 'POST',
189
+ payload: {
190
+ hello: 'world',
191
+ world: 'foo'
192
+ }
193
+ })
194
+ })
195
+
196
+ await subtest.test(
197
+ 'Should use the custom validator compiler for the route',
198
+ async t => {
199
+ const fastify = Fastify()
200
+ let called = 0
201
+ const custom = ({ schema, httpPart, url, method }) => {
202
+ t.assert.strictEqual(schema, defaultSchema)
203
+ t.assert.strictEqual(url, '/')
204
+ t.assert.strictEqual(method, 'GET')
205
+ t.assert.strictEqual(httpPart, 'querystring')
206
+
207
+ return input => {
208
+ called++
209
+ t.assert.deepStrictEqual(input, { hello: 'world' })
210
+ return true
211
+ }
212
+ }
213
+
214
+ t.plan(10)
215
+
216
+ fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
217
+ const first = req.compileValidationSchema(defaultSchema, 'querystring')
218
+ const second = req.compileValidationSchema(defaultSchema, 'querystring')
219
+
220
+ t.assert.strictEqual(first, second)
221
+ t.assert.ok(first({ hello: 'world' }))
222
+ t.assert.ok(second({ hello: 'world' }))
223
+ t.assert.strictEqual(called, 2)
224
+
225
+ reply.send({ hello: 'world' })
226
+ })
227
+
228
+ await fastify.inject({
229
+ path: '/',
230
+ method: 'GET'
231
+ })
232
+ }
233
+ )
234
+
235
+ await subtest.test(
236
+ 'Should instantiate a WeakMap when executed for first time',
237
+ async t => {
238
+ const fastify = Fastify()
239
+
240
+ t.plan(5)
241
+
242
+ fastify.get('/', (req, reply) => {
243
+ t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
244
+ t.assert.ok(req.compileValidationSchema(defaultSchema) instanceof Function)
245
+ t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
246
+ t.assert.ok(req.compileValidationSchema(Object.assign({}, defaultSchema)) instanceof Function)
247
+ t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
248
+
249
+ reply.send({ hello: 'world' })
250
+ })
251
+
252
+ await fastify.inject({
253
+ path: '/',
254
+ method: 'GET'
255
+ })
256
+ }
257
+ )
258
+ })
259
+
260
+ test('#getValidationFunction', async subtest => {
261
+ subtest.plan(6)
262
+
263
+ await subtest.test('Should return a validation function', async t => {
264
+ const fastify = Fastify()
265
+
266
+ t.plan(1)
267
+
268
+ fastify.get('/', (req, reply) => {
269
+ const original = req.compileValidationSchema(defaultSchema)
270
+ const referenced = req.getValidationFunction(defaultSchema)
271
+
272
+ t.assert.strictEqual(original, referenced)
273
+
274
+ reply.send({ hello: 'world' })
275
+ })
276
+
277
+ await fastify.inject({
278
+ path: '/',
279
+ method: 'GET'
280
+ })
281
+ })
282
+
283
+ await subtest.test('Validate function errors property should be null after validation when input is valid', async t => {
284
+ const fastify = Fastify()
285
+
286
+ t.plan(3)
287
+
288
+ fastify.get('/', (req, reply) => {
289
+ req.compileValidationSchema(defaultSchema)
290
+ const validate = req.getValidationFunction(defaultSchema)
291
+
292
+ t.assert.ok(validate({ hello: 'world' }))
293
+ t.assert.ok(Object.hasOwn(validate, 'errors'))
294
+ t.assert.strictEqual(validate.errors, null)
295
+
296
+ reply.send({ hello: 'world' })
297
+ })
298
+
299
+ await fastify.inject({
300
+ path: '/',
301
+ method: 'GET'
302
+ })
303
+ })
304
+
305
+ await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => {
306
+ const fastify = Fastify()
307
+
308
+ t.plan(4)
309
+
310
+ fastify.get('/', (req, reply) => {
311
+ req.compileValidationSchema(defaultSchema)
312
+ const validate = req.getValidationFunction(defaultSchema)
313
+
314
+ t.assert.ok(!validate({ world: 'foo' }))
315
+ t.assert.ok(Object.hasOwn(validate, 'errors'))
316
+ t.assert.ok(Array.isArray(validate.errors))
317
+ t.assert.ok(validate.errors.length > 0)
318
+
319
+ reply.send({ hello: 'world' })
320
+ })
321
+
322
+ await fastify.inject({
323
+ path: '/',
324
+ method: 'GET'
325
+ })
326
+ })
327
+
328
+ await subtest.test('Should return undefined if no schema compiled', async t => {
329
+ const fastify = Fastify()
330
+
331
+ t.plan(2)
332
+
333
+ fastify.get('/', (req, reply) => {
334
+ const validate = req.getValidationFunction(defaultSchema)
335
+ t.assert.ok(!validate)
336
+
337
+ const validateFn = req.getValidationFunction(42)
338
+ t.assert.ok(!validateFn)
339
+
340
+ reply.send({ hello: 'world' })
341
+ })
342
+
343
+ await fastify.inject('/')
344
+ })
345
+
346
+ await subtest.test(
347
+ 'Should return the validation function from each HTTP part',
348
+ async t => {
349
+ const fastify = Fastify()
350
+ let headerValidation = null
351
+ let customValidation = null
352
+
353
+ t.plan(15)
354
+
355
+ fastify.post(
356
+ '/:id',
357
+ {
358
+ schema: requestSchema
359
+ },
360
+ (req, reply) => {
361
+ const { params } = req
362
+
363
+ switch (params.id) {
364
+ case 1:
365
+ customValidation = req.compileValidationSchema(defaultSchema)
366
+ t.assert.ok(req.getValidationFunction('body'))
367
+ t.assert.ok(req.getValidationFunction('body')({ hello: 'world' }))
368
+ t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' }))
369
+ break
370
+ case 2:
371
+ headerValidation = req.getValidationFunction('headers')
372
+ t.assert.ok(headerValidation)
373
+ t.assert.ok(headerValidation({ 'x-foo': 'world' }))
374
+ t.assert.ok(!headerValidation({ 'x-foo': [] }))
375
+ break
376
+ case 3:
377
+ t.assert.ok(req.getValidationFunction('params'))
378
+ t.assert.ok(req.getValidationFunction('params')({ id: 123 }))
379
+ t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 })))
380
+ break
381
+ case 4:
382
+ t.assert.ok(req.getValidationFunction('querystring'))
383
+ t.assert.ok(req.getValidationFunction('querystring')({ foo: 'bar' }))
384
+ t.assert.ok(!req.getValidationFunction('querystring')({ foo: 'not-bar' })
385
+ )
386
+ break
387
+ case 5:
388
+ t.assert.strictEqual(
389
+ customValidation,
390
+ req.getValidationFunction(defaultSchema)
391
+ )
392
+ t.assert.ok(customValidation({ hello: 'world' }))
393
+ t.assert.ok(!customValidation({}))
394
+ t.assert.strictEqual(headerValidation, req.getValidationFunction('headers'))
395
+ break
396
+ default:
397
+ t.assert.fail('Invalid id')
398
+ }
399
+
400
+ reply.send({ hello: 'world' })
401
+ }
402
+ )
403
+
404
+ const promises = []
405
+
406
+ for (let i = 1; i < 6; i++) {
407
+ promises.push(
408
+ fastify.inject({
409
+ path: `/${i}`,
410
+ method: 'post',
411
+ query: { foo: 'bar' },
412
+ payload: {
413
+ hello: 'world'
414
+ },
415
+ headers: {
416
+ 'x-foo': 'x-bar'
417
+ }
418
+ })
419
+ )
420
+ }
421
+
422
+ await Promise.all(promises)
423
+ }
424
+ )
425
+
426
+ await subtest.test('Should not set a WeakMap if there is no schema', async t => {
427
+ const fastify = Fastify()
428
+
429
+ t.plan(1)
430
+
431
+ fastify.get('/', (req, reply) => {
432
+ req.getValidationFunction(defaultSchema)
433
+ req.getValidationFunction('body')
434
+
435
+ t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
436
+ reply.send({ hello: 'world' })
437
+ })
438
+
439
+ await fastify.inject({
440
+ path: '/',
441
+ method: 'GET'
442
+ })
443
+ })
444
+ })
445
+
446
+ test('#validate', async subtest => {
447
+ subtest.plan(7)
448
+
449
+ await subtest.test(
450
+ 'Should return true/false if input valid - Route without schema',
451
+ async t => {
452
+ const fastify = Fastify()
453
+
454
+ t.plan(2)
455
+
456
+ fastify.get('/', (req, reply) => {
457
+ const isNotValid = req.validateInput({ world: 'string' }, defaultSchema)
458
+ const isValid = req.validateInput({ hello: 'string' }, defaultSchema)
459
+
460
+ t.assert.ok(!isNotValid)
461
+ t.assert.ok(isValid)
462
+
463
+ reply.send({ hello: 'world' })
464
+ })
465
+
466
+ await fastify.inject({
467
+ path: '/',
468
+ method: 'GET'
469
+ })
470
+ }
471
+ )
472
+
473
+ await subtest.test(
474
+ 'Should use the custom validator compiler for the route',
475
+ async t => {
476
+ const fastify = Fastify()
477
+ let called = 0
478
+ const custom = ({ schema, httpPart, url, method }) => {
479
+ t.assert.strictEqual(schema, defaultSchema)
480
+ t.assert.strictEqual(url, '/')
481
+ t.assert.strictEqual(method, 'GET')
482
+ t.assert.strictEqual(httpPart, 'querystring')
483
+
484
+ return input => {
485
+ called++
486
+ t.assert.deepStrictEqual(input, { hello: 'world' })
487
+ return true
488
+ }
489
+ }
490
+
491
+ t.plan(9)
492
+
493
+ fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
494
+ const ok = req.validateInput(
495
+ { hello: 'world' },
496
+ defaultSchema,
497
+ 'querystring'
498
+ )
499
+ const ok2 = req.validateInput({ hello: 'world' }, defaultSchema)
500
+
501
+ t.assert.ok(ok)
502
+ t.assert.ok(ok2)
503
+ t.assert.strictEqual(called, 2)
504
+
505
+ reply.send({ hello: 'world' })
506
+ })
507
+
508
+ await fastify.inject({
509
+ path: '/',
510
+ method: 'GET'
511
+ })
512
+ }
513
+ )
514
+
515
+ await subtest.test(
516
+ 'Should return true/false if input valid - With Schema for Route defined',
517
+ async t => {
518
+ const fastify = Fastify()
519
+
520
+ t.plan(8)
521
+
522
+ fastify.post(
523
+ '/:id',
524
+ {
525
+ schema: requestSchema
526
+ },
527
+ (req, reply) => {
528
+ const { params } = req
529
+
530
+ switch (params.id) {
531
+ case 1:
532
+ t.assert.ok(req.validateInput({ hello: 'world' }, 'body'))
533
+ t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body'))
534
+ break
535
+ case 2:
536
+ t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring'))
537
+ t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring'))
538
+ break
539
+ case 3:
540
+ t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers'))
541
+ t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers'))
542
+ break
543
+ case 4:
544
+ t.assert.ok(req.validateInput({ id: params.id }, 'params'))
545
+ t.assert.ok(!req.validateInput({ id: 0 }, 'params'))
546
+ break
547
+ default:
548
+ t.assert.fail('Invalid id')
549
+ }
550
+
551
+ reply.send({ hello: 'world' })
552
+ }
553
+ )
554
+
555
+ const promises = []
556
+
557
+ for (let i = 1; i < 5; i++) {
558
+ promises.push(
559
+ fastify.inject({
560
+ path: `/${i}`,
561
+ method: 'post',
562
+ query: { foo: 'bar' },
563
+ payload: {
564
+ hello: 'world'
565
+ },
566
+ headers: {
567
+ 'x-foo': 'x-bar'
568
+ }
569
+ })
570
+ )
571
+ }
572
+
573
+ await Promise.all(promises)
574
+ }
575
+ )
576
+
577
+ await subtest.test(
578
+ 'Should throw if missing validation fn for HTTP part and not schema provided',
579
+ async t => {
580
+ const fastify = Fastify()
581
+
582
+ t.plan(10)
583
+
584
+ fastify.get('/:id', (req, reply) => {
585
+ const { params } = req
586
+
587
+ switch (parseInt(params.id)) {
588
+ case 1:
589
+ req.validateInput({}, 'body')
590
+ break
591
+ case 2:
592
+ req.validateInput({}, 'querystring')
593
+ break
594
+ case 3:
595
+ req.validateInput({}, 'query')
596
+ break
597
+ case 4:
598
+ req.validateInput({ 'x-foo': [] }, 'headers')
599
+ break
600
+ case 5:
601
+ req.validateInput({ id: 0 }, 'params')
602
+ break
603
+ default:
604
+ t.assert.fail('Invalid id')
605
+ }
606
+ })
607
+
608
+ const promises = []
609
+
610
+ for (let i = 1; i < 6; i++) {
611
+ promises.push(
612
+ (async j => {
613
+ const response = await fastify.inject(`/${j}`)
614
+
615
+ const result = response.json()
616
+ t.assert.strictEqual(result.statusCode, 500)
617
+ t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
618
+ })(i)
619
+ )
620
+ }
621
+
622
+ await Promise.all(promises)
623
+ }
624
+ )
625
+
626
+ await subtest.test(
627
+ 'Should throw if missing validation fn for HTTP part and not valid schema provided',
628
+ async t => {
629
+ const fastify = Fastify()
630
+
631
+ t.plan(10)
632
+
633
+ fastify.get('/:id', (req, reply) => {
634
+ const { params } = req
635
+
636
+ switch (parseInt(params.id)) {
637
+ case 1:
638
+ req.validateInput({}, 1, 'body')
639
+ break
640
+ case 2:
641
+ req.validateInput({}, [], 'querystring')
642
+ break
643
+ case 3:
644
+ req.validateInput({}, '', 'query')
645
+ break
646
+ case 4:
647
+ req.validateInput({ 'x-foo': [] }, null, 'headers')
648
+ break
649
+ case 5:
650
+ req.validateInput({ id: 0 }, () => {}, 'params')
651
+ break
652
+ default:
653
+ t.assert.fail('Invalid id')
654
+ }
655
+ })
656
+
657
+ const promises = []
658
+
659
+ for (let i = 1; i < 6; i++) {
660
+ promises.push(
661
+ (async j => {
662
+ const response = await fastify.inject({
663
+ path: `/${j}`,
664
+ method: 'GET'
665
+ })
666
+
667
+ const result = response.json()
668
+ t.assert.strictEqual(result.statusCode, 500)
669
+ t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
670
+ })(i)
671
+ )
672
+ }
673
+
674
+ await Promise.all(promises)
675
+ }
676
+ )
677
+
678
+ await subtest.test('Should throw if invalid schema passed', async t => {
679
+ const fastify = Fastify()
680
+
681
+ t.plan(10)
682
+
683
+ fastify.get('/:id', (req, reply) => {
684
+ const { params } = req
685
+
686
+ switch (parseInt(params.id)) {
687
+ case 1:
688
+ req.validateInput({}, 1)
689
+ break
690
+ case 2:
691
+ req.validateInput({}, '')
692
+ break
693
+ case 3:
694
+ req.validateInput({}, [])
695
+ break
696
+ case 4:
697
+ req.validateInput({ 'x-foo': [] }, null)
698
+ break
699
+ case 5:
700
+ req.validateInput({ id: 0 }, () => {})
701
+ break
702
+ default:
703
+ t.assert.fail('Invalid id')
704
+ }
705
+ })
706
+
707
+ const promises = []
708
+
709
+ for (let i = 1; i < 6; i++) {
710
+ promises.push(
711
+ (async j => {
712
+ const response = await fastify.inject({
713
+ path: `/${j}`,
714
+ method: 'GET'
715
+ })
716
+
717
+ const result = response.json()
718
+ t.assert.strictEqual(result.statusCode, 500)
719
+ t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
720
+ })(i)
721
+ )
722
+ }
723
+
724
+ await Promise.all(promises)
725
+ })
726
+
727
+ await subtest.test(
728
+ 'Should set a WeakMap if compiling the very first schema',
729
+ async t => {
730
+ const fastify = Fastify()
731
+
732
+ t.plan(3)
733
+
734
+ fastify.get('/', (req, reply) => {
735
+ t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
736
+ t.assert.strictEqual(req.validateInput({ hello: 'world' }, defaultSchema), true)
737
+ t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
738
+
739
+ reply.send({ hello: 'world' })
740
+ })
741
+
742
+ await fastify.inject({
743
+ path: '/',
744
+ method: 'GET'
745
+ })
746
+ }
747
+ )
748
+ })
749
+
750
+ test('Nested Context', async subtest => {
751
+ subtest.plan(1)
752
+
753
+ await subtest.test('Level_1', async tst => {
754
+ tst.plan(3)
755
+ await tst.test('#compileValidationSchema', async ntst => {
756
+ ntst.plan(5)
757
+
758
+ await ntst.test('Should return a function - Route without schema', async t => {
759
+ const fastify = Fastify()
760
+
761
+ fastify.register((instance, opts, next) => {
762
+ instance.get('/', (req, reply) => {
763
+ const validate = req.compileValidationSchema(defaultSchema)
764
+
765
+ t.assert.ok(validate, Function)
766
+ t.assert.ok(validate({ hello: 'world' }))
767
+ t.assert.ok(!validate({ world: 'foo' }))
768
+
769
+ reply.send({ hello: 'world' })
770
+ })
771
+
772
+ next()
773
+ })
774
+
775
+ t.plan(3)
776
+
777
+ await fastify.inject({
778
+ path: '/',
779
+ method: 'GET'
780
+ })
781
+ })
782
+
783
+ await ntst.test(
784
+ 'Should reuse the validate fn across multiple invocations - Route without schema',
785
+ async t => {
786
+ const fastify = Fastify()
787
+ let validate = null
788
+ let counter = 0
789
+
790
+ t.plan(16)
791
+
792
+ fastify.register((instance, opts, next) => {
793
+ instance.get('/', (req, reply) => {
794
+ counter++
795
+ if (counter > 1) {
796
+ const newValidate = req.compileValidationSchema(defaultSchema)
797
+ t.assert.strictEqual(validate, newValidate, 'Are the same validate function')
798
+ validate = newValidate
799
+ } else {
800
+ validate = req.compileValidationSchema(defaultSchema)
801
+ }
802
+
803
+ t.assert.ok(validate, Function)
804
+ t.assert.ok(validate({ hello: 'world' }))
805
+ t.assert.ok(!validate({ world: 'foo' }))
806
+
807
+ reply.send({ hello: 'world' })
808
+ })
809
+
810
+ next()
811
+ })
812
+
813
+ await Promise.all([
814
+ fastify.inject('/'),
815
+ fastify.inject('/'),
816
+ fastify.inject('/'),
817
+ fastify.inject('/')
818
+ ])
819
+
820
+ t.assert.strictEqual(counter, 4)
821
+ }
822
+ )
823
+
824
+ await ntst.test('Should return a function - Route with schema', async t => {
825
+ const fastify = Fastify()
826
+
827
+ t.plan(3)
828
+
829
+ fastify.register((instance, opts, next) => {
830
+ instance.post(
831
+ '/',
832
+ {
833
+ schema: {
834
+ body: defaultSchema
835
+ }
836
+ },
837
+ (req, reply) => {
838
+ const validate = req.compileValidationSchema(defaultSchema)
839
+
840
+ t.assert.ok(validate, Function)
841
+ t.assert.ok(validate({ hello: 'world' }))
842
+ t.assert.ok(!validate({ world: 'foo' }))
843
+
844
+ reply.send({ hello: 'world' })
845
+ }
846
+ )
847
+
848
+ next()
849
+ })
850
+
851
+ await fastify.inject({
852
+ path: '/',
853
+ method: 'POST',
854
+ payload: {
855
+ hello: 'world',
856
+ world: 'foo'
857
+ }
858
+ })
859
+ })
860
+
861
+ await ntst.test(
862
+ 'Should use the custom validator compiler for the route',
863
+ async t => {
864
+ const fastify = Fastify()
865
+ let called = 0
866
+
867
+ t.plan(10)
868
+
869
+ fastify.register((instance, opts, next) => {
870
+ const custom = ({ schema, httpPart, url, method }) => {
871
+ t.assert.strictEqual(schema, defaultSchema)
872
+ t.assert.strictEqual(url, '/')
873
+ t.assert.strictEqual(method, 'GET')
874
+ t.assert.strictEqual(httpPart, 'querystring')
875
+
876
+ return input => {
877
+ called++
878
+ t.assert.deepStrictEqual(input, { hello: 'world' })
879
+ return true
880
+ }
881
+ }
882
+
883
+ fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
884
+ const first = req.compileValidationSchema(
885
+ defaultSchema,
886
+ 'querystring'
887
+ )
888
+ const second = req.compileValidationSchema(
889
+ defaultSchema,
890
+ 'querystring'
891
+ )
892
+
893
+ t.assert.strictEqual(first, second)
894
+ t.assert.ok(first({ hello: 'world' }))
895
+ t.assert.ok(second({ hello: 'world' }))
896
+ t.assert.strictEqual(called, 2)
897
+
898
+ reply.send({ hello: 'world' })
899
+ })
900
+
901
+ next()
902
+ })
903
+
904
+ await fastify.inject('/')
905
+ }
906
+ )
907
+
908
+ await ntst.test('Should compile the custom validation - nested with schema.headers', async t => {
909
+ const fastify = Fastify()
910
+ let called = false
911
+
912
+ const schemaWithHeaders = {
913
+ headers: {
914
+ 'x-foo': {
915
+ type: 'string'
916
+ }
917
+ }
918
+ }
919
+
920
+ const custom = ({ schema, httpPart, url, method }) => {
921
+ if (called) return () => true
922
+ // only custom validators keep the same headers object
923
+ t.assert.strictEqual(schema, schemaWithHeaders.headers)
924
+ t.assert.strictEqual(url, '/')
925
+ t.assert.strictEqual(httpPart, 'headers')
926
+ called = true
927
+ return () => true
928
+ }
929
+
930
+ t.plan(4)
931
+
932
+ fastify.setValidatorCompiler(custom)
933
+
934
+ fastify.register((instance, opts, next) => {
935
+ instance.get('/', { schema: schemaWithHeaders }, (req, reply) => {
936
+ t.assert.strictEqual(called, true)
937
+
938
+ reply.send({ hello: 'world' })
939
+ })
940
+
941
+ next()
942
+ })
943
+
944
+ await fastify.inject('/')
945
+ })
946
+ })
947
+
948
+ await tst.test('#getValidationFunction', async ntst => {
949
+ ntst.plan(6)
950
+
951
+ await ntst.test('Should return a validation function', async t => {
952
+ const fastify = Fastify()
953
+
954
+ t.plan(1)
955
+
956
+ fastify.register((instance, opts, next) => {
957
+ instance.get('/', (req, reply) => {
958
+ const original = req.compileValidationSchema(defaultSchema)
959
+ const referenced = req.getValidationFunction(defaultSchema)
960
+
961
+ t.assert.strictEqual(original, referenced)
962
+
963
+ reply.send({ hello: 'world' })
964
+ })
965
+
966
+ next()
967
+ })
968
+
969
+ await fastify.inject('/')
970
+ })
971
+
972
+ await ntst.test('Should return undefined if no schema compiled', async t => {
973
+ const fastify = Fastify()
974
+
975
+ t.plan(1)
976
+
977
+ fastify.register((instance, opts, next) => {
978
+ instance.get('/', (req, reply) => {
979
+ const validate = req.getValidationFunction(defaultSchema)
980
+
981
+ t.assert.ok(!validate)
982
+
983
+ reply.send({ hello: 'world' })
984
+ })
985
+
986
+ next()
987
+ })
988
+
989
+ await fastify.inject('/')
990
+ })
991
+
992
+ await ntst.test(
993
+ 'Should return the validation function from each HTTP part',
994
+ async t => {
995
+ const fastify = Fastify()
996
+ let headerValidation = null
997
+ let customValidation = null
998
+
999
+ t.plan(15)
1000
+
1001
+ fastify.register((instance, opts, next) => {
1002
+ instance.post(
1003
+ '/:id',
1004
+ {
1005
+ schema: requestSchema
1006
+ },
1007
+ (req, reply) => {
1008
+ const { params } = req
1009
+
1010
+ switch (params.id) {
1011
+ case 1:
1012
+ customValidation = req.compileValidationSchema(
1013
+ defaultSchema
1014
+ )
1015
+ t.assert.ok(req.getValidationFunction('body'))
1016
+ t.assert.ok(req.getValidationFunction('body')({ hello: 'world' }))
1017
+ t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' })
1018
+ )
1019
+ break
1020
+ case 2:
1021
+ headerValidation = req.getValidationFunction('headers')
1022
+ t.assert.ok(headerValidation)
1023
+ t.assert.ok(headerValidation({ 'x-foo': 'world' }))
1024
+ t.assert.ok(!headerValidation({ 'x-foo': [] }))
1025
+ break
1026
+ case 3:
1027
+ t.assert.ok(req.getValidationFunction('params'))
1028
+ t.assert.ok(req.getValidationFunction('params')({ id: 123 }))
1029
+ t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 })))
1030
+ break
1031
+ case 4:
1032
+ t.assert.ok(req.getValidationFunction('querystring'))
1033
+ t.assert.ok(
1034
+ req.getValidationFunction('querystring')({ foo: 'bar' })
1035
+ )
1036
+ t.assert.ok(!req.getValidationFunction('querystring')({
1037
+ foo: 'not-bar'
1038
+ })
1039
+ )
1040
+ break
1041
+ case 5:
1042
+ t.assert.strictEqual(
1043
+ customValidation,
1044
+ req.getValidationFunction(defaultSchema)
1045
+ )
1046
+ t.assert.ok(customValidation({ hello: 'world' }))
1047
+ t.assert.ok(!customValidation({}))
1048
+ t.assert.strictEqual(
1049
+ headerValidation,
1050
+ req.getValidationFunction('headers')
1051
+ )
1052
+ break
1053
+ default:
1054
+ t.assert.fail('Invalid id')
1055
+ }
1056
+
1057
+ reply.send({ hello: 'world' })
1058
+ }
1059
+ )
1060
+
1061
+ next()
1062
+ })
1063
+ const promises = []
1064
+
1065
+ for (let i = 1; i < 6; i++) {
1066
+ promises.push(
1067
+ fastify.inject({
1068
+ path: `/${i}`,
1069
+ method: 'post',
1070
+ query: { foo: 'bar' },
1071
+ payload: {
1072
+ hello: 'world'
1073
+ },
1074
+ headers: {
1075
+ 'x-foo': 'x-bar'
1076
+ }
1077
+ })
1078
+ )
1079
+ }
1080
+
1081
+ await Promise.all(promises)
1082
+ }
1083
+ )
1084
+
1085
+ await ntst.test('Should return a validation function - nested', async t => {
1086
+ const fastify = Fastify()
1087
+ let called = false
1088
+ const custom = ({ schema, httpPart, url, method }) => {
1089
+ t.assert.strictEqual(schema, defaultSchema)
1090
+ t.assert.strictEqual(url, '/')
1091
+ t.assert.strictEqual(method, 'GET')
1092
+ t.assert.ok(!httpPart)
1093
+
1094
+ called = true
1095
+ return () => true
1096
+ }
1097
+
1098
+ t.plan(6)
1099
+
1100
+ fastify.setValidatorCompiler(custom)
1101
+
1102
+ fastify.register((instance, opts, next) => {
1103
+ instance.get('/', (req, reply) => {
1104
+ const original = req.compileValidationSchema(defaultSchema)
1105
+ const referenced = req.getValidationFunction(defaultSchema)
1106
+
1107
+ t.assert.strictEqual(original, referenced)
1108
+ t.assert.strictEqual(called, true)
1109
+
1110
+ reply.send({ hello: 'world' })
1111
+ })
1112
+
1113
+ next()
1114
+ })
1115
+
1116
+ await fastify.inject('/')
1117
+ })
1118
+
1119
+ await ntst.test(
1120
+ 'Should return undefined if no schema compiled - nested',
1121
+ async t => {
1122
+ const fastify = Fastify()
1123
+ let called = 0
1124
+ const custom = ({ schema, httpPart, url, method }) => {
1125
+ called++
1126
+ return () => true
1127
+ }
1128
+
1129
+ t.plan(3)
1130
+
1131
+ fastify.setValidatorCompiler(custom)
1132
+
1133
+ fastify.get('/', (req, reply) => {
1134
+ const validate = req.compileValidationSchema(defaultSchema)
1135
+
1136
+ t.assert.strictEqual(typeof validate, 'function')
1137
+
1138
+ reply.send({ hello: 'world' })
1139
+ })
1140
+
1141
+ fastify.register(
1142
+ (instance, opts, next) => {
1143
+ instance.get('/', (req, reply) => {
1144
+ const validate = req.getValidationFunction(defaultSchema)
1145
+
1146
+ t.assert.ok(!validate)
1147
+ t.assert.strictEqual(called, 1)
1148
+
1149
+ reply.send({ hello: 'world' })
1150
+ })
1151
+
1152
+ next()
1153
+ },
1154
+ { prefix: '/nested' }
1155
+ )
1156
+
1157
+ await fastify.inject('/')
1158
+ await fastify.inject('/nested')
1159
+ }
1160
+ )
1161
+
1162
+ await ntst.test('Should per-route defined validation compiler', async t => {
1163
+ const fastify = Fastify()
1164
+ let validateParent
1165
+ let validateChild
1166
+ let calledParent = 0
1167
+ let calledChild = 0
1168
+ const customParent = ({ schema, httpPart, url, method }) => {
1169
+ calledParent++
1170
+ return () => true
1171
+ }
1172
+
1173
+ const customChild = ({ schema, httpPart, url, method }) => {
1174
+ calledChild++
1175
+ return () => true
1176
+ }
1177
+
1178
+ t.plan(5)
1179
+
1180
+ fastify.setValidatorCompiler(customParent)
1181
+
1182
+ fastify.get('/', (req, reply) => {
1183
+ validateParent = req.compileValidationSchema(defaultSchema)
1184
+
1185
+ t.assert.strictEqual(typeof validateParent, 'function')
1186
+
1187
+ reply.send({ hello: 'world' })
1188
+ })
1189
+
1190
+ fastify.register(
1191
+ (instance, opts, next) => {
1192
+ instance.get(
1193
+ '/',
1194
+ {
1195
+ validatorCompiler: customChild
1196
+ },
1197
+ (req, reply) => {
1198
+ const validate1 = req.compileValidationSchema(defaultSchema)
1199
+ validateChild = req.getValidationFunction(defaultSchema)
1200
+
1201
+ t.assert.strictEqual(validate1, validateChild)
1202
+ t.assert.notStrictEqual(validateParent, validateChild)
1203
+ t.assert.strictEqual(calledParent, 1)
1204
+ t.assert.strictEqual(calledChild, 1)
1205
+
1206
+ reply.send({ hello: 'world' })
1207
+ }
1208
+ )
1209
+
1210
+ next()
1211
+ },
1212
+ { prefix: '/nested' }
1213
+ )
1214
+
1215
+ await fastify.inject('/')
1216
+ await fastify.inject('/nested')
1217
+ })
1218
+ })
1219
+
1220
+ await tst.test('#validate', async ntst => {
1221
+ ntst.plan(3)
1222
+
1223
+ await ntst.test(
1224
+ 'Should return true/false if input valid - Route without schema',
1225
+ async t => {
1226
+ const fastify = Fastify()
1227
+
1228
+ t.plan(2)
1229
+
1230
+ fastify.register((instance, opts, next) => {
1231
+ instance.get('/', (req, reply) => {
1232
+ const isNotValid = req.validateInput(
1233
+ { world: 'string' },
1234
+ defaultSchema
1235
+ )
1236
+ const isValid = req.validateInput({ hello: 'string' }, defaultSchema)
1237
+
1238
+ t.assert.ok(!isNotValid)
1239
+ t.assert.ok(isValid)
1240
+
1241
+ reply.send({ hello: 'world' })
1242
+ })
1243
+
1244
+ next()
1245
+ })
1246
+
1247
+ await fastify.inject('/')
1248
+ }
1249
+ )
1250
+
1251
+ await ntst.test(
1252
+ 'Should use the custom validator compiler for the route',
1253
+ async t => {
1254
+ const fastify = Fastify()
1255
+ let parentCalled = 0
1256
+ let childCalled = 0
1257
+ const customParent = () => {
1258
+ parentCalled++
1259
+
1260
+ return () => true
1261
+ }
1262
+
1263
+ const customChild = ({ schema, httpPart, url, method }) => {
1264
+ t.assert.strictEqual(schema, defaultSchema)
1265
+ t.assert.strictEqual(url, '/')
1266
+ t.assert.strictEqual(method, 'GET')
1267
+ t.assert.strictEqual(httpPart, 'querystring')
1268
+
1269
+ return input => {
1270
+ childCalled++
1271
+ t.assert.deepStrictEqual(input, { hello: 'world' })
1272
+ return true
1273
+ }
1274
+ }
1275
+
1276
+ t.plan(10)
1277
+
1278
+ fastify.setValidatorCompiler(customParent)
1279
+
1280
+ fastify.register((instance, opts, next) => {
1281
+ instance.get(
1282
+ '/',
1283
+ { validatorCompiler: customChild },
1284
+ (req, reply) => {
1285
+ const ok = req.validateInput(
1286
+ { hello: 'world' },
1287
+ defaultSchema,
1288
+ 'querystring'
1289
+ )
1290
+ const ok2 = req.validateInput({ hello: 'world' }, defaultSchema)
1291
+
1292
+ t.assert.ok(ok)
1293
+ t.assert.ok(ok2)
1294
+ t.assert.strictEqual(childCalled, 2)
1295
+ t.assert.strictEqual(parentCalled, 0)
1296
+
1297
+ reply.send({ hello: 'world' })
1298
+ }
1299
+ )
1300
+
1301
+ next()
1302
+ })
1303
+
1304
+ await fastify.inject('/')
1305
+ }
1306
+ )
1307
+
1308
+ await ntst.test(
1309
+ 'Should return true/false if input valid - With Schema for Route defined and scoped validator compiler',
1310
+ async t => {
1311
+ const validator = new Ajv()
1312
+ const fastify = Fastify()
1313
+ const childCounter = {
1314
+ query: 0,
1315
+ body: 0,
1316
+ params: 0,
1317
+ headers: 0
1318
+ }
1319
+ let parentCalled = 0
1320
+
1321
+ const parent = () => {
1322
+ parentCalled++
1323
+ return () => true
1324
+ }
1325
+ const child = ({ schema, httpPart, url, method }) => {
1326
+ httpPart = httpPart === 'querystring' ? 'query' : httpPart
1327
+ const validate = validator.compile(schema)
1328
+
1329
+ return input => {
1330
+ childCounter[httpPart]++
1331
+ return validate(input)
1332
+ }
1333
+ }
1334
+
1335
+ t.plan(13)
1336
+
1337
+ fastify.setValidatorCompiler(parent)
1338
+ fastify.register((instance, opts, next) => {
1339
+ instance.setValidatorCompiler(child)
1340
+ instance.post(
1341
+ '/:id',
1342
+ {
1343
+ schema: requestSchema
1344
+ },
1345
+ (req, reply) => {
1346
+ const { params } = req
1347
+
1348
+ switch (parseInt(params.id)) {
1349
+ case 1:
1350
+ t.assert.ok(req.validateInput({ hello: 'world' }, 'body'))
1351
+ t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body'))
1352
+ break
1353
+ case 2:
1354
+ t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring'))
1355
+ t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring'))
1356
+ break
1357
+ case 3:
1358
+ t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers'))
1359
+ t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers'))
1360
+ break
1361
+ case 4:
1362
+ t.assert.ok(req.validateInput({ id: 1 }, 'params'))
1363
+ t.assert.ok(!req.validateInput({ id: params.id }, 'params'))
1364
+ break
1365
+ default:
1366
+ t.assert.fail('Invalid id')
1367
+ }
1368
+
1369
+ reply.send({ hello: 'world' })
1370
+ }
1371
+ )
1372
+
1373
+ next()
1374
+ })
1375
+
1376
+ const promises = []
1377
+
1378
+ for (let i = 1; i < 5; i++) {
1379
+ promises.push(
1380
+ fastify.inject({
1381
+ path: `/${i}`,
1382
+ method: 'post',
1383
+ query: {},
1384
+ payload: {
1385
+ hello: 'world'
1386
+ }
1387
+ })
1388
+ )
1389
+ }
1390
+
1391
+ await Promise.all(promises)
1392
+
1393
+ t.assert.strictEqual(childCounter.query, 6) // 4 calls made + 2 custom validations
1394
+ t.assert.strictEqual(childCounter.headers, 6) // 4 calls made + 2 custom validations
1395
+ t.assert.strictEqual(childCounter.body, 6) // 4 calls made + 2 custom validations
1396
+ t.assert.strictEqual(childCounter.params, 6) // 4 calls made + 2 custom validations
1397
+ t.assert.strictEqual(parentCalled, 0)
1398
+ }
1399
+ )
1400
+ })
1401
+ })
1402
+ })