fastify 2.7.1 → 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.
Files changed (72) hide show
  1. package/README.md +15 -4
  2. package/build/build-validation.js +8 -0
  3. package/docs/Benchmarking.md +2 -2
  4. package/docs/ContentTypeParser.md +12 -10
  5. package/docs/Decorators.md +14 -14
  6. package/docs/Ecosystem.md +7 -1
  7. package/docs/Errors.md +13 -8
  8. package/docs/Fluent-Schema.md +9 -12
  9. package/docs/Getting-Started.md +29 -25
  10. package/docs/HTTP2.md +1 -1
  11. package/docs/Hooks.md +201 -186
  12. package/docs/LTS.md +6 -7
  13. package/docs/Logging.md +10 -10
  14. package/docs/Middleware.md +59 -0
  15. package/docs/Plugins-Guide.md +52 -52
  16. package/docs/Plugins.md +3 -0
  17. package/docs/Reply.md +47 -3
  18. package/docs/Routes.md +120 -8
  19. package/docs/Server.md +69 -3
  20. package/docs/Serverless.md +76 -4
  21. package/docs/TypeScript.md +33 -10
  22. package/docs/Validation-and-Serialization.md +137 -1
  23. package/examples/typescript-server.ts +1 -1
  24. package/fastify.d.ts +52 -13
  25. package/fastify.js +68 -7
  26. package/lib/configValidator.js +99 -52
  27. package/lib/contentTypeParser.js +4 -4
  28. package/lib/context.js +2 -1
  29. package/lib/errors.js +21 -18
  30. package/lib/fourOhFour.js +10 -10
  31. package/lib/handleRequest.js +1 -2
  32. package/lib/logger.js +2 -2
  33. package/lib/pluginUtils.js +32 -0
  34. package/lib/reply.js +41 -6
  35. package/lib/route.js +37 -9
  36. package/lib/schemas.js +23 -12
  37. package/lib/symbols.js +4 -1
  38. package/lib/validation.js +15 -9
  39. package/lib/wrapThenable.js +1 -1
  40. package/package.json +34 -26
  41. package/test/404s.test.js +41 -1
  42. package/test/async-await.js +66 -0
  43. package/test/custom-parser.test.js +1 -1
  44. package/test/custom-querystring-parser.test.js +1 -1
  45. package/test/decorator.test.js +48 -0
  46. package/test/emit-warning.test.js +3 -3
  47. package/test/fastify-instance.test.js +29 -0
  48. package/test/helper.js +7 -7
  49. package/test/hooks-async.js +4 -3
  50. package/test/hooks.test.js +27 -8
  51. package/test/input-validation.test.js +126 -0
  52. package/test/internals/errors.test.js +9 -1
  53. package/test/internals/initialConfig.test.js +4 -2
  54. package/test/internals/plugin.test.js +4 -4
  55. package/test/internals/reply.test.js +78 -6
  56. package/test/internals/schemas.test.js +30 -0
  57. package/test/internals/validation.test.js +18 -0
  58. package/test/listen.test.js +1 -1
  59. package/test/logger.test.js +314 -1
  60. package/test/plugin.test.js +171 -0
  61. package/test/promises.test.js +55 -0
  62. package/test/proto-poisoning.test.js +76 -0
  63. package/test/route-hooks.test.js +109 -91
  64. package/test/route-prefix.test.js +1 -1
  65. package/test/schemas.test.js +450 -0
  66. package/test/shared-schemas.test.js +2 -2
  67. package/test/stream.test.js +10 -6
  68. package/test/throw.test.js +48 -2
  69. package/test/types/index.ts +86 -1
  70. package/test/validation-error-handling.test.js +3 -3
  71. package/test/versioned-routes.test.js +1 -1
  72. package/docs/Middlewares.md +0 -59
@@ -14,10 +14,11 @@ var validate = (function() {
14
14
  if ((data && typeof data === "object" && !Array.isArray(data))) {
15
15
  if (data.bodyLimit === undefined) data.bodyLimit = 1048576;
16
16
  if (data.caseSensitive === undefined) data.caseSensitive = true;
17
- if (data.disableRequestLogging === undefined) data.disableRequestLogging = false;
18
17
  if (data.ignoreTrailingSlash === undefined) data.ignoreTrailingSlash = false;
18
+ if (data.disableRequestLogging === undefined) data.disableRequestLogging = false;
19
19
  if (data.maxParamLength === undefined) data.maxParamLength = 100;
20
20
  if (data.onProtoPoisoning === undefined) data.onProtoPoisoning = "error";
21
+ if (data.onConstructorPoisoning === undefined) data.onConstructorPoisoning = "ignore";
21
22
  if (data.pluginTimeout === undefined) data.pluginTimeout = 10000;
22
23
  if (data.requestIdHeader === undefined) data.requestIdHeader = "request-id";
23
24
  if (data.requestIdLogLabel === undefined) data.requestIdLogLabel = "reqId";
@@ -321,80 +322,81 @@ var validate = (function() {
321
322
  }
322
323
  var valid1 = errors === errs_1;
323
324
  if (valid1) {
324
- var data1 = data.maxParamLength;
325
+ var data1 = data.disableRequestLogging;
325
326
  var errs_1 = errors;
326
- if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
327
+ if (typeof data1 !== "boolean") {
327
328
  var dataType1 = typeof data1;
328
329
  var coerced1 = undefined;
329
- if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1;
330
+ if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false;
331
+ else if (data1 === 'true' || data1 === 1) coerced1 = true;
330
332
  if (coerced1 === undefined) {
331
333
  validate.errors = [{
332
334
  keyword: 'type',
333
- dataPath: (dataPath || '') + '.maxParamLength',
334
- schemaPath: '#/properties/maxParamLength/type',
335
+ dataPath: (dataPath || '') + '.disableRequestLogging',
336
+ schemaPath: '#/properties/disableRequestLogging/type',
335
337
  params: {
336
- type: 'integer'
338
+ type: 'boolean'
337
339
  },
338
- message: 'should be integer'
340
+ message: 'should be boolean'
339
341
  }];
340
342
  return false;
341
343
  } else {
342
344
  data1 = coerced1;
343
- data['maxParamLength'] = coerced1;
345
+ data['disableRequestLogging'] = coerced1;
344
346
  }
345
347
  }
346
348
  var valid1 = errors === errs_1;
347
349
  if (valid1) {
348
- var data1 = data.onProtoPoisoning;
350
+ var data1 = data.maxParamLength;
349
351
  var errs_1 = errors;
350
- if (typeof data1 !== "string") {
352
+ if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
351
353
  var dataType1 = typeof data1;
352
354
  var coerced1 = undefined;
353
- if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1;
354
- else if (data1 === null) coerced1 = '';
355
+ if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1;
355
356
  if (coerced1 === undefined) {
356
357
  validate.errors = [{
357
358
  keyword: 'type',
358
- dataPath: (dataPath || '') + '.onProtoPoisoning',
359
- schemaPath: '#/properties/onProtoPoisoning/type',
359
+ dataPath: (dataPath || '') + '.maxParamLength',
360
+ schemaPath: '#/properties/maxParamLength/type',
360
361
  params: {
361
- type: 'string'
362
+ type: 'integer'
362
363
  },
363
- message: 'should be string'
364
+ message: 'should be integer'
364
365
  }];
365
366
  return false;
366
367
  } else {
367
368
  data1 = coerced1;
368
- data['onProtoPoisoning'] = coerced1;
369
+ data['maxParamLength'] = coerced1;
369
370
  }
370
371
  }
371
372
  var valid1 = errors === errs_1;
372
373
  if (valid1) {
373
- var data1 = data.pluginTimeout;
374
+ var data1 = data.onProtoPoisoning;
374
375
  var errs_1 = errors;
375
- if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
376
+ if (typeof data1 !== "string") {
376
377
  var dataType1 = typeof data1;
377
378
  var coerced1 = undefined;
378
- if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1;
379
+ if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1;
380
+ else if (data1 === null) coerced1 = '';
379
381
  if (coerced1 === undefined) {
380
382
  validate.errors = [{
381
383
  keyword: 'type',
382
- dataPath: (dataPath || '') + '.pluginTimeout',
383
- schemaPath: '#/properties/pluginTimeout/type',
384
+ dataPath: (dataPath || '') + '.onProtoPoisoning',
385
+ schemaPath: '#/properties/onProtoPoisoning/type',
384
386
  params: {
385
- type: 'integer'
387
+ type: 'string'
386
388
  },
387
- message: 'should be integer'
389
+ message: 'should be string'
388
390
  }];
389
391
  return false;
390
392
  } else {
391
393
  data1 = coerced1;
392
- data['pluginTimeout'] = coerced1;
394
+ data['onProtoPoisoning'] = coerced1;
393
395
  }
394
396
  }
395
397
  var valid1 = errors === errs_1;
396
398
  if (valid1) {
397
- var data1 = data.requestIdHeader;
399
+ var data1 = data.onConstructorPoisoning;
398
400
  var errs_1 = errors;
399
401
  if (typeof data1 !== "string") {
400
402
  var dataType1 = typeof data1;
@@ -404,8 +406,8 @@ var validate = (function() {
404
406
  if (coerced1 === undefined) {
405
407
  validate.errors = [{
406
408
  keyword: 'type',
407
- dataPath: (dataPath || '') + '.requestIdHeader',
408
- schemaPath: '#/properties/requestIdHeader/type',
409
+ dataPath: (dataPath || '') + '.onConstructorPoisoning',
410
+ schemaPath: '#/properties/onConstructorPoisoning/type',
409
411
  params: {
410
412
  type: 'string'
411
413
  },
@@ -414,35 +416,86 @@ var validate = (function() {
414
416
  return false;
415
417
  } else {
416
418
  data1 = coerced1;
417
- data['requestIdHeader'] = coerced1;
419
+ data['onConstructorPoisoning'] = coerced1;
418
420
  }
419
421
  }
420
422
  var valid1 = errors === errs_1;
421
423
  if (valid1) {
422
- var data1 = data.requestIdLogLabel;
424
+ var data1 = data.pluginTimeout;
423
425
  var errs_1 = errors;
424
- if (typeof data1 !== "string") {
426
+ if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
425
427
  var dataType1 = typeof data1;
426
428
  var coerced1 = undefined;
427
- if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1;
428
- else if (data1 === null) coerced1 = '';
429
+ if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1;
429
430
  if (coerced1 === undefined) {
430
431
  validate.errors = [{
431
432
  keyword: 'type',
432
- dataPath: (dataPath || '') + '.requestIdLogLabel',
433
- schemaPath: '#/properties/requestIdLogLabel/type',
433
+ dataPath: (dataPath || '') + '.pluginTimeout',
434
+ schemaPath: '#/properties/pluginTimeout/type',
434
435
  params: {
435
- type: 'string'
436
+ type: 'integer'
436
437
  },
437
- message: 'should be string'
438
+ message: 'should be integer'
438
439
  }];
439
440
  return false;
440
441
  } else {
441
442
  data1 = coerced1;
442
- data['requestIdLogLabel'] = coerced1;
443
+ data['pluginTimeout'] = coerced1;
443
444
  }
444
445
  }
445
446
  var valid1 = errors === errs_1;
447
+ if (valid1) {
448
+ var data1 = data.requestIdHeader;
449
+ var errs_1 = errors;
450
+ if (typeof data1 !== "string") {
451
+ var dataType1 = typeof data1;
452
+ var coerced1 = undefined;
453
+ if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1;
454
+ else if (data1 === null) coerced1 = '';
455
+ if (coerced1 === undefined) {
456
+ validate.errors = [{
457
+ keyword: 'type',
458
+ dataPath: (dataPath || '') + '.requestIdHeader',
459
+ schemaPath: '#/properties/requestIdHeader/type',
460
+ params: {
461
+ type: 'string'
462
+ },
463
+ message: 'should be string'
464
+ }];
465
+ return false;
466
+ } else {
467
+ data1 = coerced1;
468
+ data['requestIdHeader'] = coerced1;
469
+ }
470
+ }
471
+ var valid1 = errors === errs_1;
472
+ if (valid1) {
473
+ var data1 = data.requestIdLogLabel;
474
+ var errs_1 = errors;
475
+ if (typeof data1 !== "string") {
476
+ var dataType1 = typeof data1;
477
+ var coerced1 = undefined;
478
+ if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1;
479
+ else if (data1 === null) coerced1 = '';
480
+ if (coerced1 === undefined) {
481
+ validate.errors = [{
482
+ keyword: 'type',
483
+ dataPath: (dataPath || '') + '.requestIdLogLabel',
484
+ schemaPath: '#/properties/requestIdLogLabel/type',
485
+ params: {
486
+ type: 'string'
487
+ },
488
+ message: 'should be string'
489
+ }];
490
+ return false;
491
+ } else {
492
+ data1 = coerced1;
493
+ data['requestIdLogLabel'] = coerced1;
494
+ }
495
+ }
496
+ var valid1 = errors === errs_1;
497
+ }
498
+ }
446
499
  }
447
500
  }
448
501
  }
@@ -507,11 +560,11 @@ validate.schema = {
507
560
  "setDefaultValue": true
508
561
  }
509
562
  },
510
- "disableRequestLogging": {
563
+ "ignoreTrailingSlash": {
511
564
  "type": "boolean",
512
565
  "default": false
513
566
  },
514
- "ignoreTrailingSlash": {
567
+ "disableRequestLogging": {
515
568
  "type": "boolean",
516
569
  "default": false
517
570
  },
@@ -523,6 +576,10 @@ validate.schema = {
523
576
  "type": "string",
524
577
  "default": "error"
525
578
  },
579
+ "onConstructorPoisoning": {
580
+ "type": "string",
581
+ "default": "ignore"
582
+ },
526
583
  "pluginTimeout": {
527
584
  "type": "integer",
528
585
  "default": 10000
@@ -545,14 +602,4 @@ function customRule0 (schemaParamValue, validatedParamValue, validationSchemaObj
545
602
  return true
546
603
  }
547
604
 
548
- module.exports.defaultInitOptions = {
549
- "bodyLimit":1048576,
550
- "caseSensitive":true,
551
- "disableRequestLogging": false,
552
- "ignoreTrailingSlash":false,
553
- "maxParamLength":100,
554
- "onProtoPoisoning":"error",
555
- "pluginTimeout":10000,
556
- "requestIdHeader":"request-id",
557
- "requestIdLogLabel":"reqId"
558
- }
605
+ module.exports.defaultInitOptions = {"bodyLimit":1048576,"caseSensitive":true,"disableRequestLogging":false,"ignoreTrailingSlash":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"ignore","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId"}
@@ -22,8 +22,8 @@ const {
22
22
  }
23
23
  } = require('./errors')
24
24
 
25
- function ContentTypeParser (bodyLimit, onProtoPoisoning) {
26
- this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning)
25
+ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
26
+ this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
27
27
  this.customParsers = {}
28
28
  this.customParsers['application/json'] = new Parser(true, false, bodyLimit, this[kDefaultJsonParse])
29
29
  this.customParsers['text/plain'] = new Parser(true, false, bodyLimit, defaultPlainTextParser)
@@ -189,7 +189,7 @@ function rawBody (request, reply, options, parser, done) {
189
189
  }
190
190
  }
191
191
 
192
- function getDefaultJsonParser (onProtoPoisoning) {
192
+ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
193
193
  return defaultJsonParser
194
194
 
195
195
  function defaultJsonParser (req, body, done) {
@@ -198,7 +198,7 @@ function getDefaultJsonParser (onProtoPoisoning) {
198
198
  }
199
199
 
200
200
  try {
201
- var json = secureJson.parse(body, { protoAction: onProtoPoisoning })
201
+ var json = secureJson.parse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })
202
202
  } catch (err) {
203
203
  err.statusCode = 400
204
204
  return done(err, undefined)
package/lib/context.js CHANGED
@@ -4,7 +4,7 @@ const { kFourOhFourContext, kReplySerializerDefault } = require('./symbols.js')
4
4
 
5
5
  // Objects that holds the context of every request
6
6
  // Every route holds an instance of this object.
7
- function Context (schema, handler, Reply, Request, contentTypeParser, config, errorHandler, bodyLimit, logLevel, attachValidation, replySerializer) {
7
+ function Context (schema, handler, Reply, Request, contentTypeParser, config, errorHandler, bodyLimit, logLevel, logSerializers, attachValidation, replySerializer) {
8
8
  this.schema = schema
9
9
  this.handler = handler
10
10
  this.Reply = Reply
@@ -20,6 +20,7 @@ function Context (schema, handler, Reply, Request, contentTypeParser, config, er
20
20
  this._middie = null
21
21
  this._parserOptions = { limit: bodyLimit || null }
22
22
  this.logLevel = logLevel
23
+ this.logSerializers = logSerializers
23
24
  this[kFourOhFourContext] = null
24
25
  this.attachValidation = attachValidation
25
26
  this[kReplySerializerDefault] = replySerializer
package/lib/errors.js CHANGED
@@ -7,42 +7,42 @@ const codes = {}
7
7
  * Basic
8
8
  */
9
9
 
10
- createError('FST_ERR_NOT_FOUND', `Not Found`, 404)
10
+ createError('FST_ERR_NOT_FOUND', 'Not Found', 404)
11
11
 
12
12
  /**
13
13
  * ContentTypeParser
14
14
  */
15
- createError('FST_ERR_CTP_ALREADY_PRESENT', `Content type parser '%s' already present.`)
15
+ createError('FST_ERR_CTP_ALREADY_PRESENT', "Content type parser '%s' already present.")
16
16
  createError('FST_ERR_CTP_INVALID_TYPE', 'The content type should be a string', 500, TypeError)
17
17
  createError('FST_ERR_CTP_EMPTY_TYPE', 'The content type cannot be an empty string', 500, TypeError)
18
18
  createError('FST_ERR_CTP_INVALID_HANDLER', 'The content type handler should be a function', 500, TypeError)
19
- createError('FST_ERR_CTP_INVALID_PARSE_TYPE', `The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.`, 500, TypeError)
19
+ createError('FST_ERR_CTP_INVALID_PARSE_TYPE', "The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.", 500, TypeError)
20
20
  createError('FST_ERR_CTP_BODY_TOO_LARGE', 'Request body is too large', 413, RangeError)
21
- createError('FST_ERR_CTP_INVALID_MEDIA_TYPE', `Unsupported Media Type: %s`, 415)
21
+ createError('FST_ERR_CTP_INVALID_MEDIA_TYPE', 'Unsupported Media Type: %s', 415)
22
22
  createError('FST_ERR_CTP_INVALID_CONTENT_LENGTH', 'Request body size did not match Content-Length', 400, RangeError)
23
- createError('FST_ERR_CTP_EMPTY_JSON_BODY', `Body cannot be empty when content-type is set to 'application/json'`, 400)
23
+ createError('FST_ERR_CTP_EMPTY_JSON_BODY', "Body cannot be empty when content-type is set to 'application/json'", 400)
24
24
 
25
25
  /**
26
26
  * decorate
27
27
  */
28
- createError('FST_ERR_DEC_ALREADY_PRESENT', `The decorator '%s' has already been added!`)
29
- createError('FST_ERR_DEC_MISSING_DEPENDENCY', `The decorator is missing dependency '%s'.`)
28
+ createError('FST_ERR_DEC_ALREADY_PRESENT', "The decorator '%s' has already been added!")
29
+ createError('FST_ERR_DEC_MISSING_DEPENDENCY', "The decorator is missing dependency '%s'.")
30
30
 
31
31
  /**
32
32
  * hooks
33
33
  */
34
- createError('FST_ERR_HOOK_INVALID_TYPE', `The hook name must be a string`, 500, TypeError)
35
- createError('FST_ERR_HOOK_INVALID_HANDLER', `The hook callback must be a function`, 500, TypeError)
34
+ createError('FST_ERR_HOOK_INVALID_TYPE', 'The hook name must be a string', 500, TypeError)
35
+ createError('FST_ERR_HOOK_INVALID_HANDLER', 'The hook callback must be a function', 500, TypeError)
36
36
 
37
37
  /**
38
38
  * logger
39
39
  */
40
- createError('FST_ERR_LOG_INVALID_DESTINATION', `Cannot specify both logger.stream and logger.file options`)
40
+ createError('FST_ERR_LOG_INVALID_DESTINATION', 'Cannot specify both logger.stream and logger.file options')
41
41
 
42
42
  /**
43
43
  * reply
44
44
  */
45
- createError('FST_ERR_REP_INVALID_PAYLOAD_TYPE', `Attempted to send payload of invalid type '%s'. Expected a string or Buffer.`, 500, TypeError)
45
+ createError('FST_ERR_REP_INVALID_PAYLOAD_TYPE', "Attempted to send payload of invalid type '%s'. Expected a string or Buffer.", 500, TypeError)
46
46
  createError('FST_ERR_REP_ALREADY_SENT', 'Reply was already sent.')
47
47
  createError('FST_ERR_REP_SENT_VALUE', 'The only possible value for reply.sent is true.')
48
48
  createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onError` hook')
@@ -50,25 +50,27 @@ createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onEr
50
50
  /**
51
51
  * schemas
52
52
  */
53
- createError('FST_ERR_SCH_MISSING_ID', `Missing schema $id property`)
54
- createError('FST_ERR_SCH_ALREADY_PRESENT', `Schema with id '%s' already declared!`)
55
- createError('FST_ERR_SCH_NOT_PRESENT', `Schema with id '%s' does not exist!`)
56
- createError('FST_ERR_SCH_DUPLICATE', `Schema with '%s' already present!`)
53
+ createError('FST_ERR_SCH_MISSING_ID', 'Missing schema $id property')
54
+ createError('FST_ERR_SCH_ALREADY_PRESENT', "Schema with id '%s' already declared!")
55
+ createError('FST_ERR_SCH_NOT_PRESENT', "Schema with id '%s' does not exist!")
56
+ createError('FST_ERR_SCH_DUPLICATE', "Schema with '%s' already present!")
57
+ createError('FST_ERR_SCH_BUILD', 'Failed building the schema for %s: %s, due error %s')
58
+ createError('FST_ERR_SCH_MISSING_COMPILER', 'You must provide a schemaCompiler to route %s %s to use the schemaResolver')
57
59
 
58
60
  /**
59
61
  * wrapThenable
60
62
  */
61
- createError('FST_ERR_PROMISE_NOT_FULLFILLED', `Promise may not be fulfilled with 'undefined' when statusCode is not 204`)
63
+ createError('FST_ERR_PROMISE_NOT_FULLFILLED', "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
62
64
 
63
65
  /**
64
66
  * http2
65
67
  */
66
- createError('FST_ERR_HTTP2_INVALID_VERSION', `HTTP2 is available only from node >= 8.8.1`)
68
+ createError('FST_ERR_HTTP2_INVALID_VERSION', 'HTTP2 is available only from node >= 8.8.1')
67
69
 
68
70
  /**
69
71
  * initialConfig
70
72
  */
71
- createError('FST_ERR_INIT_OPTS_INVALID', `Invalid initialization options: '%s'`)
73
+ createError('FST_ERR_INIT_OPTS_INVALID', "Invalid initialization options: '%s'")
72
74
 
73
75
  function createError (code, message, statusCode = 500, Base = Error) {
74
76
  if (!code) throw new Error('Fastify error code must not be empty')
@@ -95,6 +97,7 @@ function createError (code, message, statusCode = 500, Base = Error) {
95
97
  this.message = `${this.code}: ${this.message}`
96
98
  this.statusCode = statusCode || undefined
97
99
  }
100
+ FastifyError.prototype[Symbol.toStringTag] = 'Error'
98
101
 
99
102
  inherits(FastifyError, Base)
100
103
 
package/lib/fourOhFour.js CHANGED
@@ -34,14 +34,7 @@ function fourOhFour (options) {
34
34
  // 404 router, used for handling encapsulated 404 handlers
35
35
  const router = FindMyWay({ defaultRoute: fourOhFourFallBack })
36
36
 
37
- const fof = {
38
- router,
39
- setNotFoundHandler: setNotFoundHandler,
40
- setContext: setContext,
41
- arrange404: arrange404
42
- }
43
-
44
- return fof
37
+ return { router, setNotFoundHandler, setContext, arrange404 }
45
38
 
46
39
  function arrange404 (instance) {
47
40
  // Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
@@ -49,8 +42,15 @@ function fourOhFour (options) {
49
42
  instance[kCanSetNotFoundHandler] = true
50
43
  }
51
44
 
52
- function basic404 (req, reply) {
53
- reply.code(404).send(new Error('Not Found'))
45
+ function basic404 (request, reply) {
46
+ const { url, method } = request.raw
47
+ const message = `Route ${method}:${url} not found`
48
+ request.log.info(message)
49
+ reply.code(404).send({
50
+ message,
51
+ error: 'Not Found',
52
+ statusCode: 404
53
+ })
54
54
  }
55
55
 
56
56
  function setContext (instance, context) {
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const validation = require('./validation')
4
- const validateSchema = validation.validate
3
+ const { validate: validateSchema } = require('./validation')
5
4
  const { hookRunner, hookIterator } = require('./hooks')
6
5
  const wrapThenable = require('./wrapThenable')
7
6
 
package/lib/logger.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
7
7
  */
8
8
 
9
- const abstractLogging = require('abstract-logging')
9
+ const nullLogger = require('abstract-logging')
10
10
  const pino = require('pino')
11
11
  const { serializersSym } = pino.symbols
12
12
  const { isValidLogger } = require('./validation')
@@ -82,7 +82,7 @@ function createLogger (options) {
82
82
  })
83
83
  return { logger, hasLogger: true }
84
84
  } else if (!options.logger) {
85
- const logger = Object.create(abstractLogging)
85
+ const logger = nullLogger
86
86
  logger.child = () => logger
87
87
  return { logger, hasLogger: false }
88
88
  } else {
@@ -11,6 +11,35 @@ function getMeta (fn) {
11
11
  return fn[Symbol.for('plugin-meta')]
12
12
  }
13
13
 
14
+ function getPluginName (func) {
15
+ // let's see if this is a file, and in that case use that
16
+ // this is common for plugins
17
+ const cache = require.cache
18
+ const keys = Object.keys(cache)
19
+
20
+ for (var i = 0; i < keys.length; i++) {
21
+ if (cache[keys[i]].exports === func) {
22
+ return keys[i]
23
+ }
24
+ }
25
+
26
+ // if not maybe it's a named function, so use that
27
+ if (func.name) {
28
+ return func.name
29
+ }
30
+
31
+ return null
32
+ }
33
+
34
+ function getFuncPreview (func) {
35
+ // takes the first two lines of the function if nothing else works
36
+ return func.toString().split('\n').slice(0, 2).map(s => s.trim()).join(' -- ')
37
+ }
38
+
39
+ function getDisplayName (fn) {
40
+ return fn[Symbol.for('fastify.display-name')]
41
+ }
42
+
14
43
  function shouldSkipOverride (fn) {
15
44
  return !!fn[Symbol.for('skip-override')]
16
45
  }
@@ -71,7 +100,10 @@ function registerPlugin (fn) {
71
100
  }
72
101
 
73
102
  module.exports = {
103
+ getPluginName,
104
+ getFuncPreview,
74
105
  registeredPlugins,
106
+ getDisplayName,
75
107
  registerPlugin
76
108
  }
77
109
 
package/lib/reply.js CHANGED
@@ -86,6 +86,15 @@ Object.defineProperty(Reply.prototype, 'sent', {
86
86
  }
87
87
  })
88
88
 
89
+ Object.defineProperty(Reply.prototype, 'statusCode', {
90
+ get () {
91
+ return this.res.statusCode
92
+ },
93
+ set (value) {
94
+ this.code(value)
95
+ }
96
+ })
97
+
89
98
  Reply.prototype.send = function (payload) {
90
99
  if (this[kReplyIsRunningOnErrorHook] === true) {
91
100
  throw new FST_ERR_SEND_INSIDE_ONERR()
@@ -93,17 +102,17 @@ Reply.prototype.send = function (payload) {
93
102
 
94
103
  if (this[kReplySent]) {
95
104
  this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
96
- return
105
+ return this
97
106
  }
98
107
 
99
108
  if (payload instanceof Error || this[kReplyIsError] === true) {
100
109
  onErrorHook(this, payload, onSendHook)
101
- return
110
+ return this
102
111
  }
103
112
 
104
113
  if (payload === undefined) {
105
114
  onSendHook(this, payload)
106
- return
115
+ return this
107
116
  }
108
117
 
109
118
  var contentType = getHeader(this, 'content-type')
@@ -115,13 +124,13 @@ Reply.prototype.send = function (payload) {
115
124
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
116
125
  }
117
126
  onSendHook(this, payload)
118
- return
127
+ return this
119
128
  }
120
129
 
121
130
  if (hasContentType === false && typeof payload === 'string') {
122
131
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN
123
132
  onSendHook(this, payload)
124
- return
133
+ return this
125
134
  }
126
135
  }
127
136
 
@@ -133,10 +142,12 @@ Reply.prototype.send = function (payload) {
133
142
  }
134
143
 
135
144
  preserializeHook(this, payload)
136
- return
145
+ return this
137
146
  }
138
147
 
139
148
  onSendHook(this, payload)
149
+
150
+ return this
140
151
  }
141
152
 
142
153
  Reply.prototype.getHeader = function (key) {
@@ -237,6 +248,30 @@ Reply.prototype.getResponseTime = function () {
237
248
  return responseTime
238
249
  }
239
250
 
251
+ // Make reply a thenable, so it could be used with async/await.
252
+ // See
253
+ // - https://github.com/fastify/fastify/issues/1864 for the discussions
254
+ // - https://promisesaplus.com/ for the definition of thenable
255
+ // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then for the signature
256
+ Reply.prototype.then = function (fullfilled, rejected) {
257
+ if (this.sent) {
258
+ fullfilled()
259
+ return
260
+ }
261
+
262
+ eos(this.res, function (err) {
263
+ // We must not treat ERR_STREAM_PREMATURE_CLOSE as
264
+ // an error because it is created by eos, not by the stream.
265
+ if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
266
+ if (rejected) {
267
+ rejected(err)
268
+ }
269
+ } else {
270
+ fullfilled()
271
+ }
272
+ })
273
+ }
274
+
240
275
  function preserializeHook (reply, payload) {
241
276
  if (reply.context.preSerialization !== null) {
242
277
  onSendHookRunner(