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