fastify 2.4.1 → 2.7.1

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 (51) hide show
  1. package/README.md +1 -1
  2. package/SECURITY.md +33 -0
  3. package/build/build-validation.js +1 -1
  4. package/docs/Decorators.md +2 -2
  5. package/docs/Ecosystem.md +5 -1
  6. package/docs/Fluent-Schema.md +5 -7
  7. package/docs/Hooks.md +40 -40
  8. package/docs/Logging.md +15 -3
  9. package/docs/Plugins-Guide.md +21 -21
  10. package/docs/Plugins.md +11 -11
  11. package/docs/Reply.md +14 -7
  12. package/docs/Routes.md +6 -6
  13. package/docs/Server.md +40 -19
  14. package/docs/Serverless.md +127 -28
  15. package/docs/Validation-and-Serialization.md +15 -12
  16. package/fastify.d.ts +22 -14
  17. package/fastify.js +21 -0
  18. package/lib/context.js +5 -4
  19. package/lib/decorate.js +2 -0
  20. package/lib/errors.js +1 -0
  21. package/lib/handleRequest.js +2 -2
  22. package/lib/reply.js +23 -6
  23. package/lib/request.js +2 -2
  24. package/lib/route.js +24 -15
  25. package/lib/schemas.js +24 -3
  26. package/lib/symbols.js +1 -0
  27. package/lib/validation.js +19 -0
  28. package/package.json +19 -18
  29. package/test/async-await.js +1 -1
  30. package/test/close-pipelining.test.js +43 -2
  31. package/test/close.test.js +69 -12
  32. package/test/content-length.test.js +2 -2
  33. package/test/decorator.test.js +2 -0
  34. package/test/fluent-schema.js +54 -0
  35. package/test/hooks-async.js +80 -1
  36. package/test/hooks.test.js +4 -4
  37. package/test/http2/closing.js +86 -33
  38. package/test/input-validation.test.js +1 -1
  39. package/test/internals/decorator.test.js +2 -0
  40. package/test/internals/initialConfig.test.js +1 -1
  41. package/test/internals/reply.test.js +276 -1
  42. package/test/internals/validation.test.js +57 -0
  43. package/test/logger.test.js +33 -1
  44. package/test/nullable-validation.test.js +52 -0
  45. package/test/plugin.test.js +2 -0
  46. package/test/register.test.js +2 -0
  47. package/test/route-prefix.test.js +31 -0
  48. package/test/route.test.js +35 -0
  49. package/test/shared-schemas.test.js +3 -3
  50. package/test/types/index.ts +33 -2
  51. package/test/versioned-routes.test.js +3 -3
@@ -74,6 +74,37 @@ test('build schema - payload schema', t => {
74
74
  t.is(typeof opts[symbols.bodySchema], 'function')
75
75
  })
76
76
 
77
+ test('build schema - query schema', t => {
78
+ t.plan(2)
79
+ const opts = {
80
+ schema: {
81
+ query: {
82
+ type: 'object',
83
+ properties: {
84
+ hello: { type: 'string' }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ validation.build(opts, schema => ajv.compile(schema), new Schemas())
90
+ t.type(opts[symbols.querystringSchema].schema.type, 'string')
91
+ t.is(typeof opts[symbols.querystringSchema], 'function')
92
+ })
93
+
94
+ test('build schema - query schema abbreviated', t => {
95
+ t.plan(2)
96
+ const opts = {
97
+ schema: {
98
+ query: {
99
+ hello: { type: 'string' }
100
+ }
101
+ }
102
+ }
103
+ validation.build(opts, schema => ajv.compile(schema), new Schemas())
104
+ t.type(opts[symbols.querystringSchema].schema.type, 'string')
105
+ t.is(typeof opts[symbols.querystringSchema], 'function')
106
+ })
107
+
77
108
  test('build schema - querystring schema', t => {
78
109
  t.plan(2)
79
110
  const opts = {
@@ -105,6 +136,32 @@ test('build schema - querystring schema abbreviated', t => {
105
136
  t.is(typeof opts[symbols.querystringSchema], 'function')
106
137
  })
107
138
 
139
+ test('build schema - must throw if querystring and query schema exist', t => {
140
+ t.plan(2)
141
+ try {
142
+ const opts = {
143
+ schema: {
144
+ query: {
145
+ type: 'object',
146
+ properties: {
147
+ hello: { type: 'string' }
148
+ }
149
+ },
150
+ querystring: {
151
+ type: 'object',
152
+ properties: {
153
+ hello: { type: 'string' }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ validation.build(opts, schema => ajv.compile(schema), new Schemas())
159
+ } catch (err) {
160
+ t.is(err.code, 'FST_ERR_SCH_DUPLICATE')
161
+ t.is(err.message, 'FST_ERR_SCH_DUPLICATE: Schema with \'querystring\' already present!')
162
+ }
163
+ })
164
+
108
165
  test('build schema - params schema', t => {
109
166
  t.plan(1)
110
167
  const opts = {
@@ -1072,6 +1072,38 @@ test('should not log the error if error handler is defined', t => {
1072
1072
  })
1073
1073
  })
1074
1074
 
1075
+ test('should not rely on raw request to log errors', t => {
1076
+ t.plan(7)
1077
+ const stream = split(JSON.parse)
1078
+ const fastify = Fastify({
1079
+ modifyCoreObjects: false,
1080
+ logger: {
1081
+ stream: stream,
1082
+ level: 'info'
1083
+ }
1084
+ })
1085
+ fastify.get('/error', function (req, reply) {
1086
+ t.ok(req.log)
1087
+ reply.status(415).send(new Error('something happened'))
1088
+ })
1089
+ fastify.listen(0, err => {
1090
+ t.error(err)
1091
+ fastify.server.unref()
1092
+ http.get('http://localhost:' + fastify.server.address().port + '/error')
1093
+ stream.once('data', listenAtLogLine => {
1094
+ t.ok(listenAtLogLine, 'listen at log message is ok')
1095
+ stream.once('data', line => {
1096
+ t.equal(line.msg, 'incoming request', 'message is set')
1097
+ stream.once('data', line => {
1098
+ t.equal(line.level, 30, 'level is correct')
1099
+ t.equal(line.msg, 'something happened', 'message is set')
1100
+ t.deepEqual(line.res, { statusCode: 415 }, 'status code is set')
1101
+ })
1102
+ })
1103
+ })
1104
+ })
1105
+ })
1106
+
1075
1107
  test('should redact the authorization header if so specified', t => {
1076
1108
  t.plan(7)
1077
1109
  const stream = split(JSON.parse)
@@ -1111,7 +1143,7 @@ test('should redact the authorization header if so specified', t => {
1111
1143
  method: 'GET',
1112
1144
  url: 'http://localhost:' + fastify.server.address().port,
1113
1145
  headers: {
1114
- 'authorization': 'Bearer abcde'
1146
+ authorization: 'Bearer abcde'
1115
1147
  }
1116
1148
  }, (err, response, body) => {
1117
1149
  t.error(err)
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('..')
6
+
7
+ test('nullable string', t => {
8
+ t.plan(3)
9
+ const fastify = Fastify()
10
+ fastify.route({
11
+ method: 'POST',
12
+ url: '/',
13
+ handler: (req, reply) => {
14
+ t.same(req.body.hello, null)
15
+ reply.code(200).send(req.body)
16
+ },
17
+ schema: {
18
+ body: {
19
+ type: 'object',
20
+ properties: {
21
+ hello: {
22
+ type: 'string',
23
+ format: 'email',
24
+ nullable: true
25
+ }
26
+ }
27
+ },
28
+ response: {
29
+ 200: {
30
+ type: 'object',
31
+ properties: {
32
+ hello: {
33
+ type: 'string',
34
+ format: 'email',
35
+ nullable: true
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ })
42
+ fastify.inject({
43
+ method: 'POST',
44
+ url: '/',
45
+ body: {
46
+ hello: null
47
+ }
48
+ }, (err, res) => {
49
+ t.error(err)
50
+ t.same(res.payload.hello, null)
51
+ })
52
+ })
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ /* eslint no-prototype-builtins: 0 */
4
+
3
5
  const t = require('tap')
4
6
  const test = t.test
5
7
  const Fastify = require('..')
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ /* eslint no-prototype-builtins: 0 */
4
+
3
5
  const t = require('tap')
4
6
  const test = t.test
5
7
  const sget = require('simple-get').concat
@@ -459,6 +459,37 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "
459
459
  })
460
460
  })
461
461
 
462
+ test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', t => {
463
+ t.plan(2)
464
+ const fastify = Fastify({
465
+ ignoreTrailingSlash: true
466
+ })
467
+
468
+ fastify.register(function (fastify, opts, next) {
469
+ fastify.route({
470
+ method: 'GET',
471
+ url: '/',
472
+ handler: (req, reply) => {
473
+ reply.send({ hello: 'world' })
474
+ }
475
+ })
476
+
477
+ next()
478
+ }, { prefix: '/prefix/' })
479
+
480
+ fastify.inject({
481
+ method: 'GET',
482
+ url: '/prefix//'
483
+ }, (err, res) => {
484
+ t.error(err)
485
+ t.same(JSON.parse(res.payload), {
486
+ error: 'Not Found',
487
+ message: 'Not Found',
488
+ statusCode: 404
489
+ })
490
+ })
491
+ })
492
+
462
493
  test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', t => {
463
494
  t.plan(4)
464
495
  const fastify = Fastify({
@@ -3,6 +3,7 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const sget = require('simple-get').concat
6
+ const joi = require('joi')
6
7
  const Fastify = require('..')
7
8
 
8
9
  test('route', t => {
@@ -255,3 +256,37 @@ test('handler function in options of shorthand route should works correctly', t
255
256
  t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
256
257
  })
257
258
  })
259
+
260
+ test('does not mutate joi schemas', t => {
261
+ t.plan(4)
262
+
263
+ const fastify = Fastify()
264
+ function schemaCompiler (schema) {
265
+ return function (data, opts) {
266
+ return joi.validate(data, schema)
267
+ }
268
+ }
269
+
270
+ fastify.setSchemaCompiler(schemaCompiler)
271
+
272
+ fastify.route({
273
+ path: '/foo/:an_id',
274
+ method: 'GET',
275
+ schema: {
276
+ params: { an_id: joi.number() }
277
+ },
278
+ handler (req, res) {
279
+ t.deepEqual(req.params, { an_id: 42 })
280
+ res.send({ hello: 'world' })
281
+ }
282
+ })
283
+
284
+ fastify.inject({
285
+ method: 'GET',
286
+ url: '/foo/42'
287
+ }, (err, result) => {
288
+ t.error(err)
289
+ t.strictEqual(result.statusCode, 200)
290
+ t.deepEqual(JSON.parse(result.payload), { hello: 'world' })
291
+ })
292
+ })
@@ -565,7 +565,7 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => {
565
565
  $id: '#address',
566
566
  type: 'object',
567
567
  properties: {
568
- city: { 'type': 'string' }
568
+ city: { type: 'string' }
569
569
  }
570
570
  }
571
571
  },
@@ -624,7 +624,7 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => {
624
624
  $id: '#address',
625
625
  type: 'object',
626
626
  properties: {
627
- city: { 'type': 'string' }
627
+ city: { type: 'string' }
628
628
  }
629
629
  }
630
630
  },
@@ -1089,7 +1089,7 @@ test('Use shared schema and $ref to /definitions', t => {
1089
1089
  $id: '#otherId',
1090
1090
  type: 'object',
1091
1091
  properties: {
1092
- city: { 'type': 'string' }
1092
+ city: { type: 'string' }
1093
1093
  }
1094
1094
  }
1095
1095
  },
@@ -58,11 +58,13 @@ const cors = require('cors')
58
58
 
59
59
  // other simple options
60
60
  const otherServer = fastify({
61
+ caseSensitive: false,
61
62
  ignoreTrailingSlash: true,
62
63
  bodyLimit: 1000,
63
64
  maxParamLength: 200,
64
65
  querystringParser: (str: string) => ({ str: str, strArray: [str] }),
65
- modifyCoreObjects: true
66
+ modifyCoreObjects: true,
67
+ return503OnClosing: true
66
68
  })
67
69
 
68
70
  // custom types
@@ -188,7 +190,7 @@ const schema: fastify.RouteSchema = {
188
190
  }
189
191
  }
190
192
 
191
- const opts: fastify.RouteShorthandOptions<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse> = {
193
+ const opts: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> = {
192
194
  schema,
193
195
  preValidation: [
194
196
  (request, reply, next) => {
@@ -345,6 +347,10 @@ server
345
347
  .get('/deprecatedpath/*', (req, reply) => {
346
348
  reply.callNotFound()
347
349
  })
350
+ .get('/getResponseTime', function (req, reply) {
351
+ const milliseconds : number = reply.getResponseTime()
352
+ reply.send({ milliseconds })
353
+ })
348
354
 
349
355
  // Generics example
350
356
  interface Query {
@@ -440,6 +446,13 @@ server.setErrorHandler((err, request, reply) => {
440
446
  }
441
447
  })
442
448
 
449
+ server.setReplySerializer((payload, statusCode) => {
450
+ if (statusCode === 201) {
451
+ return `Created ${payload}`
452
+ }
453
+ return JSON.stringify(payload)
454
+ })
455
+
443
456
  server.listen(3000, err => {
444
457
  if (err) throw err
445
458
  const address = server.server.address()
@@ -604,3 +617,21 @@ const server2 = fastify()
604
617
  server2.close().then(() => {})
605
618
  const server3 = fastify()
606
619
  server3.close(() => {})
620
+
621
+ {
622
+ // tests generics default values
623
+ const routeOptions: fastify.RouteOptions = {
624
+ method: 'GET',
625
+ url: '/',
626
+ handler: function (req, reply) { reply.send({}) }
627
+ }
628
+
629
+ const genericHandler: fastify.RequestHandler = (req, reply) => { reply.send(reply) }
630
+
631
+ const middleware: fastify.FastifyMiddleware = function middleware (req, reply, done) {
632
+ this.addHook('onClose', function (instance, done) {
633
+ done()
634
+ })
635
+ done()
636
+ }
637
+ }
@@ -445,7 +445,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
445
445
  method: 'GET',
446
446
  url: '/',
447
447
  headers: {
448
- 'Accept': 'application/vnd.example.api+json;version=2'
448
+ Accept: 'application/vnd.example.api+json;version=2'
449
449
  }
450
450
  }, (err, res) => {
451
451
  t.error(err)
@@ -457,7 +457,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
457
457
  method: 'GET',
458
458
  url: '/',
459
459
  headers: {
460
- 'Accept': 'application/vnd.example.api+json;version=3'
460
+ Accept: 'application/vnd.example.api+json;version=3'
461
461
  }
462
462
  }, (err, res) => {
463
463
  t.error(err)
@@ -469,7 +469,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
469
469
  method: 'GET',
470
470
  url: '/',
471
471
  headers: {
472
- 'Accept': 'application/vnd.example.api+json;version=4'
472
+ Accept: 'application/vnd.example.api+json;version=4'
473
473
  }
474
474
  }, (err, res) => {
475
475
  t.error(err)