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.
- package/README.md +15 -4
- package/build/build-validation.js +8 -0
- package/docs/Benchmarking.md +2 -2
- package/docs/ContentTypeParser.md +12 -10
- package/docs/Decorators.md +14 -14
- package/docs/Ecosystem.md +7 -1
- package/docs/Errors.md +13 -8
- package/docs/Fluent-Schema.md +9 -12
- package/docs/Getting-Started.md +29 -25
- package/docs/HTTP2.md +1 -1
- package/docs/Hooks.md +201 -186
- package/docs/LTS.md +6 -7
- package/docs/Logging.md +10 -10
- package/docs/Middleware.md +59 -0
- package/docs/Plugins-Guide.md +52 -52
- package/docs/Plugins.md +3 -0
- package/docs/Reply.md +47 -3
- package/docs/Routes.md +120 -8
- package/docs/Server.md +69 -3
- package/docs/Serverless.md +76 -4
- package/docs/TypeScript.md +33 -10
- package/docs/Validation-and-Serialization.md +137 -1
- package/examples/typescript-server.ts +1 -1
- package/fastify.d.ts +52 -13
- package/fastify.js +68 -7
- package/lib/configValidator.js +99 -52
- package/lib/contentTypeParser.js +4 -4
- package/lib/context.js +2 -1
- package/lib/errors.js +21 -18
- package/lib/fourOhFour.js +10 -10
- package/lib/handleRequest.js +1 -2
- package/lib/logger.js +2 -2
- package/lib/pluginUtils.js +32 -0
- package/lib/reply.js +41 -6
- package/lib/route.js +37 -9
- package/lib/schemas.js +23 -12
- package/lib/symbols.js +4 -1
- package/lib/validation.js +15 -9
- package/lib/wrapThenable.js +1 -1
- package/package.json +34 -26
- package/test/404s.test.js +41 -1
- package/test/async-await.js +66 -0
- package/test/custom-parser.test.js +1 -1
- package/test/custom-querystring-parser.test.js +1 -1
- package/test/decorator.test.js +48 -0
- package/test/emit-warning.test.js +3 -3
- package/test/fastify-instance.test.js +29 -0
- package/test/helper.js +7 -7
- package/test/hooks-async.js +4 -3
- package/test/hooks.test.js +27 -8
- package/test/input-validation.test.js +126 -0
- package/test/internals/errors.test.js +9 -1
- package/test/internals/initialConfig.test.js +4 -2
- package/test/internals/plugin.test.js +4 -4
- package/test/internals/reply.test.js +78 -6
- package/test/internals/schemas.test.js +30 -0
- package/test/internals/validation.test.js +18 -0
- package/test/listen.test.js +1 -1
- package/test/logger.test.js +314 -1
- package/test/plugin.test.js +171 -0
- package/test/promises.test.js +55 -0
- package/test/proto-poisoning.test.js +76 -0
- package/test/route-hooks.test.js +109 -91
- package/test/route-prefix.test.js +1 -1
- package/test/schemas.test.js +450 -0
- package/test/shared-schemas.test.js +2 -2
- package/test/stream.test.js +10 -6
- package/test/throw.test.js +48 -2
- package/test/types/index.ts +86 -1
- package/test/validation-error-handling.test.js +3 -3
- package/test/versioned-routes.test.js +1 -1
- package/docs/Middlewares.md +0 -59
package/lib/configValidator.js
CHANGED
|
@@ -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.
|
|
325
|
+
var data1 = data.disableRequestLogging;
|
|
325
326
|
var errs_1 = errors;
|
|
326
|
-
if (
|
|
327
|
+
if (typeof data1 !== "boolean") {
|
|
327
328
|
var dataType1 = typeof data1;
|
|
328
329
|
var coerced1 = undefined;
|
|
329
|
-
if (
|
|
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 || '') + '.
|
|
334
|
-
schemaPath: '#/properties/
|
|
335
|
+
dataPath: (dataPath || '') + '.disableRequestLogging',
|
|
336
|
+
schemaPath: '#/properties/disableRequestLogging/type',
|
|
335
337
|
params: {
|
|
336
|
-
type: '
|
|
338
|
+
type: 'boolean'
|
|
337
339
|
},
|
|
338
|
-
message: 'should be
|
|
340
|
+
message: 'should be boolean'
|
|
339
341
|
}];
|
|
340
342
|
return false;
|
|
341
343
|
} else {
|
|
342
344
|
data1 = coerced1;
|
|
343
|
-
data['
|
|
345
|
+
data['disableRequestLogging'] = coerced1;
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
var valid1 = errors === errs_1;
|
|
347
349
|
if (valid1) {
|
|
348
|
-
var data1 = data.
|
|
350
|
+
var data1 = data.maxParamLength;
|
|
349
351
|
var errs_1 = errors;
|
|
350
|
-
if (typeof data1 !== "
|
|
352
|
+
if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
|
|
351
353
|
var dataType1 = typeof data1;
|
|
352
354
|
var coerced1 = undefined;
|
|
353
|
-
if (dataType1 == '
|
|
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 || '') + '.
|
|
359
|
-
schemaPath: '#/properties/
|
|
359
|
+
dataPath: (dataPath || '') + '.maxParamLength',
|
|
360
|
+
schemaPath: '#/properties/maxParamLength/type',
|
|
360
361
|
params: {
|
|
361
|
-
type: '
|
|
362
|
+
type: 'integer'
|
|
362
363
|
},
|
|
363
|
-
message: 'should be
|
|
364
|
+
message: 'should be integer'
|
|
364
365
|
}];
|
|
365
366
|
return false;
|
|
366
367
|
} else {
|
|
367
368
|
data1 = coerced1;
|
|
368
|
-
data['
|
|
369
|
+
data['maxParamLength'] = coerced1;
|
|
369
370
|
}
|
|
370
371
|
}
|
|
371
372
|
var valid1 = errors === errs_1;
|
|
372
373
|
if (valid1) {
|
|
373
|
-
var data1 = data.
|
|
374
|
+
var data1 = data.onProtoPoisoning;
|
|
374
375
|
var errs_1 = errors;
|
|
375
|
-
if (
|
|
376
|
+
if (typeof data1 !== "string") {
|
|
376
377
|
var dataType1 = typeof data1;
|
|
377
378
|
var coerced1 = undefined;
|
|
378
|
-
if (dataType1 == '
|
|
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 || '') + '.
|
|
383
|
-
schemaPath: '#/properties/
|
|
384
|
+
dataPath: (dataPath || '') + '.onProtoPoisoning',
|
|
385
|
+
schemaPath: '#/properties/onProtoPoisoning/type',
|
|
384
386
|
params: {
|
|
385
|
-
type: '
|
|
387
|
+
type: 'string'
|
|
386
388
|
},
|
|
387
|
-
message: 'should be
|
|
389
|
+
message: 'should be string'
|
|
388
390
|
}];
|
|
389
391
|
return false;
|
|
390
392
|
} else {
|
|
391
393
|
data1 = coerced1;
|
|
392
|
-
data['
|
|
394
|
+
data['onProtoPoisoning'] = coerced1;
|
|
393
395
|
}
|
|
394
396
|
}
|
|
395
397
|
var valid1 = errors === errs_1;
|
|
396
398
|
if (valid1) {
|
|
397
|
-
var data1 = data.
|
|
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 || '') + '.
|
|
408
|
-
schemaPath: '#/properties/
|
|
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['
|
|
419
|
+
data['onConstructorPoisoning'] = coerced1;
|
|
418
420
|
}
|
|
419
421
|
}
|
|
420
422
|
var valid1 = errors === errs_1;
|
|
421
423
|
if (valid1) {
|
|
422
|
-
var data1 = data.
|
|
424
|
+
var data1 = data.pluginTimeout;
|
|
423
425
|
var errs_1 = errors;
|
|
424
|
-
if (typeof data1 !== "
|
|
426
|
+
if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) {
|
|
425
427
|
var dataType1 = typeof data1;
|
|
426
428
|
var coerced1 = undefined;
|
|
427
|
-
if (dataType1 == '
|
|
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 || '') + '.
|
|
433
|
-
schemaPath: '#/properties/
|
|
433
|
+
dataPath: (dataPath || '') + '.pluginTimeout',
|
|
434
|
+
schemaPath: '#/properties/pluginTimeout/type',
|
|
434
435
|
params: {
|
|
435
|
-
type: '
|
|
436
|
+
type: 'integer'
|
|
436
437
|
},
|
|
437
|
-
message: 'should be
|
|
438
|
+
message: 'should be integer'
|
|
438
439
|
}];
|
|
439
440
|
return false;
|
|
440
441
|
} else {
|
|
441
442
|
data1 = coerced1;
|
|
442
|
-
data['
|
|
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
|
-
"
|
|
563
|
+
"ignoreTrailingSlash": {
|
|
511
564
|
"type": "boolean",
|
|
512
565
|
"default": false
|
|
513
566
|
},
|
|
514
|
-
"
|
|
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"}
|
package/lib/contentTypeParser.js
CHANGED
|
@@ -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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
29
|
-
createError('FST_ERR_DEC_MISSING_DEPENDENCY',
|
|
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',
|
|
35
|
-
createError('FST_ERR_HOOK_INVALID_HANDLER',
|
|
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',
|
|
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',
|
|
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',
|
|
54
|
-
createError('FST_ERR_SCH_ALREADY_PRESENT',
|
|
55
|
-
createError('FST_ERR_SCH_NOT_PRESENT',
|
|
56
|
-
createError('FST_ERR_SCH_DUPLICATE',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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 (
|
|
53
|
-
|
|
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) {
|
package/lib/handleRequest.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
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
|
|
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 =
|
|
85
|
+
const logger = nullLogger
|
|
86
86
|
logger.child = () => logger
|
|
87
87
|
return { logger, hasLogger: false }
|
|
88
88
|
} else {
|
package/lib/pluginUtils.js
CHANGED
|
@@ -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(
|