fastify 5.3.3 → 5.5.0

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 (137) hide show
  1. package/.vscode/settings.json +15 -15
  2. package/LICENSE +1 -1
  3. package/README.md +2 -0
  4. package/SECURITY.md +158 -2
  5. package/build/build-validation.js +20 -1
  6. package/docs/Guides/Delay-Accepting-Requests.md +8 -5
  7. package/docs/Guides/Ecosystem.md +20 -5
  8. package/docs/Guides/Migration-Guide-V5.md +6 -10
  9. package/docs/Guides/Recommendations.md +1 -1
  10. package/docs/Reference/ContentTypeParser.md +1 -1
  11. package/docs/Reference/Errors.md +5 -3
  12. package/docs/Reference/Hooks.md +16 -20
  13. package/docs/Reference/Lifecycle.md +2 -2
  14. package/docs/Reference/Logging.md +3 -3
  15. package/docs/Reference/Middleware.md +1 -1
  16. package/docs/Reference/Reply.md +8 -8
  17. package/docs/Reference/Request.md +2 -2
  18. package/docs/Reference/Routes.md +7 -6
  19. package/docs/Reference/Server.md +341 -200
  20. package/docs/Reference/TypeScript.md +1 -3
  21. package/docs/Reference/Validation-and-Serialization.md +56 -4
  22. package/docs/Reference/Warnings.md +2 -1
  23. package/fastify.d.ts +4 -3
  24. package/fastify.js +47 -34
  25. package/lib/configValidator.js +196 -28
  26. package/lib/contentTypeParser.js +41 -48
  27. package/lib/error-handler.js +3 -3
  28. package/lib/errors.js +11 -0
  29. package/lib/handleRequest.js +13 -17
  30. package/lib/pluginOverride.js +3 -1
  31. package/lib/promise.js +23 -0
  32. package/lib/reply.js +24 -30
  33. package/lib/request.js +3 -10
  34. package/lib/route.js +37 -3
  35. package/lib/server.js +36 -35
  36. package/lib/symbols.js +1 -0
  37. package/lib/warnings.js +19 -1
  38. package/package.json +14 -10
  39. package/test/404s.test.js +226 -325
  40. package/test/allow-unsafe-regex.test.js +19 -48
  41. package/test/als.test.js +28 -40
  42. package/test/async-await.test.js +84 -128
  43. package/test/async_hooks.test.js +18 -37
  44. package/test/body-limit.test.js +90 -63
  45. package/test/buffer.test.js +22 -0
  46. package/test/build-certificate.js +1 -1
  47. package/test/case-insensitive.test.js +44 -65
  48. package/test/check.test.js +17 -21
  49. package/test/close-pipelining.test.js +24 -15
  50. package/test/constrained-routes.test.js +231 -0
  51. package/test/custom-http-server.test.js +7 -15
  52. package/test/custom-parser-async.test.js +17 -22
  53. package/test/custom-parser.0.test.js +267 -348
  54. package/test/custom-parser.1.test.js +141 -191
  55. package/test/custom-parser.2.test.js +34 -44
  56. package/test/custom-parser.3.test.js +56 -104
  57. package/test/custom-parser.4.test.js +106 -144
  58. package/test/custom-parser.5.test.js +56 -75
  59. package/test/custom-querystring-parser.test.js +51 -77
  60. package/test/decorator-namespace.test._js_ +3 -4
  61. package/test/decorator.test.js +76 -259
  62. package/test/delete.test.js +101 -110
  63. package/test/diagnostics-channel/404.test.js +7 -15
  64. package/test/diagnostics-channel/async-delay-request.test.js +7 -16
  65. package/test/diagnostics-channel/async-request.test.js +8 -16
  66. package/test/diagnostics-channel/error-request.test.js +7 -15
  67. package/test/diagnostics-channel/sync-delay-request.test.js +7 -16
  68. package/test/diagnostics-channel/sync-request-reply.test.js +9 -16
  69. package/test/diagnostics-channel/sync-request.test.js +9 -16
  70. package/test/fastify-instance.test.js +1 -1
  71. package/test/header-overflow.test.js +18 -29
  72. package/test/helper.js +139 -135
  73. package/test/hooks-async.test.js +259 -235
  74. package/test/hooks.test.js +951 -996
  75. package/test/http-methods/copy.test.js +14 -19
  76. package/test/http-methods/get.test.js +131 -143
  77. package/test/http-methods/head.test.js +53 -84
  78. package/test/http-methods/lock.test.js +31 -31
  79. package/test/http-methods/mkcalendar.test.js +45 -72
  80. package/test/http-methods/mkcol.test.js +5 -9
  81. package/test/http-methods/move.test.js +6 -10
  82. package/test/http-methods/propfind.test.js +34 -44
  83. package/test/http-methods/proppatch.test.js +23 -29
  84. package/test/http-methods/report.test.js +44 -69
  85. package/test/http-methods/search.test.js +67 -82
  86. package/test/http-methods/unlock.test.js +5 -9
  87. package/test/http2/closing.test.js +38 -20
  88. package/test/http2/secure-with-fallback.test.js +31 -28
  89. package/test/https/custom-https-server.test.js +9 -13
  90. package/test/https/https.test.js +56 -53
  91. package/test/input-validation.js +139 -150
  92. package/test/internals/errors.test.js +50 -1
  93. package/test/internals/handle-request.test.js +72 -65
  94. package/test/internals/promise.test.js +63 -0
  95. package/test/internals/reply.test.js +277 -496
  96. package/test/issue-4959.test.js +12 -3
  97. package/test/listen.4.test.js +31 -43
  98. package/test/nullable-validation.test.js +33 -46
  99. package/test/output-validation.test.js +24 -26
  100. package/test/plugin.1.test.js +40 -68
  101. package/test/plugin.2.test.js +108 -120
  102. package/test/plugin.3.test.js +50 -72
  103. package/test/plugin.4.test.js +124 -119
  104. package/test/promises.test.js +42 -63
  105. package/test/proto-poisoning.test.js +78 -97
  106. package/test/register.test.js +8 -18
  107. package/test/request-error.test.js +57 -146
  108. package/test/request-id.test.js +30 -49
  109. package/test/route-hooks.test.js +117 -101
  110. package/test/route-prefix.test.js +194 -133
  111. package/test/route-shorthand.test.js +9 -27
  112. package/test/route.1.test.js +74 -131
  113. package/test/route.8.test.js +9 -17
  114. package/test/router-options.test.js +450 -0
  115. package/test/schema-serialization.test.js +177 -154
  116. package/test/schema-special-usage.test.js +165 -132
  117. package/test/schema-validation.test.js +254 -218
  118. package/test/server.test.js +143 -5
  119. package/test/set-error-handler.test.js +58 -1
  120. package/test/skip-reply-send.test.js +64 -69
  121. package/test/stream.1.test.js +33 -50
  122. package/test/stream.4.test.js +18 -28
  123. package/test/stream.5.test.js +11 -19
  124. package/test/trust-proxy.test.js +32 -58
  125. package/test/types/errors.test-d.ts +13 -1
  126. package/test/types/fastify.test-d.ts +3 -0
  127. package/test/types/request.test-d.ts +1 -0
  128. package/test/types/type-provider.test-d.ts +55 -0
  129. package/test/url-rewriting.test.js +45 -62
  130. package/test/use-semicolon-delimiter.test.js +117 -59
  131. package/test/versioned-routes.test.js +39 -56
  132. package/types/errors.d.ts +11 -1
  133. package/types/hooks.d.ts +1 -1
  134. package/types/instance.d.ts +1 -1
  135. package/types/reply.d.ts +2 -2
  136. package/types/request.d.ts +1 -0
  137. package/.taprc +0 -7
@@ -2,6 +2,7 @@
2
2
 
3
3
  const split = require('split2')
4
4
  const { test } = require('node:test')
5
+ const querystring = require('node:querystring')
5
6
  const Fastify = require('../')
6
7
  const {
7
8
  FST_ERR_BAD_URL,
@@ -445,3 +446,452 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST
445
446
  }
446
447
  )
447
448
  })
449
+
450
+ test('Should honor routerOptions.defaultRoute', async t => {
451
+ t.plan(3)
452
+ const fastify = Fastify({
453
+ routerOptions: {
454
+ defaultRoute: function (_, res) {
455
+ t.assert.ok('default route called')
456
+ res.statusCode = 404
457
+ res.end('default route')
458
+ }
459
+ }
460
+ })
461
+
462
+ const res = await fastify.inject('/')
463
+ t.assert.strictEqual(res.statusCode, 404)
464
+ t.assert.strictEqual(res.payload, 'default route')
465
+ })
466
+
467
+ test('Should honor routerOptions.badUrl', async t => {
468
+ t.plan(3)
469
+ const fastify = Fastify({
470
+ routerOptions: {
471
+ defaultRoute: function (_, res) {
472
+ t.asset.fail('default route should not be called')
473
+ },
474
+ onBadUrl: function (path, _, res) {
475
+ t.assert.ok('bad url called')
476
+ res.statusCode = 400
477
+ res.end(`Bath URL: ${path}`)
478
+ }
479
+ }
480
+ })
481
+
482
+ fastify.get('/hello/:id', (req, res) => {
483
+ res.send({ hello: 'world' })
484
+ })
485
+
486
+ const res = await fastify.inject('/hello/%world')
487
+ t.assert.strictEqual(res.statusCode, 400)
488
+ t.assert.strictEqual(res.payload, 'Bath URL: /hello/%world')
489
+ })
490
+
491
+ test('Should honor routerOptions.ignoreTrailingSlash', async t => {
492
+ t.plan(4)
493
+ const fastify = Fastify({
494
+ routerOptions: {
495
+ ignoreTrailingSlash: true
496
+ }
497
+ })
498
+
499
+ fastify.get('/test', (req, res) => {
500
+ res.send('test')
501
+ })
502
+
503
+ let res = await fastify.inject('/test')
504
+ t.assert.strictEqual(res.statusCode, 200)
505
+ t.assert.strictEqual(res.payload.toString(), 'test')
506
+
507
+ res = await fastify.inject('/test/')
508
+ t.assert.strictEqual(res.statusCode, 200)
509
+ t.assert.strictEqual(res.payload.toString(), 'test')
510
+ })
511
+
512
+ test('Should honor routerOptions.ignoreDuplicateSlashes', async t => {
513
+ t.plan(4)
514
+ const fastify = Fastify({
515
+ routerOptions: {
516
+ ignoreDuplicateSlashes: true
517
+ }
518
+ })
519
+
520
+ fastify.get('/test//test///test', (req, res) => {
521
+ res.send('test')
522
+ })
523
+
524
+ let res = await fastify.inject('/test/test/test')
525
+ t.assert.strictEqual(res.statusCode, 200)
526
+ t.assert.strictEqual(res.payload.toString(), 'test')
527
+
528
+ res = await fastify.inject('/test//test///test')
529
+ t.assert.strictEqual(res.statusCode, 200)
530
+ t.assert.strictEqual(res.payload.toString(), 'test')
531
+ })
532
+
533
+ test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes', async t => {
534
+ t.plan(4)
535
+ const fastify = Fastify({
536
+ routerOptions: {
537
+ ignoreTrailingSlash: true,
538
+ ignoreDuplicateSlashes: true
539
+ }
540
+ })
541
+
542
+ t.after(() => fastify.close())
543
+
544
+ fastify.get('/test//test///test', (req, res) => {
545
+ res.send('test')
546
+ })
547
+
548
+ let res = await fastify.inject('/test/test/test/')
549
+ t.assert.strictEqual(res.statusCode, 200)
550
+ t.assert.strictEqual(res.payload.toString(), 'test')
551
+
552
+ res = await fastify.inject('/test//test///test//')
553
+ t.assert.strictEqual(res.statusCode, 200)
554
+ t.assert.strictEqual(res.payload.toString(), 'test')
555
+ })
556
+
557
+ test('Should honor routerOptions.maxParamLength', async (t) => {
558
+ const fastify = Fastify({
559
+ routerOptions:
560
+ {
561
+ maxParamLength: 10
562
+ }
563
+ })
564
+
565
+ fastify.get('/test/:id', (req, reply) => {
566
+ reply.send({ hello: 'world' })
567
+ })
568
+
569
+ const res = await fastify.inject({
570
+ method: 'GET',
571
+ url: '/test/123456789'
572
+ })
573
+ t.assert.strictEqual(res.statusCode, 200)
574
+
575
+ const resError = await fastify.inject({
576
+ method: 'GET',
577
+ url: '/test/123456789abcd'
578
+ })
579
+ t.assert.strictEqual(resError.statusCode, 404)
580
+ })
581
+
582
+ test('Should honor routerOptions.allowUnsafeRegex', async (t) => {
583
+ const fastify = Fastify({
584
+ routerOptions:
585
+ {
586
+ allowUnsafeRegex: true
587
+ }
588
+ })
589
+
590
+ fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => {
591
+ reply.send({ hello: 'world' })
592
+ })
593
+
594
+ let res = await fastify.inject({
595
+ method: 'GET',
596
+ url: '/test/bac,1ea'
597
+ })
598
+ t.assert.strictEqual(res.statusCode, 200)
599
+
600
+ res = await fastify.inject({
601
+ method: 'GET',
602
+ url: '/test/qwerty'
603
+ })
604
+
605
+ t.assert.strictEqual(res.statusCode, 404)
606
+ })
607
+
608
+ test('Should honor routerOptions.caseSensitive', async (t) => {
609
+ const fastify = Fastify({
610
+ routerOptions:
611
+ {
612
+ caseSensitive: false
613
+ }
614
+ })
615
+
616
+ fastify.get('/TeSt', (req, reply) => {
617
+ reply.send('test')
618
+ })
619
+
620
+ let res = await fastify.inject({
621
+ method: 'GET',
622
+ url: '/test'
623
+ })
624
+ t.assert.strictEqual(res.statusCode, 200)
625
+
626
+ res = await fastify.inject({
627
+ method: 'GET',
628
+ url: '/tEsT'
629
+ })
630
+ t.assert.strictEqual(res.statusCode, 200)
631
+
632
+ res = await fastify.inject({
633
+ method: 'GET',
634
+ url: '/TEST'
635
+ })
636
+ t.assert.strictEqual(res.statusCode, 200)
637
+ })
638
+
639
+ test('Should honor routerOptions.queryStringParser', async (t) => {
640
+ t.plan(4)
641
+ const fastify = Fastify({
642
+ routerOptions:
643
+ {
644
+ querystringParser: function (str) {
645
+ t.assert.ok('custom query string parser called')
646
+ return querystring.parse(str)
647
+ }
648
+ }
649
+ })
650
+
651
+ fastify.get('/test', (req, reply) => {
652
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
653
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
654
+ reply.send('test')
655
+ })
656
+
657
+ const res = await fastify.inject({
658
+ method: 'GET',
659
+ url: '/test?foo=bar&baz=faz'
660
+ })
661
+ t.assert.strictEqual(res.statusCode, 200)
662
+ })
663
+
664
+ test('Should honor routerOptions.useSemicolonDelimiter', async (t) => {
665
+ t.plan(6)
666
+ const fastify = Fastify({
667
+ routerOptions:
668
+ {
669
+ useSemicolonDelimiter: true
670
+ }
671
+ })
672
+
673
+ fastify.get('/test', (req, reply) => {
674
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
675
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
676
+ reply.send('test')
677
+ })
678
+
679
+ // Support semicolon delimiter
680
+ let res = await fastify.inject({
681
+ method: 'GET',
682
+ url: '/test;foo=bar&baz=faz'
683
+ })
684
+ t.assert.strictEqual(res.statusCode, 200)
685
+
686
+ // Support query string `?` delimiter
687
+ res = await fastify.inject({
688
+ method: 'GET',
689
+ url: '/test?foo=bar&baz=faz'
690
+ })
691
+ t.assert.strictEqual(res.statusCode, 200)
692
+ })
693
+
694
+ test('Should honor routerOptions.buildPrettyMeta', async (t) => {
695
+ t.plan(10)
696
+ const fastify = Fastify({
697
+ routerOptions:
698
+ {
699
+ buildPrettyMeta: function (route) {
700
+ t.assert.ok('custom buildPrettyMeta called')
701
+ return { metaKey: route.path }
702
+ }
703
+ }
704
+ })
705
+
706
+ fastify.get('/test', () => {})
707
+ fastify.get('/test/hello', () => {})
708
+ fastify.get('/testing', () => {})
709
+ fastify.get('/testing/:param', () => {})
710
+ fastify.put('/update', () => {})
711
+
712
+ await fastify.ready()
713
+
714
+ const result = fastify.printRoutes({ includeMeta: true })
715
+ const expected = `\
716
+ └── /
717
+ ├── test (GET, HEAD)
718
+ │ • (metaKey) "/test"
719
+ │ ├── /hello (GET, HEAD)
720
+ │ │ • (metaKey) "/test/hello"
721
+ │ └── ing (GET, HEAD)
722
+ │ • (metaKey) "/testing"
723
+ │ └── /
724
+ │ └── :param (GET, HEAD)
725
+ │ • (metaKey) "/testing/:param"
726
+ └── update (PUT)
727
+ • (metaKey) "/update"
728
+ `
729
+
730
+ t.assert.strictEqual(result, expected)
731
+ })
732
+
733
+ test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes over top level options', async t => {
734
+ t.plan(4)
735
+ const fastify = Fastify({
736
+ ignoreTrailingSlash: false,
737
+ ignoreDuplicateSlashes: false,
738
+ routerOptions: {
739
+ ignoreTrailingSlash: true,
740
+ ignoreDuplicateSlashes: true
741
+ }
742
+ })
743
+
744
+ fastify.get('/test//test///test', (req, res) => {
745
+ res.send('test')
746
+ })
747
+
748
+ let res = await fastify.inject('/test/test/test/')
749
+ t.assert.strictEqual(res.statusCode, 200)
750
+ t.assert.strictEqual(res.payload.toString(), 'test')
751
+
752
+ res = await fastify.inject('/test//test///test//')
753
+ t.assert.strictEqual(res.statusCode, 200)
754
+ t.assert.strictEqual(res.payload.toString(), 'test')
755
+ })
756
+
757
+ test('Should honor routerOptions.maxParamLength over maxParamLength option', async (t) => {
758
+ const fastify = Fastify({
759
+ maxParamLength: 0,
760
+ routerOptions:
761
+ {
762
+ maxParamLength: 10
763
+ }
764
+ })
765
+
766
+ fastify.get('/test/:id', (req, reply) => {
767
+ reply.send({ hello: 'world' })
768
+ })
769
+
770
+ const res = await fastify.inject({
771
+ method: 'GET',
772
+ url: '/test/123456789'
773
+ })
774
+ t.assert.strictEqual(res.statusCode, 200)
775
+
776
+ const resError = await fastify.inject({
777
+ method: 'GET',
778
+ url: '/test/123456789abcd'
779
+ })
780
+ t.assert.strictEqual(resError.statusCode, 404)
781
+ })
782
+
783
+ test('Should honor routerOptions.allowUnsafeRegex over allowUnsafeRegex option', async (t) => {
784
+ const fastify = Fastify({
785
+ allowUnsafeRegex: false,
786
+ routerOptions:
787
+ {
788
+ allowUnsafeRegex: true
789
+ }
790
+ })
791
+
792
+ fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => {
793
+ reply.send({ hello: 'world' })
794
+ })
795
+
796
+ let res = await fastify.inject({
797
+ method: 'GET',
798
+ url: '/test/bac,1ea'
799
+ })
800
+ t.assert.strictEqual(res.statusCode, 200)
801
+
802
+ res = await fastify.inject({
803
+ method: 'GET',
804
+ url: '/test/qwerty'
805
+ })
806
+
807
+ t.assert.strictEqual(res.statusCode, 404)
808
+ })
809
+
810
+ test('Should honor routerOptions.caseSensitive over caseSensitive option', async (t) => {
811
+ const fastify = Fastify({
812
+ caseSensitive: true,
813
+ routerOptions:
814
+ {
815
+ caseSensitive: false
816
+ }
817
+ })
818
+
819
+ fastify.get('/TeSt', (req, reply) => {
820
+ reply.send('test')
821
+ })
822
+
823
+ let res = await fastify.inject({
824
+ method: 'GET',
825
+ url: '/test'
826
+ })
827
+ t.assert.strictEqual(res.statusCode, 200)
828
+
829
+ res = await fastify.inject({
830
+ method: 'GET',
831
+ url: '/tEsT'
832
+ })
833
+ t.assert.strictEqual(res.statusCode, 200)
834
+
835
+ res = await fastify.inject({
836
+ method: 'GET',
837
+ url: '/TEST'
838
+ })
839
+ t.assert.strictEqual(res.statusCode, 200)
840
+ })
841
+
842
+ test('Should honor routerOptions.queryStringParser over queryStringParser option', async (t) => {
843
+ t.plan(4)
844
+ const fastify = Fastify({
845
+ queryStringParser: undefined,
846
+ routerOptions:
847
+ {
848
+ querystringParser: function (str) {
849
+ t.assert.ok('custom query string parser called')
850
+ return querystring.parse(str)
851
+ }
852
+ }
853
+ })
854
+
855
+ fastify.get('/test', (req, reply) => {
856
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
857
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
858
+ reply.send('test')
859
+ })
860
+
861
+ const res = await fastify.inject({
862
+ method: 'GET',
863
+ url: '/test?foo=bar&baz=faz'
864
+ })
865
+ t.assert.strictEqual(res.statusCode, 200)
866
+ })
867
+
868
+ test('Should honor routerOptions.useSemicolonDelimiter over useSemicolonDelimiter option', async (t) => {
869
+ t.plan(6)
870
+ const fastify = Fastify({
871
+ useSemicolonDelimiter: false,
872
+ routerOptions:
873
+ {
874
+ useSemicolonDelimiter: true
875
+ }
876
+ })
877
+
878
+ fastify.get('/test', (req, reply) => {
879
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
880
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
881
+ reply.send('test')
882
+ })
883
+
884
+ // Support semicolon delimiter
885
+ let res = await fastify.inject({
886
+ method: 'GET',
887
+ url: '/test;foo=bar&baz=faz'
888
+ })
889
+ t.assert.strictEqual(res.statusCode, 200)
890
+
891
+ // Support query string `?` delimiter
892
+ res = await fastify.inject({
893
+ method: 'GET',
894
+ url: '/test?foo=bar&baz=faz'
895
+ })
896
+ t.assert.strictEqual(res.statusCode, 200)
897
+ })