fastify 4.2.1 → 4.3.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.
@@ -0,0 +1,583 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const { kReplySerializeWeakMap } = require('../../lib/symbols')
5
+ const Fastify = require('../../fastify')
6
+
7
+ function getDefaultSchema () {
8
+ return {
9
+ type: 'object',
10
+ required: ['hello'],
11
+ properties: {
12
+ hello: { type: 'string' },
13
+ world: { type: 'string' }
14
+ }
15
+ }
16
+ }
17
+
18
+ function getResponseSchema () {
19
+ return {
20
+ 201: {
21
+ type: 'object',
22
+ required: ['status'],
23
+ properties: {
24
+ status: {
25
+ type: 'string',
26
+ enum: ['ok']
27
+ },
28
+ message: {
29
+ type: 'string'
30
+ }
31
+ }
32
+ },
33
+ '4xx': {
34
+ type: 'object',
35
+ properties: {
36
+ status: {
37
+ type: 'string',
38
+ enum: ['error']
39
+ },
40
+ code: {
41
+ type: 'integer',
42
+ minimum: 1
43
+ },
44
+ message: {
45
+ type: 'string'
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ test('Reply#compileSerializationSchema', t => {
53
+ t.plan(4)
54
+
55
+ t.test('Should return a serialization function', async t => {
56
+ const fastify = Fastify()
57
+
58
+ t.plan(4)
59
+
60
+ fastify.get('/', (req, reply) => {
61
+ const serialize = reply.compileSerializationSchema(getDefaultSchema())
62
+ const input = { hello: 'world' }
63
+ t.type(serialize, Function)
64
+ t.type(serialize(input), 'string')
65
+ t.equal(serialize(input), JSON.stringify(input))
66
+
67
+ try {
68
+ serialize({ world: 'foo' })
69
+ } catch (err) {
70
+ t.equal(err.message, '"hello" is required!')
71
+ }
72
+
73
+ reply.send({ hello: 'world' })
74
+ })
75
+
76
+ await fastify.inject({
77
+ path: '/',
78
+ method: 'GET'
79
+ })
80
+ })
81
+
82
+ t.test('Should reuse the serialize fn across multiple invocations - Route without schema',
83
+ async t => {
84
+ const fastify = Fastify()
85
+ let serialize = null
86
+ let counter = 0
87
+
88
+ t.plan(17)
89
+
90
+ const schemaObj = getDefaultSchema()
91
+
92
+ fastify.get('/', (req, reply) => {
93
+ const input = { hello: 'world' }
94
+ counter++
95
+ if (counter > 1) {
96
+ const newSerialize = reply.compileSerializationSchema(schemaObj)
97
+ t.equal(serialize, newSerialize, 'Are the same validate function')
98
+ serialize = newSerialize
99
+ } else {
100
+ t.pass('build the schema compilation function')
101
+ serialize = reply.compileSerializationSchema(schemaObj)
102
+ }
103
+
104
+ t.type(serialize, Function)
105
+ t.equal(serialize(input), JSON.stringify(input))
106
+
107
+ try {
108
+ serialize({ world: 'foo' })
109
+ } catch (err) {
110
+ t.equal(err.message, '"hello" is required!')
111
+ }
112
+
113
+ reply.send({ hello: 'world' })
114
+ })
115
+
116
+ await Promise.all([
117
+ fastify.inject('/'),
118
+ fastify.inject('/'),
119
+ fastify.inject('/'),
120
+ fastify.inject('/')
121
+ ])
122
+
123
+ t.equal(counter, 4)
124
+ }
125
+ )
126
+
127
+ t.test('Should use the custom serializer compiler for the route',
128
+ async t => {
129
+ const fastify = Fastify()
130
+ let called = 0
131
+ const custom = ({ schema, httpStatus, url, method }) => {
132
+ t.equal(schema, schemaObj)
133
+ t.equal(url, '/')
134
+ t.equal(method, 'GET')
135
+ t.equal(httpStatus, '201')
136
+
137
+ return input => {
138
+ called++
139
+ t.same(input, { hello: 'world' })
140
+ return JSON.stringify(input)
141
+ }
142
+ }
143
+
144
+ t.plan(10)
145
+ const schemaObj = getDefaultSchema()
146
+
147
+ fastify.get('/', { serializerCompiler: custom }, (req, reply) => {
148
+ const input = { hello: 'world' }
149
+ const first = reply.compileSerializationSchema(schemaObj, '201')
150
+ const second = reply.compileSerializationSchema(schemaObj, '201')
151
+
152
+ t.equal(first, second)
153
+ t.ok(first(input), JSON.stringify(input))
154
+ t.ok(second(input), JSON.stringify(input))
155
+ t.equal(called, 2)
156
+
157
+ reply.send({ hello: 'world' })
158
+ })
159
+
160
+ await fastify.inject({
161
+ path: '/',
162
+ method: 'GET'
163
+ })
164
+ }
165
+ )
166
+
167
+ t.test('Should build a WeakMap for cache when called', async t => {
168
+ const fastify = Fastify()
169
+
170
+ t.plan(4)
171
+
172
+ fastify.get('/', (req, reply) => {
173
+ const input = { hello: 'world' }
174
+
175
+ t.equal(reply.context[kReplySerializeWeakMap], null)
176
+ t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input))
177
+ t.type(reply.context[kReplySerializeWeakMap], WeakMap)
178
+ t.equal(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input))
179
+
180
+ reply.send({ hello: 'world' })
181
+ })
182
+
183
+ await fastify.inject({
184
+ path: '/',
185
+ method: 'GET'
186
+ })
187
+ })
188
+ })
189
+
190
+ test('Reply#getSerializationFunction', t => {
191
+ t.plan(3)
192
+
193
+ t.test('Should retrieve the serialization function from the Schema definition',
194
+ async t => {
195
+ const fastify = Fastify()
196
+ const okInput201 = {
197
+ status: 'ok',
198
+ message: 'done!'
199
+ }
200
+ const notOkInput201 = {
201
+ message: 'created'
202
+ }
203
+ const okInput4xx = {
204
+ status: 'error',
205
+ code: 2,
206
+ message: 'oops!'
207
+ }
208
+ const notOkInput4xx = {
209
+ status: 'error',
210
+ code: 'something'
211
+ }
212
+ let cached4xx
213
+ let cached201
214
+
215
+ t.plan(9)
216
+
217
+ const responseSchema = getResponseSchema()
218
+
219
+ fastify.get(
220
+ '/:id',
221
+ {
222
+ params: {
223
+ id: {
224
+ type: 'integer'
225
+ }
226
+ },
227
+ schema: {
228
+ response: responseSchema
229
+ }
230
+ },
231
+ (req, reply) => {
232
+ const { id } = req.params
233
+
234
+ if (parseInt(id) === 1) {
235
+ const serialize4xx = reply.getSerializationFunction('4xx')
236
+ const serialize201 = reply.getSerializationFunction(201)
237
+ const serializeUndefined = reply.getSerializationFunction(undefined)
238
+
239
+ cached4xx = serialize4xx
240
+ cached201 = serialize201
241
+
242
+ t.type(serialize4xx, Function)
243
+ t.type(serialize201, Function)
244
+ t.equal(serialize4xx(okInput4xx), JSON.stringify(okInput4xx))
245
+ t.equal(serialize201(okInput201), JSON.stringify(okInput201))
246
+ t.notOk(serializeUndefined)
247
+
248
+ try {
249
+ serialize4xx(notOkInput4xx)
250
+ } catch (err) {
251
+ t.equal(
252
+ err.message,
253
+ 'The value "something" cannot be converted to an integer.'
254
+ )
255
+ }
256
+
257
+ try {
258
+ serialize201(notOkInput201)
259
+ } catch (err) {
260
+ t.equal(err.message, '"status" is required!')
261
+ }
262
+
263
+ reply.status(201).send(okInput201)
264
+ } else {
265
+ const serialize201 = reply.getSerializationFunction(201)
266
+ const serialize4xx = reply.getSerializationFunction('4xx')
267
+
268
+ t.equal(serialize4xx, cached4xx)
269
+ t.equal(serialize201, cached201)
270
+ reply.status(401).send(okInput4xx)
271
+ }
272
+ }
273
+ )
274
+
275
+ await Promise.all([
276
+ fastify.inject('/1'),
277
+ fastify.inject('/2')
278
+ ])
279
+ }
280
+ )
281
+
282
+ t.test('Should retrieve the serialization function from the cached one',
283
+ async t => {
284
+ const fastify = Fastify()
285
+
286
+ const schemaObj = getDefaultSchema()
287
+
288
+ const okInput = {
289
+ hello: 'world',
290
+ world: 'done!'
291
+ }
292
+ const notOkInput = {
293
+ world: 'done!'
294
+ }
295
+ let cached
296
+
297
+ t.plan(6)
298
+
299
+ fastify.get(
300
+ '/:id',
301
+ {
302
+ params: {
303
+ id: {
304
+ type: 'integer'
305
+ }
306
+ }
307
+ },
308
+ (req, reply) => {
309
+ const { id } = req.params
310
+
311
+ if (parseInt(id) === 1) {
312
+ const serialize = reply.compileSerializationSchema(schemaObj)
313
+
314
+ t.type(serialize, Function)
315
+ t.equal(serialize(okInput), JSON.stringify(okInput))
316
+
317
+ try {
318
+ serialize(notOkInput)
319
+ } catch (err) {
320
+ t.equal(err.message, '"hello" is required!')
321
+ }
322
+
323
+ cached = serialize
324
+ } else {
325
+ const serialize = reply.getSerializationFunction(schemaObj)
326
+
327
+ t.equal(serialize, cached)
328
+ t.equal(serialize(okInput), JSON.stringify(okInput))
329
+
330
+ try {
331
+ serialize(notOkInput)
332
+ } catch (err) {
333
+ t.equal(err.message, '"hello" is required!')
334
+ }
335
+ }
336
+
337
+ reply.status(201).send(okInput)
338
+ }
339
+ )
340
+
341
+ await Promise.all([
342
+ fastify.inject('/1'),
343
+ fastify.inject('/2')
344
+ ])
345
+ }
346
+ )
347
+
348
+ t.test('Should not instantiate a WeakMap if it is not needed', async t => {
349
+ const fastify = Fastify()
350
+
351
+ t.plan(4)
352
+
353
+ fastify.get('/', (req, reply) => {
354
+ t.notOk(reply.getSerializationFunction(getDefaultSchema()))
355
+ t.equal(reply.context[kReplySerializeWeakMap], null)
356
+ t.notOk(reply.getSerializationFunction('200'))
357
+ t.equal(reply.context[kReplySerializeWeakMap], null)
358
+
359
+ reply.send({ hello: 'world' })
360
+ })
361
+
362
+ await fastify.inject({
363
+ path: '/',
364
+ method: 'GET'
365
+ })
366
+ })
367
+ })
368
+
369
+ test('Reply#serializeInput', t => {
370
+ t.plan(5)
371
+
372
+ t.test(
373
+ 'Should throw if missed serialization function from HTTP status',
374
+ async t => {
375
+ const fastify = Fastify()
376
+
377
+ t.plan(2)
378
+
379
+ fastify.get('/', (req, reply) => {
380
+ reply.serializeInput({}, 201)
381
+ })
382
+
383
+ const result = await fastify.inject({
384
+ path: '/',
385
+ method: 'GET'
386
+ })
387
+
388
+ t.equal(result.statusCode, 500)
389
+ t.same(result.json(), {
390
+ statusCode: 500,
391
+ code: 'FST_ERR_MISSING_SERIALIZATION_FN',
392
+ error: 'Internal Server Error',
393
+ message: 'Missing serialization function. Key "201"'
394
+ })
395
+ }
396
+ )
397
+
398
+ t.test('Should use a serializer fn from HTTP status', async t => {
399
+ const fastify = Fastify()
400
+ const okInput201 = {
401
+ status: 'ok',
402
+ message: 'done!'
403
+ }
404
+ const notOkInput201 = {
405
+ message: 'created'
406
+ }
407
+ const okInput4xx = {
408
+ status: 'error',
409
+ code: 2,
410
+ message: 'oops!'
411
+ }
412
+ const notOkInput4xx = {
413
+ status: 'error',
414
+ code: 'something'
415
+ }
416
+
417
+ t.plan(4)
418
+
419
+ fastify.get(
420
+ '/',
421
+ {
422
+ params: {
423
+ id: {
424
+ type: 'integer'
425
+ }
426
+ },
427
+ schema: {
428
+ response: getResponseSchema()
429
+ }
430
+ },
431
+ (req, reply) => {
432
+ t.equal(
433
+ reply.serializeInput(okInput4xx, '4xx'),
434
+ JSON.stringify(okInput4xx)
435
+ )
436
+ t.equal(
437
+ reply.serializeInput(okInput201, 201),
438
+ JSON.stringify(okInput201)
439
+ )
440
+
441
+ try {
442
+ reply.serializeInput(notOkInput4xx, '4xx')
443
+ } catch (err) {
444
+ t.equal(
445
+ err.message,
446
+ 'The value "something" cannot be converted to an integer.'
447
+ )
448
+ }
449
+
450
+ try {
451
+ reply.serializeInput(notOkInput201, 201)
452
+ } catch (err) {
453
+ t.equal(err.message, '"status" is required!')
454
+ }
455
+
456
+ reply.status(204).send('')
457
+ }
458
+ )
459
+
460
+ await fastify.inject({
461
+ path: '/',
462
+ method: 'GET'
463
+ })
464
+ })
465
+
466
+ t.test(
467
+ 'Should compile a serializer out of a schema if serializer fn missed',
468
+ async t => {
469
+ let compilerCalled = 0
470
+ let serializerCalled = 0
471
+ const testInput = { hello: 'world' }
472
+ const schemaObj = getDefaultSchema()
473
+ const fastify = Fastify()
474
+ const serializerCompiler = ({ schema, httpStatus, method, url }) => {
475
+ t.equal(schema, schemaObj)
476
+ t.notOk(httpStatus)
477
+ t.equal(method, 'GET')
478
+ t.equal(url, '/')
479
+
480
+ compilerCalled++
481
+ return input => {
482
+ t.equal(input, testInput)
483
+ serializerCalled++
484
+ return JSON.stringify(input)
485
+ }
486
+ }
487
+
488
+ t.plan(10)
489
+
490
+ fastify.get('/', { serializerCompiler }, (req, reply) => {
491
+ t.equal(
492
+ reply.serializeInput(testInput, schemaObj),
493
+ JSON.stringify(testInput)
494
+ )
495
+
496
+ t.equal(
497
+ reply.serializeInput(testInput, schemaObj),
498
+ JSON.stringify(testInput)
499
+ )
500
+
501
+ reply.status(201).send(testInput)
502
+ })
503
+
504
+ await fastify.inject({
505
+ path: '/',
506
+ method: 'GET'
507
+ })
508
+
509
+ t.equal(compilerCalled, 1)
510
+ t.equal(serializerCalled, 2)
511
+ }
512
+ )
513
+
514
+ t.test('Should use a cached serializer fn', async t => {
515
+ let compilerCalled = 0
516
+ let serializerCalled = 0
517
+ let cached
518
+ const testInput = { hello: 'world' }
519
+ const schemaObj = getDefaultSchema()
520
+ const fastify = Fastify()
521
+ const serializer = input => {
522
+ t.equal(input, testInput)
523
+ serializerCalled++
524
+ return JSON.stringify(input)
525
+ }
526
+ const serializerCompiler = ({ schema, httpStatus, method, url }) => {
527
+ t.equal(schema, schemaObj)
528
+ t.notOk(httpStatus)
529
+ t.equal(method, 'GET')
530
+ t.equal(url, '/')
531
+
532
+ compilerCalled++
533
+ return serializer
534
+ }
535
+
536
+ t.plan(12)
537
+
538
+ fastify.get('/', { serializerCompiler }, (req, reply) => {
539
+ t.equal(
540
+ reply.serializeInput(testInput, schemaObj),
541
+ JSON.stringify(testInput)
542
+ )
543
+
544
+ cached = reply.getSerializationFunction(schemaObj)
545
+
546
+ t.equal(
547
+ reply.serializeInput(testInput, schemaObj),
548
+ cached(testInput)
549
+ )
550
+
551
+ reply.status(201).send(testInput)
552
+ })
553
+
554
+ await fastify.inject({
555
+ path: '/',
556
+ method: 'GET'
557
+ })
558
+
559
+ t.equal(cached, serializer)
560
+ t.equal(compilerCalled, 1)
561
+ t.equal(serializerCalled, 3)
562
+ })
563
+
564
+ t.test('Should instantiate a WeakMap after first call', async t => {
565
+ const fastify = Fastify()
566
+
567
+ t.plan(3)
568
+
569
+ fastify.get('/', (req, reply) => {
570
+ const input = { hello: 'world' }
571
+ t.equal(reply.context[kReplySerializeWeakMap], null)
572
+ t.equal(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input))
573
+ t.type(reply.context[kReplySerializeWeakMap], WeakMap)
574
+
575
+ reply.send({ hello: 'world' })
576
+ })
577
+
578
+ await fastify.inject({
579
+ path: '/',
580
+ method: 'GET'
581
+ })
582
+ })
583
+ })