fastify 2.10.0 → 2.11.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.
@@ -567,6 +567,40 @@ test('Should set a custom logLevel for a plugin', t => {
567
567
  })
568
568
  })
569
569
 
570
+ test('Should set a custom logSerializers for a plugin', t => {
571
+ t.plan(3)
572
+
573
+ const splitStream = split(JSON.parse)
574
+ splitStream.on('data', (line) => {
575
+ if (line.test) {
576
+ t.is(line.test, 'XHello')
577
+ }
578
+ })
579
+
580
+ const logger = pino({ level: 'error' }, splitStream)
581
+
582
+ const fastify = Fastify({
583
+ logger
584
+ })
585
+
586
+ fastify.register(function (instance, opts, next) {
587
+ instance.get('/plugin', (req, reply) => {
588
+ req.log.info({ test: 'Hello' }) // we should see this log
589
+ reply.send({ hello: 'world' })
590
+ })
591
+ next()
592
+ }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } })
593
+
594
+ fastify.inject({
595
+ method: 'GET',
596
+ url: '/plugin'
597
+ }, (err, res) => {
598
+ t.error(err)
599
+ const payload = JSON.parse(res.payload)
600
+ t.deepEqual(payload, { hello: 'world' })
601
+ })
602
+ })
603
+
570
604
  test('Should set a custom logLevel for every plugin', t => {
571
605
  const lines = ['incoming request', 'request completed', 'info', 'debug']
572
606
  t.plan(18)
@@ -634,6 +668,281 @@ test('Should set a custom logLevel for every plugin', t => {
634
668
  })
635
669
  })
636
670
 
671
+ test('Should set a custom logSerializers for every plugin', t => {
672
+ const lines = ['Hello', 'XHello', 'ZHello']
673
+ t.plan(9)
674
+
675
+ const splitStream = split(JSON.parse)
676
+ splitStream.on('data', (line) => {
677
+ if (line.test) {
678
+ t.is(line.test, lines.shift())
679
+ }
680
+ })
681
+
682
+ const logger = pino({ level: 'info' }, splitStream)
683
+ const fastify = Fastify({
684
+ logger
685
+ })
686
+
687
+ fastify.get('/', (req, reply) => {
688
+ req.log.warn({ test: 'Hello' })
689
+ reply.send({ hello: 'world' })
690
+ })
691
+
692
+ fastify.register(function (instance, opts, next) {
693
+ instance.get('/test1', (req, reply) => {
694
+ req.log.info({ test: 'Hello' })
695
+ reply.send({ hello: 'world' })
696
+ })
697
+ next()
698
+ }, { logSerializers: { test: value => 'X' + value } })
699
+
700
+ fastify.register(function (instance, opts, next) {
701
+ instance.get('/test2', (req, reply) => {
702
+ req.log.info({ test: 'Hello' })
703
+ reply.send({ hello: 'world' })
704
+ })
705
+ next()
706
+ }, { logSerializers: { test: value => 'Z' + value } })
707
+
708
+ fastify.inject({
709
+ method: 'GET',
710
+ url: '/'
711
+ }, (err, res) => {
712
+ t.error(err)
713
+ const payload = JSON.parse(res.payload)
714
+ t.deepEqual(payload, { hello: 'world' })
715
+ })
716
+
717
+ fastify.inject({
718
+ method: 'GET',
719
+ url: '/test1'
720
+ }, (err, res) => {
721
+ t.error(err)
722
+ const payload = JSON.parse(res.payload)
723
+ t.deepEqual(payload, { hello: 'world' })
724
+ })
725
+
726
+ fastify.inject({
727
+ method: 'GET',
728
+ url: '/test2'
729
+ }, (err, res) => {
730
+ t.error(err)
731
+ const payload = JSON.parse(res.payload)
732
+ t.deepEqual(payload, { hello: 'world' })
733
+ })
734
+ })
735
+
736
+ test('Should override serializers from route', t => {
737
+ t.plan(3)
738
+
739
+ const splitStream = split(JSON.parse)
740
+ splitStream.on('data', (line) => {
741
+ if (line.test) {
742
+ t.is(line.test, 'ZHello')
743
+ }
744
+ })
745
+
746
+ const logger = pino({ level: 'info' }, splitStream)
747
+ const fastify = Fastify({
748
+ logger
749
+ })
750
+
751
+ fastify.register(function (instance, opts, next) {
752
+ instance.get('/', {
753
+ logSerializers: {
754
+ test: value => 'Z' + value // should override
755
+ }
756
+ }, (req, reply) => {
757
+ req.log.info({ test: 'Hello' })
758
+ reply.send({ hello: 'world' })
759
+ })
760
+ next()
761
+ }, { logSerializers: { test: value => 'X' + value } })
762
+
763
+ fastify.inject({
764
+ method: 'GET',
765
+ url: '/'
766
+ }, (err, res) => {
767
+ t.error(err)
768
+ const payload = JSON.parse(res.payload)
769
+ t.deepEqual(payload, { hello: 'world' })
770
+ })
771
+ })
772
+
773
+ test('Should override serializers from plugin', t => {
774
+ t.plan(3)
775
+
776
+ const splitStream = split(JSON.parse)
777
+ splitStream.on('data', (line) => {
778
+ if (line.test) {
779
+ t.is(line.test, 'ZHello')
780
+ }
781
+ })
782
+
783
+ const logger = pino({ level: 'info' }, splitStream)
784
+ const fastify = Fastify({
785
+ logger
786
+ })
787
+
788
+ fastify.register(function (instance, opts, next) {
789
+ instance.register(context1, {
790
+ logSerializers: {
791
+ test: value => 'Z' + value // should override
792
+ }
793
+ })
794
+ next()
795
+ }, { logSerializers: { test: value => 'X' + value } })
796
+
797
+ function context1 (instance, opts, next) {
798
+ instance.get('/', (req, reply) => {
799
+ req.log.info({ test: 'Hello' })
800
+ reply.send({ hello: 'world' })
801
+ })
802
+ next()
803
+ }
804
+
805
+ fastify.inject({
806
+ method: 'GET',
807
+ url: '/'
808
+ }, (err, res) => {
809
+ t.error(err)
810
+ const payload = JSON.parse(res.payload)
811
+ t.deepEqual(payload, { hello: 'world' })
812
+ })
813
+ })
814
+
815
+ test('Should use serializers from plugin and route', t => {
816
+ t.plan(4)
817
+
818
+ const splitStream = split(JSON.parse)
819
+ splitStream.on('data', (line) => {
820
+ if (line.test) {
821
+ t.is(line.test, 'XHello')
822
+ }
823
+ if (line.test2) {
824
+ t.is(line.test2, 'ZHello')
825
+ }
826
+ })
827
+
828
+ const logger = pino({ level: 'info' }, splitStream)
829
+ const fastify = Fastify({
830
+ logger
831
+ })
832
+
833
+ fastify.register(context1, {
834
+ logSerializers: { test: value => 'X' + value }
835
+ })
836
+
837
+ function context1 (instance, opts, next) {
838
+ instance.get('/', {
839
+ logSerializers: {
840
+ test2: value => 'Z' + value
841
+ }
842
+ }, (req, reply) => {
843
+ req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' }
844
+ reply.send({ hello: 'world' })
845
+ })
846
+ next()
847
+ }
848
+
849
+ fastify.inject({
850
+ method: 'GET',
851
+ url: '/'
852
+ }, (err, res) => {
853
+ t.error(err)
854
+ const payload = JSON.parse(res.payload)
855
+ t.deepEqual(payload, { hello: 'world' })
856
+ })
857
+ })
858
+
859
+ test('Should use serializers from instance fastify and route', t => {
860
+ t.plan(4)
861
+
862
+ const splitStream = split(JSON.parse)
863
+ splitStream.on('data', (line) => {
864
+ if (line.test) {
865
+ t.is(line.test, 'XHello')
866
+ }
867
+ if (line.test2) {
868
+ t.is(line.test2, 'ZHello')
869
+ }
870
+ })
871
+
872
+ const logger = pino({
873
+ level: 'info',
874
+ serializers: {
875
+ test: value => 'X' + value,
876
+ test2: value => 'This should be override - ' + value
877
+ }
878
+ }, splitStream)
879
+ const fastify = Fastify({
880
+ logger
881
+ })
882
+
883
+ fastify.get('/', {
884
+ logSerializers: {
885
+ test2: value => 'Z' + value
886
+ }
887
+ }, (req, reply) => {
888
+ req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' }
889
+ reply.send({ hello: 'world' })
890
+ })
891
+
892
+ fastify.inject({
893
+ method: 'GET',
894
+ url: '/'
895
+ }, (err, res) => {
896
+ t.error(err)
897
+ const payload = JSON.parse(res.payload)
898
+ t.deepEqual(payload, { hello: 'world' })
899
+ })
900
+ })
901
+
902
+ test('Should use serializers inherit from contexts', t => {
903
+ t.plan(5)
904
+
905
+ const splitStream = split(JSON.parse)
906
+ splitStream.on('data', (line) => {
907
+ if (line.test && line.test2 && line.test3) {
908
+ t.is(line.test, 'XHello')
909
+ t.is(line.test2, 'YHello')
910
+ t.is(line.test3, 'ZHello')
911
+ }
912
+ })
913
+
914
+ const logger = pino({
915
+ level: 'info',
916
+ serializers: {
917
+ test: value => 'X' + value
918
+ }
919
+ }, splitStream)
920
+
921
+ const fastify = Fastify({ logger })
922
+ fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } })
923
+
924
+ function context1 (instance, opts, next) {
925
+ instance.get('/', {
926
+ logSerializers: {
927
+ test3: value => 'Z' + value
928
+ }
929
+ }, (req, reply) => {
930
+ req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' }
931
+ reply.send({ hello: 'world' })
932
+ })
933
+ next()
934
+ }
935
+
936
+ fastify.inject({
937
+ method: 'GET',
938
+ url: '/'
939
+ }, (err, res) => {
940
+ t.error(err)
941
+ const payload = JSON.parse(res.payload)
942
+ t.deepEqual(payload, { hello: 'world' })
943
+ })
944
+ })
945
+
637
946
  test('Should increase the log level for a specific plugin', t => {
638
947
  t.plan(4)
639
948
 
@@ -81,3 +81,79 @@ test('proto-poisoning ignore', t => {
81
81
  })
82
82
  })
83
83
  })
84
+
85
+ test('constructor-poisoning ignore (default in v2)', t => {
86
+ t.plan(3)
87
+
88
+ const fastify = Fastify()
89
+ t.tearDown(fastify.close.bind(fastify))
90
+
91
+ fastify.post('/', (request, reply) => {
92
+ reply.send('ok')
93
+ })
94
+
95
+ fastify.listen(0, function (err) {
96
+ t.error(err)
97
+
98
+ sget({
99
+ method: 'POST',
100
+ url: 'http://localhost:' + fastify.server.address().port,
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: '{ "constructor": { "prototype": { "foo": "bar" } } }'
103
+ }, (err, response, body) => {
104
+ t.error(err)
105
+ t.strictEqual(response.statusCode, 200)
106
+ })
107
+ })
108
+ })
109
+
110
+ test('constructor-poisoning error', t => {
111
+ t.plan(3)
112
+
113
+ const fastify = Fastify({ onConstructorPoisoning: 'error' })
114
+ t.tearDown(fastify.close.bind(fastify))
115
+
116
+ fastify.post('/', (request, reply) => {
117
+ t.fail('handler should not be called')
118
+ })
119
+
120
+ fastify.listen(0, function (err) {
121
+ t.error(err)
122
+
123
+ sget({
124
+ method: 'POST',
125
+ url: 'http://localhost:' + fastify.server.address().port,
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: '{ "constructor": { "prototype": { "foo": "bar" } } }'
128
+ }, (err, response, body) => {
129
+ t.error(err)
130
+ t.strictEqual(response.statusCode, 400)
131
+ })
132
+ })
133
+ })
134
+
135
+ test('constructor-poisoning remove', t => {
136
+ t.plan(4)
137
+
138
+ const fastify = Fastify({ onProtoPoisoning: 'remove' })
139
+ t.tearDown(fastify.close.bind(fastify))
140
+
141
+ fastify.post('/', (request, reply) => {
142
+ t.equal(undefined, Object.assign({}, request.body).foo)
143
+ reply.send({ ok: true })
144
+ })
145
+
146
+ fastify.listen(0, function (err) {
147
+ t.error(err)
148
+
149
+ sget({
150
+ method: 'POST',
151
+ url: 'http://localhost:' + fastify.server.address().port,
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: '{ "constructor": { "prototype": { "foo": "bar" } } }'
154
+ }, (err, response, body) => {
155
+ t.error(err)
156
+ t.strictEqual(response.statusCode, 200)
157
+ })
158
+ })
159
+ })
@@ -3,15 +3,23 @@
3
3
  const test = require('tap').test
4
4
  const Fastify = require('../')
5
5
 
6
+ function endMiddleware (nextOrPayload, next) {
7
+ if (typeof nextOrPayload === 'function') {
8
+ nextOrPayload()
9
+ } else {
10
+ next()
11
+ }
12
+ }
13
+
6
14
  function testExecutionHook (hook) {
7
15
  test(`${hook}`, t => {
8
16
  t.plan(3)
9
17
  const fastify = Fastify()
10
18
 
11
19
  fastify.post('/', {
12
- [hook]: (req, reply, done) => {
20
+ [hook]: (req, reply, nextOrPayload, next) => {
13
21
  t.pass('hook called')
14
- done()
22
+ endMiddleware(nextOrPayload, next)
15
23
  }
16
24
  }, (req, reply) => {
17
25
  reply.send(req.body)
@@ -35,15 +43,15 @@ function testExecutionHook (hook) {
35
43
  get: function () { return ++this.calledTimes }
36
44
  })
37
45
 
38
- fastify.addHook(hook, (req, reply, next) => {
46
+ fastify.addHook(hook, (req, reply, nextOrPayload, next) => {
39
47
  t.equal(checker.check, 1)
40
- next()
48
+ endMiddleware(nextOrPayload, next)
41
49
  })
42
50
 
43
51
  fastify.post('/', {
44
- [hook]: (req, reply, done) => {
52
+ [hook]: (req, reply, nextOrPayload, next) => {
45
53
  t.equal(checker.check, 2)
46
- done()
54
+ endMiddleware(nextOrPayload, next)
47
55
  }
48
56
  }, (req, reply) => {
49
57
  reply.send({})
@@ -67,13 +75,13 @@ function testExecutionHook (hook) {
67
75
 
68
76
  fastify.post('/', {
69
77
  [hook]: [
70
- (req, reply, done) => {
78
+ (req, reply, nextOrPayload, next) => {
71
79
  t.equal(checker.check, 1)
72
- done()
80
+ endMiddleware(nextOrPayload, next)
73
81
  },
74
- (req, reply, done) => {
82
+ (req, reply, nextOrPayload, next) => {
75
83
  t.equal(checker.check, 2)
76
- done()
84
+ endMiddleware(nextOrPayload, next)
77
85
  }
78
86
  ]
79
87
  }, (req, reply) => {
@@ -96,15 +104,15 @@ function testExecutionHook (hook) {
96
104
  get: function () { return ++this.calledTimes }
97
105
  })
98
106
 
99
- fastify.addHook(hook, (req, reply, next) => {
107
+ fastify.addHook(hook, (req, reply, nextOrPayload, next) => {
100
108
  t.equal(checker.check, 1)
101
- next()
109
+ endMiddleware(nextOrPayload, next)
102
110
  })
103
111
 
104
112
  fastify.post('/', {
105
- [hook]: (req, reply, done) => {
113
+ [hook]: (req, reply, nextOrPayload, next) => {
106
114
  t.equal(checker.check, 2)
107
- done()
115
+ endMiddleware(nextOrPayload, next)
108
116
  }
109
117
  }, handler)
110
118
 
@@ -286,6 +294,7 @@ function testBeforeHandlerHook (hook) {
286
294
  }
287
295
 
288
296
  testExecutionHook('preHandler')
297
+ testExecutionHook('onSend')
289
298
  testExecutionHook('onRequest')
290
299
  testExecutionHook('onResponse')
291
300
  testExecutionHook('preValidation')
@@ -4,6 +4,7 @@ const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('..')
6
6
 
7
+ const ajvMergePatch = require('ajv-merge-patch')
7
8
  const AJV = require('ajv')
8
9
  const fastClone = require('rfdc')({ circles: false, proto: true })
9
10
 
@@ -322,3 +323,128 @@ test('$ref with a simple $id', t => {
322
323
  t.deepEquals(JSON.parse(res.payload), { foo: { foo: 'bar' } })
323
324
  })
324
325
  })
326
+
327
+ test('Should handle root $merge keywords in header', t => {
328
+ t.plan(5)
329
+ const fastify = Fastify({
330
+ ajv: {
331
+ plugins: [
332
+ ajvMergePatch
333
+ ]
334
+ }
335
+ })
336
+
337
+ fastify.route({
338
+ method: 'GET',
339
+ url: '/',
340
+ schema: {
341
+ headers: {
342
+ $merge: {
343
+ source: {
344
+ type: 'object',
345
+ properties: {
346
+ q: {
347
+ type: 'string'
348
+ }
349
+ }
350
+ },
351
+ with: {
352
+ required: ['q']
353
+ }
354
+ }
355
+ }
356
+ },
357
+ handler (req, reply) {
358
+ reply.send({ ok: 1 })
359
+ }
360
+ })
361
+
362
+ fastify.ready(err => {
363
+ t.error(err)
364
+
365
+ fastify.inject({
366
+ method: 'GET',
367
+ url: '/'
368
+ }, (err, res) => {
369
+ t.error(err)
370
+ t.equals(res.statusCode, 400)
371
+ })
372
+
373
+ fastify.inject({
374
+ method: 'GET',
375
+ url: '/',
376
+ headers: {
377
+ q: 'foo'
378
+ }
379
+ }, (err, res) => {
380
+ t.error(err)
381
+ t.equals(res.statusCode, 200)
382
+ })
383
+ })
384
+ })
385
+
386
+ test('Should handle root $patch keywords in header', t => {
387
+ t.plan(5)
388
+ const fastify = Fastify({
389
+ ajv: {
390
+ plugins: [
391
+ ajvMergePatch
392
+ ]
393
+ }
394
+ })
395
+
396
+ fastify.route({
397
+ method: 'GET',
398
+ url: '/',
399
+ schema: {
400
+ headers: {
401
+ $patch: {
402
+ source: {
403
+ type: 'object',
404
+ properties: {
405
+ q: {
406
+ type: 'string'
407
+ }
408
+ }
409
+ },
410
+ with: [
411
+ {
412
+ op: 'add',
413
+ path: '/properties/q',
414
+ value: { type: 'number' }
415
+ }
416
+ ]
417
+ }
418
+ }
419
+ },
420
+ handler (req, reply) {
421
+ reply.send({ ok: 1 })
422
+ }
423
+ })
424
+
425
+ fastify.ready(err => {
426
+ t.error(err)
427
+
428
+ fastify.inject({
429
+ method: 'GET',
430
+ url: '/',
431
+ headers: {
432
+ q: 'foo'
433
+ }
434
+ }, (err, res) => {
435
+ t.error(err)
436
+ t.equals(res.statusCode, 400)
437
+ })
438
+
439
+ fastify.inject({
440
+ method: 'GET',
441
+ url: '/',
442
+ headers: {
443
+ q: 10
444
+ }
445
+ }, (err, res) => {
446
+ t.error(err)
447
+ t.equals(res.statusCode, 200)
448
+ })
449
+ })
450
+ })
@@ -64,7 +64,31 @@ const cors = require('cors')
64
64
  maxParamLength: 200,
65
65
  querystringParser: (str: string) => ({ str: str, strArray: [str] }),
66
66
  modifyCoreObjects: true,
67
- return503OnClosing: true
67
+ return503OnClosing: true,
68
+ genReqId: () => {
69
+ if (Math.random() > 0.5) {
70
+ return Math.random().toString()
71
+ }
72
+ return Math.random()
73
+ },
74
+ requestIdHeader: 'request-id',
75
+ requestIdLogLabel: 'reqId',
76
+ serverFactory: (handler, options) => {
77
+ const server = http.createServer((req, res) => {
78
+ handler(req, res)
79
+ })
80
+ return server
81
+ }
82
+ })
83
+
84
+ // http2 server factory option
85
+ const otherHttp2Server = fastify({
86
+ serverFactory: (handler, options) => {
87
+ const server = http2.createServer((req, res) => {
88
+ handler(req, res)
89
+ })
90
+ return server
91
+ }
68
92
  })
69
93
 
70
94
  // custom types
@@ -102,6 +126,11 @@ server.use('/', (req, res, next) => {
102
126
  console.log(`${req.method} ${req.url}`)
103
127
  })
104
128
 
129
+ // Custom middleware with multiple paths
130
+ server.use(['/foo', '/bar'], (req, res, next) => {
131
+ console.log(`${req.method} ${req.url}`)
132
+ })
133
+
105
134
  // Third party plugin
106
135
  // Also check if async functions are allowed to be passed to .register()
107
136
  // https://github.com/fastify/fastify/pull/1841
@@ -233,6 +262,7 @@ const opts: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2Se
233
262
  schemaCompiler: (schema: Object) => () => {},
234
263
  bodyLimit: 5000,
235
264
  logLevel: 'trace',
265
+ version: '1.0.0',
236
266
  config: { }
237
267
  }
238
268
  const optsWithHandler: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> = {