fastify 6.0.0-alpha.5 → 6.0.0-alpha.6
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 +1 -0
- package/build/build-validation.js +2 -0
- package/docs/Guides/Ecosystem.md +4 -0
- package/docs/Guides/Migration-Guide-V5.md +2 -2
- package/docs/Guides/Serverless.md +33 -10
- package/docs/Reference/Errors.md +13 -0
- package/docs/Reference/Hooks.md +13 -0
- package/docs/Reference/Reply.md +786 -697
- package/docs/Reference/Request.md +303 -107
- package/docs/Reference/Server.md +39 -2
- package/docs/Reference/Validation-and-Serialization.md +15 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +35 -1
- package/lib/config-validator.js +102 -73
- package/lib/content-type-parser.js +14 -2
- package/lib/context.js +2 -1
- package/lib/decorate.js +4 -3
- package/lib/error-handler.js +21 -4
- package/lib/error-status.js +4 -2
- package/lib/errors.js +15 -9
- package/lib/reply.js +12 -11
- package/lib/request.js +3 -2
- package/lib/route.js +34 -5
- package/lib/schemas.js +44 -32
- package/package.json +8 -3
- package/test/close-disposable.test.js +154 -0
- package/test/constrained-routes.test.js +4 -2
- package/test/content-parser.test.js +39 -0
- package/test/decorator.test.js +29 -0
- package/test/diagnostics-channel/error-status.test.js +35 -2
- package/test/handler-timeout.test.js +224 -0
- package/test/internals/all.test.js +5 -0
- package/test/internals/errors.test.js +18 -9
- package/test/internals/initial-config.test.js +2 -0
- package/test/internals/server.test.js +6 -4
- package/test/reply-error.test.js +82 -0
- package/test/reply-trailers.test.js +159 -1
- package/test/router-compile.test.js +241 -0
- package/test/schema-serialization.test.js +120 -7
- package/test/types/fastify.tst.ts +4 -0
- package/test/types/instance.tst.ts +1 -1
- package/test/types/route.tst.ts +16 -0
- package/test/types/using.tst.ts +20 -0
- package/test/validation-error-handling.test.js +21 -1
- package/types/instance.d.ts +5 -1
- package/types/route.d.ts +1 -0
package/lib/config-validator.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
module.exports = validate10;
|
|
5
5
|
module.exports.default = validate10;
|
|
6
|
-
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"handlerTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean"},"ignoreDuplicateSlashes":{"type":"boolean"},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"routerOptions":{"type":"object","additionalProperties":false,"properties":{"allowUnsafeRegex":{"type":"boolean","default":false},"caseSensitive":{"type":"boolean","default":true},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"useSemicolonDelimiter":{"type":"boolean","default":false}}}}};
|
|
6
|
+
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"handlerTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean"},"ignoreDuplicateSlashes":{"type":"boolean"},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"routerOptions":{"type":"object","additionalProperties":false,"properties":{"allowUnsafeRegex":{"type":"boolean","default":false},"caseSensitive":{"type":"boolean","default":true},"compile":{"type":"boolean","default":false},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"useSemicolonDelimiter":{"type":"boolean","default":false}}}}};
|
|
7
7
|
const func2 = Object.prototype.hasOwnProperty;
|
|
8
8
|
const pattern0 = new RegExp("idle", "u");
|
|
9
9
|
|
|
@@ -876,6 +876,9 @@ data18.allowUnsafeRegex = false;
|
|
|
876
876
|
if(data18.caseSensitive === undefined){
|
|
877
877
|
data18.caseSensitive = true;
|
|
878
878
|
}
|
|
879
|
+
if(data18.compile === undefined){
|
|
880
|
+
data18.compile = false;
|
|
881
|
+
}
|
|
879
882
|
if(data18.ignoreTrailingSlash === undefined){
|
|
880
883
|
data18.ignoreTrailingSlash = false;
|
|
881
884
|
}
|
|
@@ -890,7 +893,7 @@ data18.useSemicolonDelimiter = false;
|
|
|
890
893
|
}
|
|
891
894
|
const _errs61 = errors;
|
|
892
895
|
for(const key2 in data18){
|
|
893
|
-
if(!(((((((key2 === "allowUnsafeRegex") || (key2 === "caseSensitive")) || (key2 === "constraints")) || (key2 === "ignoreTrailingSlash")) || (key2 === "ignoreDuplicateSlashes")) || (key2 === "maxParamLength")) || (key2 === "useSemicolonDelimiter"))){
|
|
896
|
+
if(!((((((((key2 === "allowUnsafeRegex") || (key2 === "caseSensitive")) || (key2 === "compile")) || (key2 === "constraints")) || (key2 === "ignoreTrailingSlash")) || (key2 === "ignoreDuplicateSlashes")) || (key2 === "maxParamLength")) || (key2 === "useSemicolonDelimiter"))){
|
|
894
897
|
delete data18[key2];
|
|
895
898
|
}
|
|
896
899
|
}
|
|
@@ -945,43 +948,68 @@ data18["caseSensitive"] = coerced22;
|
|
|
945
948
|
}
|
|
946
949
|
var valid7 = _errs64 === errors;
|
|
947
950
|
if(valid7){
|
|
948
|
-
|
|
949
|
-
let data21 = data18.constraints;
|
|
951
|
+
let data21 = data18.compile;
|
|
950
952
|
const _errs66 = errors;
|
|
951
|
-
if(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
953
|
+
if(typeof data21 !== "boolean"){
|
|
954
|
+
let coerced23 = undefined;
|
|
955
|
+
if(!(coerced23 !== undefined)){
|
|
956
|
+
if(data21 === "false" || data21 === 0 || data21 === null){
|
|
957
|
+
coerced23 = false;
|
|
958
|
+
}
|
|
959
|
+
else if(data21 === "true" || data21 === 1){
|
|
960
|
+
coerced23 = true;
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
validate10.errors = [{instancePath:instancePath+"/routerOptions/compile",schemaPath:"#/properties/routerOptions/properties/compile/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if(coerced23 !== undefined){
|
|
968
|
+
data21 = coerced23;
|
|
969
|
+
if(data18 !== undefined){
|
|
970
|
+
data18["compile"] = coerced23;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
var valid7 = _errs66 === errors;
|
|
975
|
+
if(valid7){
|
|
976
|
+
if(data18.constraints !== undefined){
|
|
977
|
+
let data22 = data18.constraints;
|
|
978
|
+
const _errs68 = errors;
|
|
979
|
+
if(errors === _errs68){
|
|
957
980
|
if(data22 && typeof data22 == "object" && !Array.isArray(data22)){
|
|
981
|
+
for(const key3 in data22){
|
|
982
|
+
let data23 = data22[key3];
|
|
983
|
+
const _errs71 = errors;
|
|
984
|
+
if(errors === _errs71){
|
|
985
|
+
if(data23 && typeof data23 == "object" && !Array.isArray(data23)){
|
|
958
986
|
let missing1;
|
|
959
|
-
if(((((
|
|
987
|
+
if(((((data23.name === undefined) && (missing1 = "name")) || ((data23.storage === undefined) && (missing1 = "storage"))) || ((data23.validate === undefined) && (missing1 = "validate"))) || ((data23.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){
|
|
960
988
|
validate10.errors = [{instancePath:instancePath+"/routerOptions/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/routerOptions/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
|
|
961
989
|
return false;
|
|
962
990
|
}
|
|
963
991
|
else {
|
|
964
|
-
if(
|
|
965
|
-
let
|
|
966
|
-
if(typeof
|
|
967
|
-
let
|
|
968
|
-
let
|
|
969
|
-
if(!(
|
|
970
|
-
if(
|
|
971
|
-
|
|
992
|
+
if(data23.name !== undefined){
|
|
993
|
+
let data24 = data23.name;
|
|
994
|
+
if(typeof data24 !== "string"){
|
|
995
|
+
let dataType24 = typeof data24;
|
|
996
|
+
let coerced24 = undefined;
|
|
997
|
+
if(!(coerced24 !== undefined)){
|
|
998
|
+
if(dataType24 == "number" || dataType24 == "boolean"){
|
|
999
|
+
coerced24 = "" + data24;
|
|
972
1000
|
}
|
|
973
|
-
else if(
|
|
974
|
-
|
|
1001
|
+
else if(data24 === null){
|
|
1002
|
+
coerced24 = "";
|
|
975
1003
|
}
|
|
976
1004
|
else {
|
|
977
1005
|
validate10.errors = [{instancePath:instancePath+"/routerOptions/constraints/" + key3.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/routerOptions/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}];
|
|
978
1006
|
return false;
|
|
979
1007
|
}
|
|
980
1008
|
}
|
|
981
|
-
if(
|
|
982
|
-
|
|
983
|
-
if(
|
|
984
|
-
|
|
1009
|
+
if(coerced24 !== undefined){
|
|
1010
|
+
data24 = coerced24;
|
|
1011
|
+
if(data23 !== undefined){
|
|
1012
|
+
data23["name"] = coerced24;
|
|
985
1013
|
}
|
|
986
1014
|
}
|
|
987
1015
|
}
|
|
@@ -993,7 +1021,7 @@ validate10.errors = [{instancePath:instancePath+"/routerOptions/constraints/" +
|
|
|
993
1021
|
return false;
|
|
994
1022
|
}
|
|
995
1023
|
}
|
|
996
|
-
var valid8 =
|
|
1024
|
+
var valid8 = _errs71 === errors;
|
|
997
1025
|
if(!valid8){
|
|
998
1026
|
break;
|
|
999
1027
|
}
|
|
@@ -1004,38 +1032,13 @@ validate10.errors = [{instancePath:instancePath+"/routerOptions/constraints",sch
|
|
|
1004
1032
|
return false;
|
|
1005
1033
|
}
|
|
1006
1034
|
}
|
|
1007
|
-
var valid7 =
|
|
1035
|
+
var valid7 = _errs68 === errors;
|
|
1008
1036
|
}
|
|
1009
1037
|
else {
|
|
1010
1038
|
var valid7 = true;
|
|
1011
1039
|
}
|
|
1012
1040
|
if(valid7){
|
|
1013
|
-
let
|
|
1014
|
-
const _errs74 = errors;
|
|
1015
|
-
if(typeof data24 !== "boolean"){
|
|
1016
|
-
let coerced24 = undefined;
|
|
1017
|
-
if(!(coerced24 !== undefined)){
|
|
1018
|
-
if(data24 === "false" || data24 === 0 || data24 === null){
|
|
1019
|
-
coerced24 = false;
|
|
1020
|
-
}
|
|
1021
|
-
else if(data24 === "true" || data24 === 1){
|
|
1022
|
-
coerced24 = true;
|
|
1023
|
-
}
|
|
1024
|
-
else {
|
|
1025
|
-
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
1026
|
-
return false;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
if(coerced24 !== undefined){
|
|
1030
|
-
data24 = coerced24;
|
|
1031
|
-
if(data18 !== undefined){
|
|
1032
|
-
data18["ignoreTrailingSlash"] = coerced24;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
var valid7 = _errs74 === errors;
|
|
1037
|
-
if(valid7){
|
|
1038
|
-
let data25 = data18.ignoreDuplicateSlashes;
|
|
1041
|
+
let data25 = data18.ignoreTrailingSlash;
|
|
1039
1042
|
const _errs76 = errors;
|
|
1040
1043
|
if(typeof data25 !== "boolean"){
|
|
1041
1044
|
let coerced25 = undefined;
|
|
@@ -1047,67 +1050,93 @@ else if(data25 === "true" || data25 === 1){
|
|
|
1047
1050
|
coerced25 = true;
|
|
1048
1051
|
}
|
|
1049
1052
|
else {
|
|
1050
|
-
validate10.errors = [{instancePath:instancePath+"/routerOptions/
|
|
1053
|
+
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
1051
1054
|
return false;
|
|
1052
1055
|
}
|
|
1053
1056
|
}
|
|
1054
1057
|
if(coerced25 !== undefined){
|
|
1055
1058
|
data25 = coerced25;
|
|
1056
1059
|
if(data18 !== undefined){
|
|
1057
|
-
data18["
|
|
1060
|
+
data18["ignoreTrailingSlash"] = coerced25;
|
|
1058
1061
|
}
|
|
1059
1062
|
}
|
|
1060
1063
|
}
|
|
1061
1064
|
var valid7 = _errs76 === errors;
|
|
1062
1065
|
if(valid7){
|
|
1063
|
-
let data26 = data18.
|
|
1066
|
+
let data26 = data18.ignoreDuplicateSlashes;
|
|
1064
1067
|
const _errs78 = errors;
|
|
1065
|
-
if(
|
|
1066
|
-
let dataType26 = typeof data26;
|
|
1068
|
+
if(typeof data26 !== "boolean"){
|
|
1067
1069
|
let coerced26 = undefined;
|
|
1068
1070
|
if(!(coerced26 !== undefined)){
|
|
1069
|
-
if(
|
|
1070
|
-
|
|
1071
|
-
|
|
1071
|
+
if(data26 === "false" || data26 === 0 || data26 === null){
|
|
1072
|
+
coerced26 = false;
|
|
1073
|
+
}
|
|
1074
|
+
else if(data26 === "true" || data26 === 1){
|
|
1075
|
+
coerced26 = true;
|
|
1072
1076
|
}
|
|
1073
1077
|
else {
|
|
1074
|
-
validate10.errors = [{instancePath:instancePath+"/routerOptions/
|
|
1078
|
+
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
1075
1079
|
return false;
|
|
1076
1080
|
}
|
|
1077
1081
|
}
|
|
1078
1082
|
if(coerced26 !== undefined){
|
|
1079
1083
|
data26 = coerced26;
|
|
1080
1084
|
if(data18 !== undefined){
|
|
1081
|
-
data18["
|
|
1085
|
+
data18["ignoreDuplicateSlashes"] = coerced26;
|
|
1082
1086
|
}
|
|
1083
1087
|
}
|
|
1084
1088
|
}
|
|
1085
1089
|
var valid7 = _errs78 === errors;
|
|
1086
1090
|
if(valid7){
|
|
1087
|
-
let data27 = data18.
|
|
1091
|
+
let data27 = data18.maxParamLength;
|
|
1088
1092
|
const _errs80 = errors;
|
|
1089
|
-
if(typeof data27
|
|
1093
|
+
if(!(((typeof data27 == "number") && (!(data27 % 1) && !isNaN(data27))) && (isFinite(data27)))){
|
|
1094
|
+
let dataType27 = typeof data27;
|
|
1090
1095
|
let coerced27 = undefined;
|
|
1091
1096
|
if(!(coerced27 !== undefined)){
|
|
1092
|
-
if(
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
else if(data27 === "true" || data27 === 1){
|
|
1096
|
-
coerced27 = true;
|
|
1097
|
+
if(dataType27 === "boolean" || data27 === null
|
|
1098
|
+
|| (dataType27 === "string" && data27 && data27 == +data27 && !(data27 % 1))){
|
|
1099
|
+
coerced27 = +data27;
|
|
1097
1100
|
}
|
|
1098
1101
|
else {
|
|
1099
|
-
validate10.errors = [{instancePath:instancePath+"/routerOptions/
|
|
1102
|
+
validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
|
|
1100
1103
|
return false;
|
|
1101
1104
|
}
|
|
1102
1105
|
}
|
|
1103
1106
|
if(coerced27 !== undefined){
|
|
1104
1107
|
data27 = coerced27;
|
|
1105
1108
|
if(data18 !== undefined){
|
|
1106
|
-
data18["
|
|
1109
|
+
data18["maxParamLength"] = coerced27;
|
|
1107
1110
|
}
|
|
1108
1111
|
}
|
|
1109
1112
|
}
|
|
1110
1113
|
var valid7 = _errs80 === errors;
|
|
1114
|
+
if(valid7){
|
|
1115
|
+
let data28 = data18.useSemicolonDelimiter;
|
|
1116
|
+
const _errs82 = errors;
|
|
1117
|
+
if(typeof data28 !== "boolean"){
|
|
1118
|
+
let coerced28 = undefined;
|
|
1119
|
+
if(!(coerced28 !== undefined)){
|
|
1120
|
+
if(data28 === "false" || data28 === 0 || data28 === null){
|
|
1121
|
+
coerced28 = false;
|
|
1122
|
+
}
|
|
1123
|
+
else if(data28 === "true" || data28 === 1){
|
|
1124
|
+
coerced28 = true;
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if(coerced28 !== undefined){
|
|
1132
|
+
data28 = coerced28;
|
|
1133
|
+
if(data18 !== undefined){
|
|
1134
|
+
data18["useSemicolonDelimiter"] = coerced28;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
var valid7 = _errs82 === errors;
|
|
1139
|
+
}
|
|
1111
1140
|
}
|
|
1112
1141
|
}
|
|
1113
1142
|
}
|
|
@@ -1155,5 +1184,5 @@ return errors === 0;
|
|
|
1155
1184
|
}
|
|
1156
1185
|
|
|
1157
1186
|
|
|
1158
|
-
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"handlerTimeout":0,"bodyLimit":1048576,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"http2SessionTimeout":72000,"exposeHeadRoutes":true,"allowErrorHandlerOverride":false,"routerOptions":{"allowUnsafeRegex":false,"caseSensitive":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"useSemicolonDelimiter":false}}
|
|
1187
|
+
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"handlerTimeout":0,"bodyLimit":1048576,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"http2SessionTimeout":72000,"exposeHeadRoutes":true,"allowErrorHandlerOverride":false,"routerOptions":{"allowUnsafeRegex":false,"caseSensitive":true,"compile":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"useSemicolonDelimiter":false}}
|
|
1159
1188
|
/* c8 ignore stop */
|
|
@@ -222,7 +222,13 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
|
|
|
222
222
|
return
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
let result
|
|
226
|
+
try {
|
|
227
|
+
result = parser.fn(request, request[kRequestPayloadStream], done)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
done(error, undefined)
|
|
230
|
+
return
|
|
231
|
+
}
|
|
226
232
|
if (result && typeof result.then === 'function') {
|
|
227
233
|
result.then(body => { done(null, body) }, done)
|
|
228
234
|
}
|
|
@@ -317,7 +323,13 @@ function rawBody (request, reply, options, parser, done) {
|
|
|
317
323
|
body = Buffer.concat(chunks, receivedLength)
|
|
318
324
|
}
|
|
319
325
|
|
|
320
|
-
|
|
326
|
+
let result
|
|
327
|
+
try {
|
|
328
|
+
result = parser.fn(request, body, done)
|
|
329
|
+
} catch (error) {
|
|
330
|
+
done(error, undefined)
|
|
331
|
+
return
|
|
332
|
+
}
|
|
321
333
|
if (result && typeof result.then === 'function') {
|
|
322
334
|
result.then(body => { done(null, body) }, done)
|
|
323
335
|
}
|
package/lib/context.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
kReplyCacheSerializeFns,
|
|
17
17
|
kHandlerTimeout
|
|
18
18
|
} = require('./symbols.js')
|
|
19
|
+
const { FST_ERR_VALIDATION } = require('./errors.js')
|
|
19
20
|
|
|
20
21
|
// Object that holds the context of every request
|
|
21
22
|
// Every route holds an instance of this object.
|
|
@@ -89,7 +90,7 @@ function defaultSchemaErrorFormatter (errors, dataVar) {
|
|
|
89
90
|
const e = errors[i]
|
|
90
91
|
text += dataVar + (e.instancePath || '') + ' ' + e.message + separator
|
|
91
92
|
}
|
|
92
|
-
return new
|
|
93
|
+
return new FST_ERR_VALIDATION(text.slice(0, -separator.length))
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
module.exports = Context
|
package/lib/decorate.js
CHANGED
|
@@ -38,11 +38,12 @@ function getInstanceDecorator (name) {
|
|
|
38
38
|
throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const decorator = this[name]
|
|
42
|
+
if (typeof decorator === 'function') {
|
|
43
|
+
return decorator.bind(this)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
return
|
|
46
|
+
return decorator
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
function decorateConstructor (konstructor, name, fn, dependencies) {
|
package/lib/error-handler.js
CHANGED
|
@@ -33,10 +33,27 @@ function handleError (reply, error, cb) {
|
|
|
33
33
|
const context = reply[kRouteContext]
|
|
34
34
|
if (reply[kReplyNextErrorHandler] === false) {
|
|
35
35
|
fallbackErrorHandler(error, reply, function (reply, payload) {
|
|
36
|
+
if (reply.raw.headersSent === true || reply.raw.destroyed === true) {
|
|
37
|
+
// The response has already started transmitting (e.g. an active
|
|
38
|
+
// stream that timed out after its headers were sent). We cannot
|
|
39
|
+
// write a new error response, so abort the in-flight response
|
|
40
|
+
// instead of raising an uncaught ERR_HTTP_HEADERS_SENT.
|
|
41
|
+
reply.raw.destroy(error)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
36
44
|
try {
|
|
37
45
|
reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
|
|
38
46
|
} catch (error) {
|
|
39
47
|
reply.server[kLogController].writeHeadError(error, reply.request, reply)
|
|
48
|
+
// If the response has started transmitting, retrying writeHead
|
|
49
|
+
// would raise an uncaught ERR_HTTP_HEADERS_SENT and terminate the
|
|
50
|
+
// process, so abort the in-flight response instead.
|
|
51
|
+
if (error.code === 'ERR_HTTP_HEADERS_SENT' || reply.raw.headersSent === true || reply.raw.destroyed === true) {
|
|
52
|
+
reply.raw.destroy(error)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
// For other writeHead failures (e.g. an invalid header value in
|
|
56
|
+
// the error payload) retry without the offending headers.
|
|
40
57
|
reply.raw.writeHead(reply.raw.statusCode)
|
|
41
58
|
}
|
|
42
59
|
reply.raw.end(payload)
|
|
@@ -134,16 +151,16 @@ function buildErrorHandler (parent = rootErrorHandler, func) {
|
|
|
134
151
|
function setErrorHeaders (error, reply) {
|
|
135
152
|
const res = reply.raw
|
|
136
153
|
let statusCode = res.statusCode
|
|
137
|
-
statusCode = (statusCode >= 400) ? statusCode : 500
|
|
154
|
+
statusCode = (statusCode >= 400 && statusCode <= 599) ? statusCode : 500
|
|
138
155
|
// treat undefined and null as same
|
|
139
156
|
if (error != null) {
|
|
140
157
|
if (error.headers !== undefined) {
|
|
141
158
|
reply.headers(error.headers)
|
|
142
159
|
}
|
|
143
|
-
if (error.
|
|
144
|
-
statusCode = error.status
|
|
145
|
-
} else if (error.statusCode >= 400) {
|
|
160
|
+
if (error.statusCode >= 400 && error.statusCode <= 599) {
|
|
146
161
|
statusCode = error.statusCode
|
|
162
|
+
} else if (error.status >= 400 && error.status <= 599) {
|
|
163
|
+
statusCode = error.status
|
|
147
164
|
}
|
|
148
165
|
}
|
|
149
166
|
res.statusCode = statusCode
|
package/lib/error-status.js
CHANGED
|
@@ -6,8 +6,10 @@ const {
|
|
|
6
6
|
|
|
7
7
|
function setErrorStatusCode (reply, err) {
|
|
8
8
|
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
let statusCode = 500
|
|
10
|
+
if (err?.statusCode >= 400 && err.statusCode <= 599) statusCode = err.statusCode
|
|
11
|
+
else if (err?.status >= 400 && err.status <= 599) statusCode = err.status
|
|
12
|
+
reply.code(statusCode)
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
package/lib/errors.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const createError = require('@fastify/error')
|
|
4
4
|
|
|
5
|
+
// Errors created when Fastify rejects an invalid request. They have no stack
|
|
6
|
+
// trace, see "Errors In Input Data" in docs/Reference/Errors.md.
|
|
7
|
+
function createRequestError (code, message, statusCode, Base = Error) {
|
|
8
|
+
return createError(code, message, statusCode, Base, false)
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
const codes = {
|
|
6
12
|
/**
|
|
7
13
|
* Basic
|
|
@@ -47,7 +53,7 @@ const codes = {
|
|
|
47
53
|
500,
|
|
48
54
|
TypeError
|
|
49
55
|
),
|
|
50
|
-
FST_ERR_VALIDATION:
|
|
56
|
+
FST_ERR_VALIDATION: createRequestError(
|
|
51
57
|
'FST_ERR_VALIDATION',
|
|
52
58
|
'%s',
|
|
53
59
|
400
|
|
@@ -102,29 +108,29 @@ const codes = {
|
|
|
102
108
|
500,
|
|
103
109
|
TypeError
|
|
104
110
|
),
|
|
105
|
-
FST_ERR_CTP_BODY_TOO_LARGE:
|
|
111
|
+
FST_ERR_CTP_BODY_TOO_LARGE: createRequestError(
|
|
106
112
|
'FST_ERR_CTP_BODY_TOO_LARGE',
|
|
107
113
|
'Request body is too large',
|
|
108
114
|
413,
|
|
109
115
|
RangeError
|
|
110
116
|
),
|
|
111
|
-
FST_ERR_CTP_INVALID_MEDIA_TYPE:
|
|
117
|
+
FST_ERR_CTP_INVALID_MEDIA_TYPE: createRequestError(
|
|
112
118
|
'FST_ERR_CTP_INVALID_MEDIA_TYPE',
|
|
113
119
|
'Unsupported Media Type',
|
|
114
120
|
415
|
|
115
121
|
),
|
|
116
|
-
FST_ERR_CTP_INVALID_CONTENT_LENGTH:
|
|
122
|
+
FST_ERR_CTP_INVALID_CONTENT_LENGTH: createRequestError(
|
|
117
123
|
'FST_ERR_CTP_INVALID_CONTENT_LENGTH',
|
|
118
124
|
'Request body size did not match Content-Length',
|
|
119
125
|
400,
|
|
120
126
|
RangeError
|
|
121
127
|
),
|
|
122
|
-
FST_ERR_CTP_EMPTY_JSON_BODY:
|
|
128
|
+
FST_ERR_CTP_EMPTY_JSON_BODY: createRequestError(
|
|
123
129
|
'FST_ERR_CTP_EMPTY_JSON_BODY',
|
|
124
130
|
"Body cannot be empty when content-type is set to 'application/json'",
|
|
125
131
|
400
|
|
126
132
|
),
|
|
127
|
-
FST_ERR_CTP_INVALID_JSON_BODY:
|
|
133
|
+
FST_ERR_CTP_INVALID_JSON_BODY: createRequestError(
|
|
128
134
|
'FST_ERR_CTP_INVALID_JSON_BODY',
|
|
129
135
|
"Body is not valid JSON but content-type is set to 'application/json'",
|
|
130
136
|
400
|
|
@@ -365,13 +371,13 @@ const codes = {
|
|
|
365
371
|
'FST_ERR_DUPLICATED_ROUTE',
|
|
366
372
|
"Method '%s' already declared for route '%s'"
|
|
367
373
|
),
|
|
368
|
-
FST_ERR_BAD_URL:
|
|
374
|
+
FST_ERR_BAD_URL: createRequestError(
|
|
369
375
|
'FST_ERR_BAD_URL',
|
|
370
376
|
"'%s' is not a valid url component",
|
|
371
377
|
400,
|
|
372
378
|
URIError
|
|
373
379
|
),
|
|
374
|
-
FST_ERR_MAX_PARAM_LENGTH:
|
|
380
|
+
FST_ERR_MAX_PARAM_LENGTH: createRequestError(
|
|
375
381
|
'FST_ERR_MAX_PARAM_LENGTH',
|
|
376
382
|
"'%s' is exceeding the max param length",
|
|
377
383
|
414,
|
|
@@ -461,7 +467,7 @@ const codes = {
|
|
|
461
467
|
500,
|
|
462
468
|
TypeError
|
|
463
469
|
),
|
|
464
|
-
FST_ERR_ROUTE_MISSING_CONTENT_TYPE:
|
|
470
|
+
FST_ERR_ROUTE_MISSING_CONTENT_TYPE: createRequestError(
|
|
465
471
|
'FST_ERR_ROUTE_MISSING_CONTENT_TYPE',
|
|
466
472
|
"Method '%s' must provide a 'Content-Type' header.",
|
|
467
473
|
400
|
package/lib/reply.js
CHANGED
|
@@ -843,9 +843,6 @@ function sendStream (payload, res, reply) {
|
|
|
843
843
|
let sourceOpen = true
|
|
844
844
|
let errorLogged = false
|
|
845
845
|
|
|
846
|
-
// set trailer when stream ended
|
|
847
|
-
sendStreamTrailer(payload, res, reply)
|
|
848
|
-
|
|
849
846
|
eos(payload, { readable: true, writable: false }, function (err) {
|
|
850
847
|
sourceOpen = false
|
|
851
848
|
if (err != null) {
|
|
@@ -858,8 +855,12 @@ function sendStream (payload, res, reply) {
|
|
|
858
855
|
} else {
|
|
859
856
|
onErrorHook(reply, err)
|
|
860
857
|
}
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
// eos fires even for a stream that already ended
|
|
861
|
+
if (reply[kReplyTrailers] !== null) {
|
|
862
|
+
sendTrailer(null, res, reply)
|
|
861
863
|
}
|
|
862
|
-
// there is nothing to do if there is not an error
|
|
863
864
|
})
|
|
864
865
|
|
|
865
866
|
eos(res, function (err) {
|
|
@@ -891,7 +892,12 @@ function sendStream (payload, res, reply) {
|
|
|
891
892
|
} else {
|
|
892
893
|
reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode')
|
|
893
894
|
}
|
|
894
|
-
|
|
895
|
+
// trailers set: the eos listener above ends the response, not pipe
|
|
896
|
+
if (reply[kReplyTrailers] !== null) {
|
|
897
|
+
payload.pipe(res, { end: false })
|
|
898
|
+
} else {
|
|
899
|
+
payload.pipe(res)
|
|
900
|
+
}
|
|
895
901
|
}
|
|
896
902
|
|
|
897
903
|
function sendTrailer (payload, res, reply) {
|
|
@@ -951,11 +957,6 @@ function sendTrailer (payload, res, reply) {
|
|
|
951
957
|
if (skipped) res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
|
952
958
|
}
|
|
953
959
|
|
|
954
|
-
function sendStreamTrailer (payload, res, reply) {
|
|
955
|
-
if (reply[kReplyTrailers] === null) return
|
|
956
|
-
payload.on('end', () => sendTrailer(null, res, reply))
|
|
957
|
-
}
|
|
958
|
-
|
|
959
960
|
function onErrorHook (reply, error, cb) {
|
|
960
961
|
if (reply[kRouteContext].onError !== null && !reply[kReplyNextErrorHandler]) {
|
|
961
962
|
reply[kReplyIsRunningOnErrorHook] = true
|
|
@@ -1083,7 +1084,7 @@ function notFound (reply) {
|
|
|
1083
1084
|
* @returns {string} the serialized payload
|
|
1084
1085
|
*/
|
|
1085
1086
|
function serialize (context, data, statusCode, contentType) {
|
|
1086
|
-
const fnSerialize = getSchemaSerializer(context, statusCode, contentType)
|
|
1087
|
+
const fnSerialize = getSchemaSerializer(context, statusCode, contentType, { failClosed: true })
|
|
1087
1088
|
if (fnSerialize) {
|
|
1088
1089
|
return fnSerialize(data)
|
|
1089
1090
|
}
|
package/lib/request.js
CHANGED
|
@@ -20,6 +20,8 @@ const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = re
|
|
|
20
20
|
const decorators = require('./decorate')
|
|
21
21
|
const ContentType = require('./content-type')
|
|
22
22
|
|
|
23
|
+
const PORT_REGEX = /(?<port>:\d+)$/
|
|
24
|
+
|
|
23
25
|
const HTTP_PART_SYMBOL_MAP = {
|
|
24
26
|
body: kSchemaBody,
|
|
25
27
|
headers: kSchemaHeaders,
|
|
@@ -263,8 +265,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
263
265
|
},
|
|
264
266
|
port: {
|
|
265
267
|
get () {
|
|
266
|
-
const
|
|
267
|
-
const matches = portReg.exec(this.host)
|
|
268
|
+
const matches = PORT_REGEX.exec(this.host)
|
|
268
269
|
if (matches === null || matches[1] === undefined) {
|
|
269
270
|
return null
|
|
270
271
|
}
|
package/lib/route.js
CHANGED
|
@@ -110,7 +110,10 @@ function buildRouting (options) {
|
|
|
110
110
|
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
|
|
111
111
|
keepAliveConnections = fastifyArgs.keepAliveConnections
|
|
112
112
|
},
|
|
113
|
-
|
|
113
|
+
// router func to find the right handler to call, not bound because
|
|
114
|
+
// router.compile() replaces router.lookup
|
|
115
|
+
routing: (req, res, done) => router.lookup(req, res, done),
|
|
116
|
+
compileRoutes: () => router.compile(),
|
|
114
117
|
route, // configure a route in the fastify instance
|
|
115
118
|
hasRoute,
|
|
116
119
|
prepareRoute,
|
|
@@ -535,12 +538,35 @@ function buildRouting (options) {
|
|
|
535
538
|
request[kRequestSignal] = ac
|
|
536
539
|
|
|
537
540
|
request[kTimeoutTimer] = setTimeout(() => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
+
// If the reply has already ended (or was hijacked) the response
|
|
542
|
+
// lifecycle is managed elsewhere and there is nothing left to do.
|
|
543
|
+
if (reply.sent === true) {
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const err = new FST_ERR_HANDLER_TIMEOUT(handlerTimeout, context.config?.url)
|
|
548
|
+
ac.abort(err)
|
|
549
|
+
|
|
550
|
+
// Aborting runs any request.signal listener synchronously; if a
|
|
551
|
+
// listener sent or hijacked the response, leave it intact rather than
|
|
552
|
+
// destroying it, which would reset the connection.
|
|
553
|
+
if (reply.sent === true) {
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (reply.raw.headersSent === true) {
|
|
558
|
+
// The response has already started transmitting, e.g. an active
|
|
559
|
+
// stream that has written its headers/body before the timeout.
|
|
560
|
+
// Sending an error response now would raise an uncaught
|
|
561
|
+
// ERR_HTTP_HEADERS_SENT and terminate the process, so abort the
|
|
562
|
+
// in-flight response instead.
|
|
541
563
|
reply[kReplyIsError] = true
|
|
542
|
-
reply.
|
|
564
|
+
reply.raw.destroy(err)
|
|
565
|
+
return
|
|
543
566
|
}
|
|
567
|
+
|
|
568
|
+
reply[kReplyIsError] = true
|
|
569
|
+
reply.send(err)
|
|
544
570
|
}, handlerTimeout)
|
|
545
571
|
|
|
546
572
|
const onAbort = () => {
|
|
@@ -678,6 +704,9 @@ function buildRouterOptions (options, defaultOptions) {
|
|
|
678
704
|
}
|
|
679
705
|
}
|
|
680
706
|
|
|
707
|
+
// compile is only accepted in routerOptions, not as a top-level option
|
|
708
|
+
routerOptions.compile ??= defaultInitOptions.routerOptions.compile
|
|
709
|
+
|
|
681
710
|
return routerOptions
|
|
682
711
|
}
|
|
683
712
|
|