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,406 @@
1
+ 'use strict'
2
+
3
+ const stream = require('node:stream')
4
+ const { ReadableStream } = require('node:stream/web')
5
+ const split = require('split2')
6
+ const { test } = require('node:test')
7
+ const Fastify = require('..')
8
+ const createError = require('@fastify/error')
9
+
10
+ test("HEAD route should handle stream.on('error')", (t, done) => {
11
+ t.plan(6)
12
+
13
+ const resStream = stream.Readable.from('Hello with error!')
14
+ const logStream = split(JSON.parse)
15
+ const expectedError = new Error('Hello!')
16
+ const fastify = Fastify({
17
+ logger: {
18
+ stream: logStream,
19
+ level: 'error'
20
+ }
21
+ })
22
+
23
+ fastify.route({
24
+ method: 'GET',
25
+ path: '/more-coffee',
26
+ exposeHeadRoute: true,
27
+ handler: (req, reply) => {
28
+ process.nextTick(() => resStream.emit('error', expectedError))
29
+ return resStream
30
+ }
31
+ })
32
+
33
+ logStream.once('data', line => {
34
+ const { message, stack } = expectedError
35
+ t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack })
36
+ t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route')
37
+ t.assert.strictEqual(line.level, 50)
38
+ })
39
+
40
+ fastify.inject({
41
+ method: 'HEAD',
42
+ url: '/more-coffee'
43
+ }, (error, res) => {
44
+ t.assert.ifError(error)
45
+ t.assert.strictEqual(res.statusCode, 200)
46
+ t.assert.strictEqual(res.headers['content-type'], undefined)
47
+ done()
48
+ })
49
+ })
50
+
51
+ test('HEAD route should handle ReadableStream.cancel() error', (t, done) => {
52
+ t.plan(7)
53
+
54
+ const logStream = split(JSON.parse)
55
+ const expectedError = new Error('Cancel error!')
56
+ const fastify = Fastify({
57
+ logger: {
58
+ stream: logStream,
59
+ level: 'error'
60
+ }
61
+ })
62
+
63
+ fastify.route({
64
+ method: 'GET',
65
+ path: '/web-stream',
66
+ exposeHeadRoute: true,
67
+ handler: (req, reply) => {
68
+ const webStream = new ReadableStream({
69
+ start (controller) {
70
+ controller.enqueue('Hello from web stream!')
71
+ },
72
+ cancel (reason) {
73
+ t.assert.strictEqual(reason, 'Stream cancelled by HEAD route')
74
+ throw expectedError
75
+ }
76
+ })
77
+ return webStream
78
+ }
79
+ })
80
+
81
+ logStream.once('data', line => {
82
+ const { message, stack } = expectedError
83
+ t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack })
84
+ t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route')
85
+ t.assert.strictEqual(line.level, 50)
86
+ })
87
+
88
+ fastify.inject({
89
+ method: 'HEAD',
90
+ url: '/web-stream'
91
+ }, (error, res) => {
92
+ t.assert.ifError(error)
93
+ t.assert.strictEqual(res.statusCode, 200)
94
+ t.assert.strictEqual(res.headers['content-type'], undefined)
95
+ done()
96
+ })
97
+ })
98
+
99
+ test('HEAD route should be exposed by default', async t => {
100
+ t.plan(5)
101
+
102
+ const resStream = stream.Readable.from('Hello with error!')
103
+ const resJson = { hello: 'world' }
104
+ const fastify = Fastify()
105
+
106
+ fastify.route({
107
+ method: 'GET',
108
+ path: '/without-flag',
109
+ handler: (req, reply) => {
110
+ return resStream
111
+ }
112
+ })
113
+
114
+ fastify.route({
115
+ exposeHeadRoute: true,
116
+ method: 'GET',
117
+ path: '/with-flag',
118
+ handler: (req, reply) => {
119
+ return resJson
120
+ }
121
+ })
122
+
123
+ let res = await fastify.inject({
124
+ method: 'HEAD',
125
+ url: '/without-flag'
126
+ })
127
+ t.assert.strictEqual(res.statusCode, 200)
128
+
129
+ res = await fastify.inject({
130
+ method: 'HEAD',
131
+ url: '/with-flag'
132
+ })
133
+ t.assert.strictEqual(res.statusCode, 200)
134
+ t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
135
+ t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`)
136
+ t.assert.strictEqual(res.body, '')
137
+ })
138
+
139
+ test('HEAD route should be exposed if route exposeHeadRoute is set', async t => {
140
+ t.plan(5)
141
+
142
+ const resBuffer = Buffer.from('I am a coffee!')
143
+ const resJson = { hello: 'world' }
144
+ const fastify = Fastify({ exposeHeadRoutes: false })
145
+
146
+ fastify.route({
147
+ exposeHeadRoute: true,
148
+ method: 'GET',
149
+ path: '/one',
150
+ handler: (req, reply) => {
151
+ return resBuffer
152
+ }
153
+ })
154
+
155
+ fastify.route({
156
+ method: 'GET',
157
+ path: '/two',
158
+ handler: (req, reply) => {
159
+ return resJson
160
+ }
161
+ })
162
+
163
+ let res = await fastify.inject({
164
+ method: 'HEAD',
165
+ url: '/one'
166
+ })
167
+ t.assert.strictEqual(res.statusCode, 200)
168
+ t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream')
169
+ t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`)
170
+ t.assert.strictEqual(res.body, '')
171
+
172
+ res = await fastify.inject({
173
+ method: 'HEAD',
174
+ url: '/two'
175
+ })
176
+ t.assert.strictEqual(res.statusCode, 404)
177
+ })
178
+
179
+ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', (t, done) => {
180
+ t.plan(6)
181
+
182
+ const resBuffer = Buffer.from('I am a coffee!')
183
+ const fastify = Fastify({
184
+ exposeHeadRoutes: true
185
+ })
186
+
187
+ fastify.route({
188
+ method: 'HEAD',
189
+ path: '/one',
190
+ handler: (req, reply) => {
191
+ reply.header('content-type', 'application/pdf')
192
+ reply.header('content-length', `${resBuffer.byteLength}`)
193
+ reply.header('x-custom-header', 'some-custom-header')
194
+ reply.send()
195
+ }
196
+ })
197
+
198
+ fastify.route({
199
+ method: 'GET',
200
+ path: '/one',
201
+ handler: (req, reply) => {
202
+ return resBuffer
203
+ }
204
+ })
205
+
206
+ fastify.inject({
207
+ method: 'HEAD',
208
+ url: '/one'
209
+ }, (error, res) => {
210
+ t.assert.ifError(error)
211
+ t.assert.strictEqual(res.statusCode, 200)
212
+ t.assert.strictEqual(res.headers['content-type'], 'application/pdf')
213
+ t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`)
214
+ t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header')
215
+ t.assert.strictEqual(res.body, '')
216
+ done()
217
+ })
218
+ })
219
+
220
+ test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', (t, done) => {
221
+ t.plan(6)
222
+
223
+ const fastify = Fastify()
224
+
225
+ const resBuffer = Buffer.from('I am a coffee!')
226
+
227
+ fastify.route({
228
+ method: 'HEAD',
229
+ path: '/one',
230
+ handler: (req, reply) => {
231
+ reply.header('content-type', 'application/pdf')
232
+ reply.header('content-length', `${resBuffer.byteLength}`)
233
+ reply.header('x-custom-header', 'some-custom-header')
234
+ reply.send()
235
+ }
236
+ })
237
+
238
+ fastify.route({
239
+ method: 'GET',
240
+ exposeHeadRoute: true,
241
+ path: '/one',
242
+ handler: (req, reply) => {
243
+ return resBuffer
244
+ }
245
+ })
246
+
247
+ fastify.inject({
248
+ method: 'HEAD',
249
+ url: '/one'
250
+ }, (error, res) => {
251
+ t.assert.ifError(error)
252
+ t.assert.strictEqual(res.statusCode, 200)
253
+ t.assert.strictEqual(res.headers['content-type'], 'application/pdf')
254
+ t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`)
255
+ t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header')
256
+ t.assert.strictEqual(res.body, '')
257
+ done()
258
+ })
259
+ })
260
+
261
+ test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', (t, done) => {
262
+ t.plan(2)
263
+
264
+ const fastify = Fastify()
265
+
266
+ fastify.register(function routes (f, opts, next) {
267
+ f.route({
268
+ method: 'GET',
269
+ url: '/',
270
+ exposeHeadRoute: true,
271
+ prefixTrailingSlash: 'no-slash',
272
+ handler: (req, reply) => {
273
+ reply.send({ hello: 'world' })
274
+ }
275
+ })
276
+
277
+ next()
278
+ }, { prefix: '/prefix' })
279
+
280
+ fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => {
281
+ t.assert.ifError(err)
282
+ t.assert.strictEqual(res.statusCode, 404)
283
+ done()
284
+ })
285
+ })
286
+
287
+ test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'both\'', async t => {
288
+ t.plan(3)
289
+
290
+ const fastify = Fastify()
291
+
292
+ fastify.register(function routes (f, opts, next) {
293
+ f.route({
294
+ method: 'GET',
295
+ url: '/',
296
+ exposeHeadRoute: true,
297
+ prefixTrailingSlash: 'both',
298
+ handler: (req, reply) => {
299
+ reply.send({ hello: 'world' })
300
+ }
301
+ })
302
+
303
+ next()
304
+ }, { prefix: '/prefix' })
305
+
306
+ const doublePrefixReply = await fastify.inject({ url: '/prefix/prefix', method: 'HEAD' })
307
+ const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' })
308
+ const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' })
309
+
310
+ t.assert.strictEqual(doublePrefixReply.statusCode, 404)
311
+ t.assert.strictEqual(trailingSlashReply.statusCode, 200)
312
+ t.assert.strictEqual(noneTrailingReply.statusCode, 200)
313
+ })
314
+
315
+ test('GET route with body schema should throw', t => {
316
+ t.plan(1)
317
+
318
+ const fastify = Fastify()
319
+
320
+ t.assert.throws(() => {
321
+ fastify.route({
322
+ method: 'GET',
323
+ path: '/get',
324
+ schema: {
325
+ body: {}
326
+ },
327
+ handler: function (req, reply) {
328
+ reply.send({ hello: 'world' })
329
+ }
330
+ })
331
+ }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/get route is not supported!')())
332
+ })
333
+
334
+ test('HEAD route with body schema should throw', t => {
335
+ t.plan(1)
336
+
337
+ const fastify = Fastify()
338
+
339
+ t.assert.throws(() => {
340
+ fastify.route({
341
+ method: 'HEAD',
342
+ path: '/shouldThrow',
343
+ schema: {
344
+ body: {}
345
+ },
346
+ handler: function (req, reply) {
347
+ reply.send({ hello: 'world' })
348
+ }
349
+ })
350
+ }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow route is not supported!')())
351
+ })
352
+
353
+ test('[HEAD, GET] route with body schema should throw', t => {
354
+ t.plan(1)
355
+
356
+ const fastify = Fastify()
357
+
358
+ t.assert.throws(() => {
359
+ fastify.route({
360
+ method: ['HEAD', 'GET'],
361
+ path: '/shouldThrowHead',
362
+ schema: {
363
+ body: {}
364
+ },
365
+ handler: function (req, reply) {
366
+ reply.send({ hello: 'world' })
367
+ }
368
+ })
369
+ }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrowHead route is not supported!')())
370
+ })
371
+
372
+ test('GET route with body schema should throw - shorthand', t => {
373
+ t.plan(1)
374
+
375
+ const fastify = Fastify()
376
+
377
+ t.assert.throws(() => {
378
+ fastify.get('/shouldThrow', {
379
+ schema: {
380
+ body: {}
381
+ }
382
+ },
383
+ function (req, reply) {
384
+ reply.send({ hello: 'world' })
385
+ }
386
+ )
387
+ }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/shouldThrow route is not supported!')())
388
+ })
389
+
390
+ test('HEAD route with body schema should throw - shorthand', t => {
391
+ t.plan(1)
392
+
393
+ const fastify = Fastify()
394
+
395
+ t.assert.throws(() => {
396
+ fastify.head('/shouldThrow2', {
397
+ schema: {
398
+ body: {}
399
+ }
400
+ },
401
+ function (req, reply) {
402
+ reply.send({ hello: 'world' })
403
+ }
404
+ )
405
+ }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow2 route is not supported!')())
406
+ })
@@ -0,0 +1,225 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+ const {
6
+ FST_ERR_INVALID_URL
7
+ } = require('../lib/errors')
8
+
9
+ test('Request and Reply share the route options', async t => {
10
+ t.plan(3)
11
+
12
+ const fastify = Fastify()
13
+
14
+ const config = {
15
+ this: 'is a string',
16
+ thisIs: function aFunction () {}
17
+ }
18
+
19
+ fastify.route({
20
+ method: 'GET',
21
+ url: '/',
22
+ config,
23
+ handler: (req, reply) => {
24
+ t.assert.deepStrictEqual(req.routeOptions, reply.routeOptions)
25
+ t.assert.deepStrictEqual(req.routeOptions.config, reply.routeOptions.config)
26
+ t.assert.match(req.routeOptions.config, config, 'there are url and method additional properties')
27
+
28
+ reply.send({ hello: 'world' })
29
+ }
30
+ })
31
+
32
+ await fastify.inject('/')
33
+ })
34
+
35
+ test('Will not try to re-createprefixed HEAD route if it already exists and exposeHeadRoutes is true', async (t) => {
36
+ t.plan(1)
37
+
38
+ const fastify = Fastify({ exposeHeadRoutes: true })
39
+
40
+ fastify.register((scope, opts, next) => {
41
+ scope.route({
42
+ method: 'HEAD',
43
+ path: '/route',
44
+ handler: (req, reply) => {
45
+ reply.header('content-type', 'text/plain')
46
+ reply.send('custom HEAD response')
47
+ }
48
+ })
49
+ scope.route({
50
+ method: 'GET',
51
+ path: '/route',
52
+ handler: (req, reply) => {
53
+ reply.send({ ok: true })
54
+ }
55
+ })
56
+
57
+ next()
58
+ }, { prefix: '/prefix' })
59
+
60
+ await fastify.ready()
61
+
62
+ t.assert.ok(true)
63
+ })
64
+
65
+ test('route with non-english characters', async (t) => {
66
+ t.plan(3)
67
+
68
+ const fastify = Fastify()
69
+
70
+ fastify.get('/föö', (request, reply) => {
71
+ reply.send('here /föö')
72
+ })
73
+
74
+ const fastifyServer = await fastify.listen({ port: 0 })
75
+ t.after(() => fastify.close())
76
+
77
+ const response = await fetch(fastifyServer + encodeURI('/föö'))
78
+ t.assert.ok(response.ok)
79
+ t.assert.strictEqual(response.status, 200)
80
+ const body = await response.text()
81
+ t.assert.strictEqual(body, 'here /föö')
82
+ })
83
+
84
+ test('invalid url attribute - non string URL', t => {
85
+ t.plan(1)
86
+ const fastify = Fastify()
87
+
88
+ try {
89
+ fastify.get(/^\/(donations|skills|blogs)/, () => { })
90
+ } catch (error) {
91
+ t.assert.strictEqual(error.code, FST_ERR_INVALID_URL().code)
92
+ }
93
+ })
94
+
95
+ test('exposeHeadRoute should not reuse the same route option', async t => {
96
+ t.plan(2)
97
+
98
+ const fastify = Fastify()
99
+
100
+ // we update the onRequest hook in onRoute hook
101
+ // if we reuse the same route option
102
+ // that means we will append another function inside the array
103
+ fastify.addHook('onRoute', function (routeOption) {
104
+ if (Array.isArray(routeOption.onRequest)) {
105
+ routeOption.onRequest.push(() => {})
106
+ } else {
107
+ routeOption.onRequest = [() => {}]
108
+ }
109
+ })
110
+
111
+ fastify.addHook('onRoute', function (routeOption) {
112
+ t.assert.strictEqual(routeOption.onRequest.length, 1)
113
+ })
114
+
115
+ fastify.route({
116
+ method: 'GET',
117
+ path: '/more-coffee',
118
+ async handler () {
119
+ return 'hello world'
120
+ }
121
+ })
122
+ })
123
+
124
+ test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 30000 }, async t => {
125
+ t.plan(1)
126
+
127
+ const fastify = Fastify()
128
+
129
+ fastify.get('/*', async (_, reply) => reply.json({ ok: true }))
130
+
131
+ for (let i = 0; i < 100; i++) {
132
+ fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true }))
133
+ }
134
+
135
+ t.assert.ok("fastify.all doesn't degrade performance")
136
+ })
137
+
138
+ test('Adding manually HEAD route after GET with the same path throws Fastify duplicated route instance error', t => {
139
+ t.plan(1)
140
+
141
+ const fastify = Fastify()
142
+
143
+ fastify.route({
144
+ method: 'GET',
145
+ path: '/:param1',
146
+ handler: (req, reply) => {
147
+ reply.send({ hello: 'world' })
148
+ }
149
+ })
150
+
151
+ try {
152
+ fastify.route({
153
+ method: 'HEAD',
154
+ path: '/:param2',
155
+ handler: (req, reply) => {
156
+ reply.send({ hello: 'world' })
157
+ }
158
+ })
159
+ t.assert.fail('Should throw fastify duplicated route declaration')
160
+ } catch (error) {
161
+ t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE')
162
+ }
163
+ })
164
+
165
+ test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /1', async (t) => {
166
+ t.plan(1)
167
+
168
+ const fastify = Fastify({ exposeHeadRoutes: true })
169
+
170
+ await fastify.register((scope, opts, next) => {
171
+ scope.route({
172
+ method: 'GET',
173
+ path: '/route',
174
+ handler: (req, reply) => {
175
+ reply.send({ ok: true })
176
+ },
177
+ onSend: (req, reply, payload, done) => {
178
+ reply.header('x-content-type', 'application/fastify')
179
+ done(null, payload)
180
+ }
181
+ })
182
+
183
+ next()
184
+ }, { prefix: '/prefix' })
185
+
186
+ await fastify.ready()
187
+
188
+ const result = await fastify.inject({
189
+ url: '/prefix/route',
190
+ method: 'HEAD'
191
+ })
192
+
193
+ t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify')
194
+ })
195
+
196
+ test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /2', async (t) => {
197
+ t.plan(1)
198
+
199
+ const fastify = Fastify({ exposeHeadRoutes: true })
200
+
201
+ await fastify.register((scope, opts, next) => {
202
+ scope.route({
203
+ method: 'get',
204
+ path: '/route',
205
+ handler: (req, reply) => {
206
+ reply.send({ ok: true })
207
+ },
208
+ onSend: (req, reply, payload, done) => {
209
+ reply.header('x-content-type', 'application/fastify')
210
+ done(null, payload)
211
+ }
212
+ })
213
+
214
+ next()
215
+ }, { prefix: '/prefix' })
216
+
217
+ await fastify.ready()
218
+
219
+ const result = await fastify.inject({
220
+ url: '/prefix/route',
221
+ method: 'HEAD'
222
+ })
223
+
224
+ t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify')
225
+ })