Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

View File

@@ -1,9 +1,9 @@
// This file is autogenerated by build/build-validation.js, do not edit
/* istanbul ignore file */
/* c8 ignore start */
"use strict";
module.exports = validate10;
module.exports.default = validate10;
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},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"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","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
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},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"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","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"default":false},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"type":"boolean"},{"type":"string"}],"default":false},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":false},"routerOptions":{"type":"object","additionalProperties":true,"properties":{"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"maxParamLength":{"type":"integer","default":100},"allowUnsafeRegex":{"type":"boolean","default":false},"useSemicolonDelimiter":{"type":"boolean","default":false}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
const func2 = Object.prototype.hasOwnProperty;
const pattern0 = new RegExp("idle", "u");
@@ -42,9 +42,6 @@ data.ignoreDuplicateSlashes = false;
if(data.disableRequestLogging === undefined){
data.disableRequestLogging = false;
}
if(data.jsonShorthand === undefined){
data.jsonShorthand = true;
}
if(data.maxParamLength === undefined){
data.maxParamLength = 100;
}
@@ -58,7 +55,7 @@ if(data.pluginTimeout === undefined){
data.pluginTimeout = 10000;
}
if(data.requestIdHeader === undefined){
data.requestIdHeader = "request-id";
data.requestIdHeader = false;
}
if(data.requestIdLogLabel === undefined){
data.requestIdLogLabel = "reqId";
@@ -70,7 +67,7 @@ if(data.exposeHeadRoutes === undefined){
data.exposeHeadRoutes = true;
}
if(data.useSemicolonDelimiter === undefined){
data.useSemicolonDelimiter = true;
data.useSemicolonDelimiter = false;
}
const _errs1 = errors;
for(const key0 in data){
@@ -688,163 +685,122 @@ data["ignoreDuplicateSlashes"] = coerced14;
}
var valid0 = _errs43 === errors;
if(valid0){
let data13 = data.disableRequestLogging;
let data13 = data.maxParamLength;
const _errs45 = errors;
if(typeof data13 !== "boolean"){
if(!(((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13))) && (isFinite(data13)))){
let dataType15 = typeof data13;
let coerced15 = undefined;
if(!(coerced15 !== undefined)){
if(data13 === "false" || data13 === 0 || data13 === null){
coerced15 = false;
}
else if(data13 === "true" || data13 === 1){
coerced15 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/disableRequestLogging",schemaPath:"#/properties/disableRequestLogging/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced15 !== undefined){
data13 = coerced15;
if(data !== undefined){
data["disableRequestLogging"] = coerced15;
}
}
}
var valid0 = _errs45 === errors;
if(valid0){
let data14 = data.jsonShorthand;
const _errs47 = errors;
if(typeof data14 !== "boolean"){
let coerced16 = undefined;
if(!(coerced16 !== undefined)){
if(data14 === "false" || data14 === 0 || data14 === null){
coerced16 = false;
}
else if(data14 === "true" || data14 === 1){
coerced16 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/jsonShorthand",schemaPath:"#/properties/jsonShorthand/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced16 !== undefined){
data14 = coerced16;
if(data !== undefined){
data["jsonShorthand"] = coerced16;
}
}
}
var valid0 = _errs47 === errors;
if(valid0){
let data15 = data.maxParamLength;
const _errs49 = errors;
if(!(((typeof data15 == "number") && (!(data15 % 1) && !isNaN(data15))) && (isFinite(data15)))){
let dataType17 = typeof data15;
let coerced17 = undefined;
if(!(coerced17 !== undefined)){
if(dataType17 === "boolean" || data15 === null
|| (dataType17 === "string" && data15 && data15 == +data15 && !(data15 % 1))){
coerced17 = +data15;
if(dataType15 === "boolean" || data13 === null
|| (dataType15 === "string" && data13 && data13 == +data13 && !(data13 % 1))){
coerced15 = +data13;
}
else {
validate10.errors = [{instancePath:instancePath+"/maxParamLength",schemaPath:"#/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced17 !== undefined){
data15 = coerced17;
if(coerced15 !== undefined){
data13 = coerced15;
if(data !== undefined){
data["maxParamLength"] = coerced17;
data["maxParamLength"] = coerced15;
}
}
}
var valid0 = _errs49 === errors;
var valid0 = _errs45 === errors;
if(valid0){
let data16 = data.onProtoPoisoning;
const _errs51 = errors;
if(typeof data16 !== "string"){
let dataType18 = typeof data16;
let coerced18 = undefined;
if(!(coerced18 !== undefined)){
if(dataType18 == "number" || dataType18 == "boolean"){
coerced18 = "" + data16;
let data14 = data.onProtoPoisoning;
const _errs47 = errors;
if(typeof data14 !== "string"){
let dataType16 = typeof data14;
let coerced16 = undefined;
if(!(coerced16 !== undefined)){
if(dataType16 == "number" || dataType16 == "boolean"){
coerced16 = "" + data14;
}
else if(data16 === null){
coerced18 = "";
else if(data14 === null){
coerced16 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/onProtoPoisoning",schemaPath:"#/properties/onProtoPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced18 !== undefined){
data16 = coerced18;
if(coerced16 !== undefined){
data14 = coerced16;
if(data !== undefined){
data["onProtoPoisoning"] = coerced18;
data["onProtoPoisoning"] = coerced16;
}
}
}
var valid0 = _errs51 === errors;
var valid0 = _errs47 === errors;
if(valid0){
let data17 = data.onConstructorPoisoning;
const _errs53 = errors;
if(typeof data17 !== "string"){
let dataType19 = typeof data17;
let coerced19 = undefined;
if(!(coerced19 !== undefined)){
if(dataType19 == "number" || dataType19 == "boolean"){
coerced19 = "" + data17;
let data15 = data.onConstructorPoisoning;
const _errs49 = errors;
if(typeof data15 !== "string"){
let dataType17 = typeof data15;
let coerced17 = undefined;
if(!(coerced17 !== undefined)){
if(dataType17 == "number" || dataType17 == "boolean"){
coerced17 = "" + data15;
}
else if(data17 === null){
coerced19 = "";
else if(data15 === null){
coerced17 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/onConstructorPoisoning",schemaPath:"#/properties/onConstructorPoisoning/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced19 !== undefined){
data17 = coerced19;
if(coerced17 !== undefined){
data15 = coerced17;
if(data !== undefined){
data["onConstructorPoisoning"] = coerced19;
data["onConstructorPoisoning"] = coerced17;
}
}
}
var valid0 = _errs53 === errors;
var valid0 = _errs49 === errors;
if(valid0){
let data18 = data.pluginTimeout;
const _errs55 = errors;
if(!(((typeof data18 == "number") && (!(data18 % 1) && !isNaN(data18))) && (isFinite(data18)))){
let dataType20 = typeof data18;
let coerced20 = undefined;
if(!(coerced20 !== undefined)){
if(dataType20 === "boolean" || data18 === null
|| (dataType20 === "string" && data18 && data18 == +data18 && !(data18 % 1))){
coerced20 = +data18;
let data16 = data.pluginTimeout;
const _errs51 = errors;
if(!(((typeof data16 == "number") && (!(data16 % 1) && !isNaN(data16))) && (isFinite(data16)))){
let dataType18 = typeof data16;
let coerced18 = undefined;
if(!(coerced18 !== undefined)){
if(dataType18 === "boolean" || data16 === null
|| (dataType18 === "string" && data16 && data16 == +data16 && !(data16 % 1))){
coerced18 = +data16;
}
else {
validate10.errors = [{instancePath:instancePath+"/pluginTimeout",schemaPath:"#/properties/pluginTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced20 !== undefined){
data18 = coerced20;
if(coerced18 !== undefined){
data16 = coerced18;
if(data !== undefined){
data["pluginTimeout"] = coerced20;
data["pluginTimeout"] = coerced18;
}
}
}
var valid0 = _errs55 === errors;
var valid0 = _errs51 === errors;
if(valid0){
let data19 = data.requestIdHeader;
const _errs57 = errors;
const _errs58 = errors;
let data17 = data.requestIdHeader;
const _errs53 = errors;
const _errs54 = errors;
let valid6 = false;
const _errs59 = errors;
if(!(data19 === false)){
const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema11.properties.requestIdHeader.anyOf[0].enum},message:"must be equal to one of the allowed values"};
const _errs55 = errors;
if(typeof data17 !== "boolean"){
let coerced19 = undefined;
if(!(coerced19 !== undefined)){
if(data17 === "false" || data17 === 0 || data17 === null){
coerced19 = false;
}
else if(data17 === "true" || data17 === 1){
coerced19 = true;
}
else {
const err12 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};
if(vErrors === null){
vErrors = [err12];
}
@@ -853,19 +809,27 @@ vErrors.push(err12);
}
errors++;
}
var _valid3 = _errs59 === errors;
}
if(coerced19 !== undefined){
data17 = coerced19;
if(data !== undefined){
data["requestIdHeader"] = coerced19;
}
}
}
var _valid3 = _errs55 === errors;
valid6 = valid6 || _valid3;
if(!valid6){
const _errs60 = errors;
if(typeof data19 !== "string"){
let dataType21 = typeof data19;
let coerced21 = undefined;
if(!(coerced21 !== undefined)){
if(dataType21 == "number" || dataType21 == "boolean"){
coerced21 = "" + data19;
const _errs57 = errors;
if(typeof data17 !== "string"){
let dataType20 = typeof data17;
let coerced20 = undefined;
if(!(coerced20 !== undefined)){
if(dataType20 == "number" || dataType20 == "boolean"){
coerced20 = "" + data17;
}
else if(data19 === null){
coerced21 = "";
else if(data17 === null){
coerced20 = "";
}
else {
const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};
@@ -878,14 +842,14 @@ vErrors.push(err13);
errors++;
}
}
if(coerced21 !== undefined){
data19 = coerced21;
if(coerced20 !== undefined){
data17 = coerced20;
if(data !== undefined){
data["requestIdHeader"] = coerced21;
data["requestIdHeader"] = coerced20;
}
}
}
var _valid3 = _errs60 === errors;
var _valid3 = _errs57 === errors;
valid6 = valid6 || _valid3;
}
if(!valid6){
@@ -901,95 +865,140 @@ validate10.errors = vErrors;
return false;
}
else {
errors = _errs58;
errors = _errs54;
if(vErrors !== null){
if(_errs58){
vErrors.length = _errs58;
if(_errs54){
vErrors.length = _errs54;
}
else {
vErrors = null;
}
}
}
var valid0 = _errs57 === errors;
var valid0 = _errs53 === errors;
if(valid0){
let data20 = data.requestIdLogLabel;
const _errs62 = errors;
if(typeof data20 !== "string"){
let dataType22 = typeof data20;
let coerced22 = undefined;
if(!(coerced22 !== undefined)){
if(dataType22 == "number" || dataType22 == "boolean"){
coerced22 = "" + data20;
let data18 = data.requestIdLogLabel;
const _errs59 = errors;
if(typeof data18 !== "string"){
let dataType21 = typeof data18;
let coerced21 = undefined;
if(!(coerced21 !== undefined)){
if(dataType21 == "number" || dataType21 == "boolean"){
coerced21 = "" + data18;
}
else if(data20 === null){
coerced22 = "";
else if(data18 === null){
coerced21 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/requestIdLogLabel",schemaPath:"#/properties/requestIdLogLabel/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced22 !== undefined){
data20 = coerced22;
if(coerced21 !== undefined){
data18 = coerced21;
if(data !== undefined){
data["requestIdLogLabel"] = coerced22;
data["requestIdLogLabel"] = coerced21;
}
}
}
var valid0 = _errs62 === errors;
var valid0 = _errs59 === errors;
if(valid0){
let data21 = data.http2SessionTimeout;
const _errs64 = errors;
if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){
let dataType23 = typeof data21;
let coerced23 = undefined;
if(!(coerced23 !== undefined)){
if(dataType23 === "boolean" || data21 === null
|| (dataType23 === "string" && data21 && data21 == +data21 && !(data21 % 1))){
coerced23 = +data21;
let data19 = data.http2SessionTimeout;
const _errs61 = errors;
if(!(((typeof data19 == "number") && (!(data19 % 1) && !isNaN(data19))) && (isFinite(data19)))){
let dataType22 = typeof data19;
let coerced22 = undefined;
if(!(coerced22 !== undefined)){
if(dataType22 === "boolean" || data19 === null
|| (dataType22 === "string" && data19 && data19 == +data19 && !(data19 % 1))){
coerced22 = +data19;
}
else {
validate10.errors = [{instancePath:instancePath+"/http2SessionTimeout",schemaPath:"#/properties/http2SessionTimeout/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced23 !== undefined){
data21 = coerced23;
if(coerced22 !== undefined){
data19 = coerced22;
if(data !== undefined){
data["http2SessionTimeout"] = coerced23;
data["http2SessionTimeout"] = coerced22;
}
}
}
var valid0 = _errs64 === errors;
var valid0 = _errs61 === errors;
if(valid0){
let data22 = data.exposeHeadRoutes;
const _errs66 = errors;
if(typeof data22 !== "boolean"){
let coerced24 = undefined;
if(!(coerced24 !== undefined)){
if(data22 === "false" || data22 === 0 || data22 === null){
coerced24 = false;
let data20 = data.exposeHeadRoutes;
const _errs63 = errors;
if(typeof data20 !== "boolean"){
let coerced23 = undefined;
if(!(coerced23 !== undefined)){
if(data20 === "false" || data20 === 0 || data20 === null){
coerced23 = false;
}
else if(data22 === "true" || data22 === 1){
coerced24 = true;
else if(data20 === "true" || data20 === 1){
coerced23 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/exposeHeadRoutes",schemaPath:"#/properties/exposeHeadRoutes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced24 !== undefined){
data22 = coerced24;
if(coerced23 !== undefined){
data20 = coerced23;
if(data !== undefined){
data["exposeHeadRoutes"] = coerced24;
data["exposeHeadRoutes"] = coerced23;
}
}
}
var valid0 = _errs66 === errors;
var valid0 = _errs63 === errors;
if(valid0){
let data23 = data.useSemicolonDelimiter;
const _errs68 = errors;
let data21 = data.useSemicolonDelimiter;
const _errs65 = errors;
if(typeof data21 !== "boolean"){
let coerced24 = undefined;
if(!(coerced24 !== undefined)){
if(data21 === "false" || data21 === 0 || data21 === null){
coerced24 = false;
}
else if(data21 === "true" || data21 === 1){
coerced24 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced24 !== undefined){
data21 = coerced24;
if(data !== undefined){
data["useSemicolonDelimiter"] = coerced24;
}
}
}
var valid0 = _errs65 === errors;
if(valid0){
if(data.routerOptions !== undefined){
let data22 = data.routerOptions;
const _errs67 = errors;
if(errors === _errs67){
if(data22 && typeof data22 == "object" && !Array.isArray(data22)){
if(data22.ignoreTrailingSlash === undefined){
data22.ignoreTrailingSlash = false;
}
if(data22.ignoreDuplicateSlashes === undefined){
data22.ignoreDuplicateSlashes = false;
}
if(data22.maxParamLength === undefined){
data22.maxParamLength = 100;
}
if(data22.allowUnsafeRegex === undefined){
data22.allowUnsafeRegex = false;
}
if(data22.useSemicolonDelimiter === undefined){
data22.useSemicolonDelimiter = false;
}
let data23 = data22.ignoreTrailingSlash;
const _errs70 = errors;
if(typeof data23 !== "boolean"){
let coerced25 = undefined;
if(!(coerced25 !== undefined)){
@@ -1000,78 +1009,170 @@ else if(data23 === "true" || data23 === 1){
coerced25 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreTrailingSlash",schemaPath:"#/properties/routerOptions/properties/ignoreTrailingSlash/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced25 !== undefined){
data23 = coerced25;
if(data !== undefined){
data["useSemicolonDelimiter"] = coerced25;
if(data22 !== undefined){
data22["ignoreTrailingSlash"] = coerced25;
}
}
}
var valid0 = _errs68 === errors;
if(valid0){
if(data.versioning !== undefined){
let data24 = data.versioning;
const _errs70 = errors;
if(errors === _errs70){
if(data24 && typeof data24 == "object" && !Array.isArray(data24)){
let missing1;
if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){
validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
var valid7 = _errs70 === errors;
if(valid7){
let data24 = data22.ignoreDuplicateSlashes;
const _errs72 = errors;
if(typeof data24 !== "boolean"){
let coerced26 = undefined;
if(!(coerced26 !== undefined)){
if(data24 === "false" || data24 === 0 || data24 === null){
coerced26 = false;
}
else if(data24 === "true" || data24 === 1){
coerced26 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/ignoreDuplicateSlashes",schemaPath:"#/properties/routerOptions/properties/ignoreDuplicateSlashes/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced26 !== undefined){
data24 = coerced26;
if(data22 !== undefined){
data22["ignoreDuplicateSlashes"] = coerced26;
}
}
}
var valid7 = _errs72 === errors;
if(valid7){
let data25 = data22.maxParamLength;
const _errs74 = errors;
if(!(((typeof data25 == "number") && (!(data25 % 1) && !isNaN(data25))) && (isFinite(data25)))){
let dataType27 = typeof data25;
let coerced27 = undefined;
if(!(coerced27 !== undefined)){
if(dataType27 === "boolean" || data25 === null
|| (dataType27 === "string" && data25 && data25 == +data25 && !(data25 % 1))){
coerced27 = +data25;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/maxParamLength",schemaPath:"#/properties/routerOptions/properties/maxParamLength/type",keyword:"type",params:{type: "integer"},message:"must be integer"}];
return false;
}
}
if(coerced27 !== undefined){
data25 = coerced27;
if(data22 !== undefined){
data22["maxParamLength"] = coerced27;
}
}
}
var valid7 = _errs74 === errors;
if(valid7){
let data26 = data22.allowUnsafeRegex;
const _errs76 = errors;
if(typeof data26 !== "boolean"){
let coerced28 = undefined;
if(!(coerced28 !== undefined)){
if(data26 === "false" || data26 === 0 || data26 === null){
coerced28 = false;
}
else if(data26 === "true" || data26 === 1){
coerced28 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/allowUnsafeRegex",schemaPath:"#/properties/routerOptions/properties/allowUnsafeRegex/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced28 !== undefined){
data26 = coerced28;
if(data22 !== undefined){
data22["allowUnsafeRegex"] = coerced28;
}
}
}
var valid7 = _errs76 === errors;
if(valid7){
let data27 = data22.useSemicolonDelimiter;
const _errs78 = errors;
if(typeof data27 !== "boolean"){
let coerced29 = undefined;
if(!(coerced29 !== undefined)){
if(data27 === "false" || data27 === 0 || data27 === null){
coerced29 = false;
}
else if(data27 === "true" || data27 === 1){
coerced29 = true;
}
else {
validate10.errors = [{instancePath:instancePath+"/routerOptions/useSemicolonDelimiter",schemaPath:"#/properties/routerOptions/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
return false;
}
}
if(coerced29 !== undefined){
data27 = coerced29;
if(data22 !== undefined){
data22["useSemicolonDelimiter"] = coerced29;
}
}
}
var valid7 = _errs78 === errors;
}
}
}
}
}
else {
validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/type",keyword:"type",params:{type: "object"},message:"must be object"}];
validate10.errors = [{instancePath:instancePath+"/routerOptions",schemaPath:"#/properties/routerOptions/type",keyword:"type",params:{type: "object"},message:"must be object"}];
return false;
}
}
var valid0 = _errs70 === errors;
var valid0 = _errs67 === errors;
}
else {
var valid0 = true;
}
if(valid0){
if(data.constraints !== undefined){
let data25 = data.constraints;
const _errs73 = errors;
if(errors === _errs73){
if(data25 && typeof data25 == "object" && !Array.isArray(data25)){
for(const key2 in data25){
let data26 = data25[key2];
const _errs76 = errors;
if(errors === _errs76){
if(data26 && typeof data26 == "object" && !Array.isArray(data26)){
let missing2;
if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}];
let data28 = data.constraints;
const _errs80 = errors;
if(errors === _errs80){
if(data28 && typeof data28 == "object" && !Array.isArray(data28)){
for(const key2 in data28){
let data29 = data28[key2];
const _errs83 = errors;
if(errors === _errs83){
if(data29 && typeof data29 == "object" && !Array.isArray(data29)){
let missing1;
if(((((data29.name === undefined) && (missing1 = "name")) || ((data29.storage === undefined) && (missing1 = "storage"))) || ((data29.validate === undefined) && (missing1 = "validate"))) || ((data29.deriveConstraint === undefined) && (missing1 = "deriveConstraint"))){
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
return false;
}
else {
if(data26.name !== undefined){
let data27 = data26.name;
if(typeof data27 !== "string"){
let dataType26 = typeof data27;
let coerced26 = undefined;
if(!(coerced26 !== undefined)){
if(dataType26 == "number" || dataType26 == "boolean"){
coerced26 = "" + data27;
if(data29.name !== undefined){
let data30 = data29.name;
if(typeof data30 !== "string"){
let dataType30 = typeof data30;
let coerced30 = undefined;
if(!(coerced30 !== undefined)){
if(dataType30 == "number" || dataType30 == "boolean"){
coerced30 = "" + data30;
}
else if(data27 === null){
coerced26 = "";
else if(data30 === null){
coerced30 = "";
}
else {
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}];
return false;
}
}
if(coerced26 !== undefined){
data27 = coerced26;
if(data26 !== undefined){
data26["name"] = coerced26;
if(coerced30 !== undefined){
data30 = coerced30;
if(data29 !== undefined){
data29["name"] = coerced30;
}
}
}
@@ -1083,8 +1184,8 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/
return false;
}
}
var valid7 = _errs76 === errors;
if(!valid7){
var valid8 = _errs83 === errors;
if(!valid8){
break;
}
}
@@ -1094,7 +1195,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro
return false;
}
}
var valid0 = _errs73 === errors;
var valid0 = _errs80 === errors;
}
else {
var valid0 = true;
@@ -1123,8 +1224,6 @@ var valid0 = true;
}
}
}
}
}
else {
validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];
return false;
@@ -1135,4 +1234,5 @@ return errors === 0;
}
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":true}
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":false,"requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":false,"allowErrorHandlerOverride":true,"routerOptions":{"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"allowUnsafeRegex":false,"useSemicolonDelimiter":false}}
/* c8 ignore stop */

View File

@@ -2,8 +2,7 @@
const { AsyncResource } = require('node:async_hooks')
const { FifoMap: Fifo } = require('toad-cache')
const { safeParse: safeParseContentType, defaultContentType } = require('fast-content-type-parse')
const secureJson = require('secure-json-parse')
const { parse: secureJsonParse } = require('secure-json-parse')
const {
kDefaultJsonParse,
kContentTypeParser,
@@ -25,8 +24,10 @@ const {
FST_ERR_CTP_INVALID_MEDIA_TYPE,
FST_ERR_CTP_INVALID_CONTENT_LENGTH,
FST_ERR_CTP_EMPTY_JSON_BODY,
FST_ERR_CTP_INSTANCE_ALREADY_STARTED
FST_ERR_CTP_INSTANCE_ALREADY_STARTED,
FST_ERR_CTP_INVALID_JSON_BODY
} = require('./errors')
const { FSTSEC001 } = require('./warnings')
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
@@ -34,7 +35,7 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
this.customParsers = new Map()
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')]
this.parserList = ['application/json', 'text/plain']
this.parserRegExpList = []
this.cache = new Fifo(100)
}
@@ -42,9 +43,16 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
const contentTypeIsString = typeof contentType === 'string'
if (!contentTypeIsString && !(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
if (contentTypeIsString && contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
if (typeof parserFn !== 'function') throw new FST_ERR_CTP_INVALID_HANDLER()
if (contentTypeIsString) {
contentType = contentType.trim().toLowerCase()
if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
} else if (!(contentType instanceof RegExp)) {
throw new FST_ERR_CTP_INVALID_TYPE()
}
if (typeof parserFn !== 'function') {
throw new FST_ERR_CTP_INVALID_HANDLER()
}
if (this.existingParser(contentType)) {
throw new FST_ERR_CTP_ALREADY_PRESENT(contentType)
@@ -63,21 +71,29 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
parserFn
)
if (contentTypeIsString && contentType === '*') {
if (contentType === '*') {
this.customParsers.set('', parser)
} else {
if (contentTypeIsString) {
this.parserList.unshift(new ParserListItem(contentType))
this.parserList.unshift(contentType)
this.customParsers.set(contentType, parser)
} else {
contentType.isEssence = contentType.source.indexOf(';') === -1
validateRegExp(contentType)
this.parserRegExpList.unshift(contentType)
this.customParsers.set(contentType.toString(), parser)
}
this.customParsers.set(contentType.toString(), parser)
}
}
ContentTypeParser.prototype.hasParser = function (contentType) {
return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
if (typeof contentType === 'string') {
contentType = contentType.trim().toLowerCase()
} else {
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
contentType = contentType.toString()
}
return this.customParsers.has(contentType)
}
ContentTypeParser.prototype.existingParser = function (contentType) {
@@ -92,38 +108,33 @@ ContentTypeParser.prototype.existingParser = function (contentType) {
}
ContentTypeParser.prototype.getParser = function (contentType) {
if (this.hasParser(contentType)) {
return this.customParsers.get(contentType)
}
const parser = this.cache.get(contentType)
let parser = this.customParsers.get(contentType)
if (parser !== undefined) return parser
parser = this.cache.get(contentType)
if (parser !== undefined) return parser
const parsed = safeParseContentType(contentType)
// dummyContentType always the same object
// we can use === for the comparison and return early
if (parsed === defaultContentType) {
return this.customParsers.get('')
}
// eslint-disable-next-line no-var
for (var i = 0; i !== this.parserList.length; ++i) {
const caseInsensitiveContentType = contentType.toLowerCase()
for (let i = 0; i !== this.parserList.length; ++i) {
const parserListItem = this.parserList[i]
if (compareContentType(parsed, parserListItem)) {
const parser = this.customParsers.get(parserListItem.name)
// we set request content-type in cache to reduce parsing of MIME type
if (
caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem &&
(
caseInsensitiveContentType.length === parserListItem.length ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ ||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */
)
) {
parser = this.customParsers.get(parserListItem)
this.cache.set(contentType, parser)
return parser
}
}
// eslint-disable-next-line no-var
for (var j = 0; j !== this.parserRegExpList.length; ++j) {
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
const parserRegExp = this.parserRegExpList[j]
if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) {
const parser = this.customParsers.get(parserRegExp.toString())
// we set request content-type in cache to reduce parsing of MIME type
if (parserRegExp.test(contentType)) {
parser = this.customParsers.get(parserRegExp.toString())
this.cache.set(contentType, parser)
return parser
}
@@ -140,13 +151,19 @@ ContentTypeParser.prototype.removeAll = function () {
}
ContentTypeParser.prototype.remove = function (contentType) {
if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
let parsers
const removed = this.customParsers.delete(contentType.toString())
if (typeof contentType === 'string') {
contentType = contentType.trim().toLowerCase()
parsers = this.parserList
} else {
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
contentType = contentType.toString()
parsers = this.parserRegExpList
}
const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
const idx = parsers.findIndex(ct => ct.toString() === contentType.toString())
const removed = this.customParsers.delete(contentType)
const idx = parsers.findIndex(ct => ct.toString() === contentType)
if (idx > -1) {
parsers.splice(idx, 1)
@@ -159,17 +176,18 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
const parser = this.getParser(contentType)
if (parser === undefined) {
if (request.is404) {
if (request.is404 === true) {
handler(request, reply)
} else {
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
return
}
// Early return to avoid allocating an AsyncResource if it's not needed
reply[kReplyIsError] = true
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
return
}
const resource = new AsyncResource('content-type-parser:run', request)
const done = resource.bind(onDone)
if (parser.asString === true || parser.asBuffer === true) {
rawBody(
@@ -179,50 +197,44 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
parser,
done
)
} else {
const result = parser.fn(request, request[kRequestPayloadStream], done)
if (result && typeof result.then === 'function') {
result.then(body => done(null, body), done)
}
return
}
function done (error, body) {
// We cannot use resource.bind() because it is broken in node v12 and v14
resource.runInAsyncScope(() => {
resource.emitDestroy()
if (error) {
reply[kReplyIsError] = true
reply.send(error)
} else {
request.body = body
handler(request, reply)
}
})
const result = parser.fn(request, request[kRequestPayloadStream], done)
if (result && typeof result.then === 'function') {
result.then(body => { done(null, body) }, done)
}
function onDone (error, body) {
resource.emitDestroy()
if (error != null) {
// We must close the connection as the client may
// send more data
reply.header('connection', 'close')
reply[kReplyIsError] = true
reply.send(error)
return
}
request.body = body
handler(request, reply)
}
}
function rawBody (request, reply, options, parser, done) {
const asString = parser.asString
const asString = parser.asString === true
const limit = options.limit === null ? parser.bodyLimit : options.limit
const contentLength = request.headers['content-length'] === undefined
? NaN
: Number(request.headers['content-length'])
const contentLength = Number(request.headers['content-length'])
if (contentLength > limit) {
// We must close the connection as the client is going
// to send this data anyway
reply.header('connection', 'close')
reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
return
}
let receivedLength = 0
let body = asString === true ? '' : []
let body = asString ? '' : []
const payload = request[kRequestPayloadStream] || request.raw
if (asString === true) {
if (asString) {
payload.setEncoding('utf8')
}
@@ -232,7 +244,7 @@ function rawBody (request, reply, options, parser, done) {
payload.resume()
function onData (chunk) {
receivedLength += chunk.length
receivedLength += asString ? Buffer.byteLength(chunk) : chunk.length
const { receivedEncodedLength = 0 } = payload
// The resulting body length must not exceed bodyLimit (see "zip bomb").
// The case when encoded length is larger than received length is rather theoretical,
@@ -241,11 +253,11 @@ function rawBody (request, reply, options, parser, done) {
payload.removeListener('data', onData)
payload.removeListener('end', onEnd)
payload.removeListener('error', onEnd)
reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
return
}
if (asString === true) {
if (asString) {
body += chunk
} else {
body.push(chunk)
@@ -257,51 +269,45 @@ function rawBody (request, reply, options, parser, done) {
payload.removeListener('end', onEnd)
payload.removeListener('error', onEnd)
if (err !== undefined) {
if (err != null) {
if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) {
err.statusCode = 400
}
reply[kReplyIsError] = true
reply.code(err.statusCode).send(err)
done(err, undefined)
return
}
if (asString === true) {
receivedLength = Buffer.byteLength(body)
}
if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) {
reply.header('connection', 'close')
reply.send(new FST_ERR_CTP_INVALID_CONTENT_LENGTH())
done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined)
return
}
if (asString === false) {
if (!asString) {
body = Buffer.concat(body)
}
const result = parser.fn(request, body, done)
if (result && typeof result.then === 'function') {
result.then(body => done(null, body), done)
result.then(body => { done(null, body) }, done)
}
}
}
function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
const parseOptions = { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }
return defaultJsonParser
function defaultJsonParser (req, body, done) {
if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) {
return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
if (body.length === 0) {
done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
return
}
let json
try {
json = secureJson.parse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })
} catch (err) {
err.statusCode = 400
return done(err, undefined)
done(null, secureJsonParse(body, parseOptions))
} catch {
done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined)
}
done(null, json)
}
}
@@ -373,60 +379,15 @@ function removeAllContentTypeParsers () {
this[kContentTypeParser].removeAll()
}
function compareContentType (contentType, parserListItem) {
if (parserListItem.isEssence) {
// we do essence check
return contentType.type.indexOf(parserListItem) !== -1
} else {
// when the content-type includes parameters
// we do a full-text search
// reject essence content-type before checking parameters
if (contentType.type.indexOf(parserListItem.type) === -1) return false
for (const key of parserListItem.parameterKeys) {
// reject when missing parameters
if (!(key in contentType.parameters)) return false
// reject when parameters do not match
if (contentType.parameters[key] !== parserListItem.parameters[key]) return false
}
return true
function validateRegExp (regexp) {
// RegExp should either start with ^ or include ;?
// It can ensure the user is properly detect the essence
// MIME types.
if (regexp.source[0] !== '^' && regexp.source.includes(';?') === false) {
FSTSEC001(regexp.source)
}
}
function compareRegExpContentType (contentType, essenceMIMEType, regexp) {
if (regexp.isEssence) {
// we do essence check
return regexp.test(essenceMIMEType)
} else {
// when the content-type includes parameters
// we do a full-text match
return regexp.test(contentType)
}
}
function ParserListItem (contentType) {
this.name = contentType
// we pre-calculate all the needed information
// before content-type comparison
const parsed = safeParseContentType(contentType)
this.isEssence = contentType.indexOf(';') === -1
// we should not allow empty string for parser list item
// because it would become a match-all handler
if (this.isEssence === false && parsed.type === '') {
// handle semicolon or empty string
const tmp = contentType.split(';', 1)[0]
this.type = tmp === '' ? contentType : tmp
} else {
this.type = parsed.type
}
this.parameters = parsed.parameters
this.parameterKeys = Object.keys(parsed.parameters)
}
// used in ContentTypeParser.remove
ParserListItem.prototype.toString = function () {
return this.name
}
module.exports = ContentTypeParser
module.exports.helpers = {
buildContentTypeParser,

View File

@@ -14,8 +14,7 @@ const {
kContentTypeParser,
kRouteByFastify,
kRequestCacheValidateFns,
kReplyCacheSerializeFns,
kPublicRouteContext
kReplyCacheSerializeFns
} = require('./symbols.js')
// Object that holds the context of every request
@@ -79,35 +78,14 @@ function Context ({
this.validatorCompiler = validatorCompiler || null
this.serializerCompiler = serializerCompiler || null
// Route + Userland configurations for the route
this[kPublicRouteContext] = getPublicRouteContext(this)
this.server = server
}
function getPublicRouteContext (context) {
return Object.create(null, {
schema: {
enumerable: true,
get () {
return context.schema
}
},
config: {
enumerable: true,
get () {
return context.config
}
}
})
}
function defaultSchemaErrorFormatter (errors, dataVar) {
let text = ''
const separator = ', '
// eslint-disable-next-line no-var
for (var i = 0; i !== errors.length; ++i) {
for (let i = 0; i !== errors.length; ++i) {
const e = errors[i]
text += dataVar + (e.instancePath || '') + ' ' + e.message + separator
}

View File

@@ -1,7 +1,5 @@
'use strict'
/* eslint no-prototype-builtins: 0 */
const {
kReply,
kRequest,
@@ -13,13 +11,13 @@ const {
FST_ERR_DEC_ALREADY_PRESENT,
FST_ERR_DEC_MISSING_DEPENDENCY,
FST_ERR_DEC_AFTER_START,
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE
FST_ERR_DEC_REFERENCE_TYPE,
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE,
FST_ERR_DEC_UNDECLARED
} = require('./errors')
const { FSTDEP006 } = require('./warnings')
function decorate (instance, name, fn, dependencies) {
if (Object.prototype.hasOwnProperty.call(instance, name)) {
if (Object.hasOwn(instance, name)) {
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
}
@@ -35,9 +33,21 @@ function decorate (instance, name, fn, dependencies) {
}
}
function getInstanceDecorator (name) {
if (!checkExistence(this, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
}
if (typeof this[name] === 'function') {
return this[name].bind(this)
}
return this[name]
}
function decorateConstructor (konstructor, name, fn, dependencies) {
const instance = konstructor.prototype
if (Object.prototype.hasOwnProperty.call(instance, name) || hasKey(konstructor, name)) {
if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
}
@@ -58,7 +68,7 @@ function decorateConstructor (konstructor, name, fn, dependencies) {
function checkReferenceType (name, fn) {
if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
FSTDEP006(name)
throw new FST_ERR_DEC_REFERENCE_TYPE(name, typeof fn)
}
}
@@ -102,8 +112,7 @@ function checkDependencies (instance, name, deps) {
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
}
// eslint-disable-next-line no-var
for (var i = 0; i !== deps.length; ++i) {
for (let i = 0; i !== deps.length; ++i) {
if (!checkExistence(instance, deps[i])) {
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
}
@@ -137,5 +146,7 @@ module.exports = {
existReply: checkReplyExistence,
dependencies: checkDependencies,
decorateReply,
decorateRequest
decorateRequest,
getInstanceDecorator,
hasKey
}

View File

@@ -1,12 +1,12 @@
'use strict'
const statusCodes = require('node:http').STATUS_CODES
const wrapThenable = require('./wrapThenable')
const wrapThenable = require('./wrap-thenable.js')
const { setErrorStatusCode } = require('./error-status.js')
const {
kReplyHeaders,
kReplyNextErrorHandler,
kReplyIsRunningOnErrorHook,
kReplyHasStatusCode,
kRouteContext,
kDisableRequestLogging
} = require('./symbols.js')
@@ -39,7 +39,7 @@ function handleError (reply, error, cb) {
if (!reply.log[kDisableRequestLogging]) {
reply.log.warn(
{ req: reply.request, res: reply, err: error },
error && error.message
error?.message
)
}
reply.raw.writeHead(reply.raw.statusCode)
@@ -81,22 +81,19 @@ function handleError (reply, error, cb) {
function defaultErrorHandler (error, request, reply) {
setErrorHeaders(error, reply)
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
const statusCode = error.statusCode || error.status
reply.code(statusCode >= 400 ? statusCode : 500)
}
setErrorStatusCode(reply, error)
if (reply.statusCode < 500) {
if (!reply.log[kDisableRequestLogging]) {
reply.log.info(
{ res: reply, err: error },
error && error.message
error?.message
)
}
} else {
if (!reply.log[kDisableRequestLogging]) {
reply.log.error(
{ req: request, res: reply, err: error },
error && error.message
error?.message
)
}
}
@@ -110,18 +107,20 @@ function fallbackErrorHandler (error, reply, cb) {
let payload
try {
const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
payload = (serializerFn === false)
? serializeError({
if (serializerFn === false) {
payload = serializeError({
error: statusCodes[statusCode + ''],
code: error.code,
message: error.message,
statusCode
})
: serializerFn(Object.create(error, {
} else {
payload = serializerFn(Object.create(error, {
error: { value: statusCodes[statusCode + ''] },
message: { value: error.message },
statusCode: { value: statusCode }
}))
}
} catch (err) {
if (!reply.log[kDisableRequestLogging]) {
// error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization

View File

@@ -1,5 +1,5 @@
// This file is autogenerated by build/build-error-serializer.js, do not edit
/* istanbul ignore file */
/* c8 ignore start */
'use strict'
@@ -117,3 +117,4 @@ let addComma = false
return main
}(validator, serializer)
/* c8 ignore stop */

14
backend/node_modules/fastify/lib/error-status.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
const {
kReplyHasStatusCode
} = require('./symbols')
function setErrorStatusCode (reply, err) {
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
const statusCode = err && (err.statusCode || err.status)
reply.code(statusCode >= 400 ? statusCode : 500)
}
}
module.exports = { setErrorStatusCode }

View File

@@ -47,12 +47,6 @@ const codes = {
500,
TypeError
),
FST_ERR_VERSION_CONSTRAINT_NOT_STR: createError(
'FST_ERR_VERSION_CONSTRAINT_NOT_STR',
'Version constraint should be a string.',
500,
TypeError
),
FST_ERR_VALIDATION: createError(
'FST_ERR_VALIDATION',
'%s',
@@ -70,6 +64,12 @@ const codes = {
500,
TypeError
),
FST_ERR_ERROR_HANDLER_ALREADY_SET: createError(
'FST_ERR_ERROR_HANDLER_ALREADY_SET',
"Error Handler already set in this scope. Set 'allowErrorHandlerOverride: true' to allow overriding.",
500,
TypeError
),
/**
* ContentTypeParser
@@ -124,6 +124,11 @@ const codes = {
"Body cannot be empty when content-type is set to 'application/json'",
400
),
FST_ERR_CTP_INVALID_JSON_BODY: createError(
'FST_ERR_CTP_INVALID_JSON_BODY',
"Body is not valid JSON but content-type is set to 'application/json'",
400
),
FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError(
'FST_ERR_CTP_INSTANCE_ALREADY_STARTED',
'Cannot call "%s" when fastify instance is already started!',
@@ -151,6 +156,14 @@ const codes = {
'FST_ERR_DEC_AFTER_START',
"The decorator '%s' has been added after start!"
),
FST_ERR_DEC_REFERENCE_TYPE: createError(
'FST_ERR_DEC_REFERENCE_TYPE',
"The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead."
),
FST_ERR_DEC_UNDECLARED: createError(
'FST_ERR_DEC_UNDECLARED',
"No decorator '%s' has been declared on %s."
),
/**
* hooks
@@ -191,7 +204,7 @@ const codes = {
FST_ERR_HOOK_TIMEOUT: createError(
'FST_ERR_HOOK_TIMEOUT',
"A callback for '%s' hook timed out. You may have forgotten to call 'done' function or to resolve a Promise"
"A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise"
),
/**
@@ -209,6 +222,27 @@ const codes = {
TypeError
),
FST_ERR_LOG_INVALID_LOGGER_INSTANCE: createError(
'FST_ERR_LOG_INVALID_LOGGER_INSTANCE',
'loggerInstance only accepts a logger instance.',
500,
TypeError
),
FST_ERR_LOG_INVALID_LOGGER_CONFIG: createError(
'FST_ERR_LOG_INVALID_LOGGER_CONFIG',
'logger options only accepts a configuration object.',
500,
TypeError
),
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED: createError(
'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED',
'You cannot provide both logger and loggerInstance. Please provide only one.',
500,
TypeError
),
/**
* reply
*/
@@ -222,6 +256,10 @@ const codes = {
'FST_ERR_REP_RESPONSE_BODY_CONSUMED',
'Response.body is already consumed.'
),
FST_ERR_REP_READABLE_STREAM_LOCKED: createError(
'FST_ERR_REP_READABLE_STREAM_LOCKED',
'ReadableStream was locked. You should call releaseLock() method on reader before sending.'
),
FST_ERR_REP_ALREADY_SENT: createError(
'FST_ERR_REP_ALREADY_SENT',
'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
@@ -301,14 +339,6 @@ const codes = {
'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }'
),
/**
* http2
*/
FST_ERR_HTTP2_INVALID_VERSION: createError(
'FST_ERR_HTTP2_INVALID_VERSION',
'HTTP2 is available only from node >= 8.8.1'
),
/**
* initialConfig
*/
@@ -339,12 +369,6 @@ const codes = {
'Unexpected error from async constraint',
500
),
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE: createError(
'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE',
'The defaultRoute type should be a function',
500,
TypeError
),
FST_ERR_INVALID_URL: createError(
'FST_ERR_INVALID_URL',
"URL must be a string. Received '%s'",
@@ -429,6 +453,12 @@ const codes = {
'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE',
"The decorator '%s'%s is not present in %s"
),
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER: createError(
'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER',
'The %s plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.',
500,
TypeError
),
/**
* Avvio Errors

View File

@@ -18,8 +18,8 @@ const { buildErrorHandler } = require('./error-handler.js')
const {
FST_ERR_NOT_FOUND
} = require('./errors')
const { createChildLogger } = require('./logger')
const { getGenReqId } = require('./reqIdGenFactory.js')
const { createChildLogger } = require('./logger-factory')
const { getGenReqId } = require('./req-id-gen-factory.js')
/**
* Each fastify instance have a:
@@ -49,7 +49,8 @@ function fourOhFour (options) {
function basic404 (request, reply) {
const { url, method } = request.raw
const message = `Route ${method}:${url} not found`
if (!disableRequestLogging) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging
if (!resolvedDisableRequestLogging) {
request.log.info(message)
}
reply.code(404).send({
@@ -150,7 +151,9 @@ function fourOhFour (options) {
.map(h => h.bind(this))
context[hook] = toSet.length ? toSet : null
}
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
context.errorHandler = opts.errorHandler
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
: this[kErrorHandler]
})
if (this[kFourOhFourContext] !== null && prefix === '/') {

187
backend/node_modules/fastify/lib/handle-request.js generated vendored Normal file
View File

@@ -0,0 +1,187 @@
'use strict'
const diagnostics = require('node:diagnostics_channel')
const { validate: validateSchema } = require('./validation')
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
const wrapThenable = require('./wrap-thenable')
const { setErrorStatusCode } = require('./error-status')
const {
kReplyIsError,
kRouteContext,
kFourOhFourContext,
kSupportedHTTPMethods
} = require('./symbols')
const channels = diagnostics.tracingChannel('fastify.request.handler')
function handleRequest (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const method = request.method
if (this[kSupportedHTTPMethods].bodyless.has(method)) {
handler(request, reply)
return
}
if (this[kSupportedHTTPMethods].bodywith.has(method)) {
const headers = request.headers
const contentType = headers['content-type']
if (contentType === undefined) {
const contentLength = headers['content-length']
const transferEncoding = headers['transfer-encoding']
const isEmptyBody = transferEncoding === undefined &&
(contentLength === undefined || contentLength === '0')
if (isEmptyBody) {
// Request has no body to parse
handler(request, reply)
return
}
request[kRouteContext].contentTypeParser.run('', handler, request, reply)
return
}
request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply)
return
}
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
handler(request, reply)
}
function handler (request, reply) {
try {
if (request[kRouteContext].preValidation !== null) {
preValidationHookRunner(
request[kRouteContext].preValidation,
request,
reply,
preValidationCallback
)
} else {
preValidationCallback(null, request, reply)
}
} catch (err) {
preValidationCallback(err, request, reply)
}
}
function preValidationCallback (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const validationErr = validateSchema(reply[kRouteContext], request)
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
if (isAsync) {
const cb = validationCompleted.bind(null, request, reply)
validationErr.then(cb, cb)
} else {
validationCompleted(request, reply, validationErr)
}
}
function validationCompleted (request, reply, validationErr) {
if (validationErr) {
if (reply[kRouteContext].attachValidation === false) {
reply.send(validationErr)
return
}
reply.request.validationError = validationErr
}
// preHandler hook
if (request[kRouteContext].preHandler !== null) {
preHandlerHookRunner(
request[kRouteContext].preHandler,
request,
reply,
preHandlerCallback
)
} else {
preHandlerCallback(null, request, reply)
}
}
function preHandlerCallback (err, request, reply) {
if (reply.sent) return
const context = request[kRouteContext]
if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
preHandlerCallbackInner(err, request, reply)
} else {
const store = {
request,
reply,
async: false,
route: {
url: context.config.url,
method: context.config.method
}
}
channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store)
}
}
function preHandlerCallbackInner (err, request, reply, store) {
const context = request[kRouteContext]
try {
if (err != null) {
reply[kReplyIsError] = true
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store)
}
reply.send(err)
return
}
let result
try {
result = context.handler(request, reply)
} catch (err) {
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store)
}
reply[kReplyIsError] = true
reply.send(err)
return
}
if (result !== undefined) {
if (result !== null && typeof result.then === 'function') {
wrapThenable(result, reply, store)
} else {
reply.send(result)
}
}
} finally {
if (store) channels.end.publish(store)
}
}
module.exports = handleRequest
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }

View File

@@ -1,156 +0,0 @@
'use strict'
const { validate: validateSchema } = require('./validation')
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
const wrapThenable = require('./wrapThenable')
const {
kReplyIsError,
kRouteContext
} = require('./symbols')
function handleRequest (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const method = request.raw.method
const headers = request.headers
const context = request[kRouteContext]
if (method === 'GET' || method === 'HEAD') {
handler(request, reply)
return
}
const contentType = headers['content-type']
if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' ||
method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'COPY' || method === 'MOVE' ||
method === 'MKCOL' || method === 'REPORT' || method === 'MKCALENDAR') {
if (contentType === undefined) {
if (
headers['transfer-encoding'] === undefined &&
(headers['content-length'] === '0' || headers['content-length'] === undefined)
) { // Request has no body to parse
handler(request, reply)
} else {
context.contentTypeParser.run('', handler, request, reply)
}
} else {
context.contentTypeParser.run(contentType, handler, request, reply)
}
return
}
if (method === 'OPTIONS' || method === 'DELETE') {
if (
contentType !== undefined &&
(
headers['transfer-encoding'] !== undefined ||
headers['content-length'] !== undefined
)
) {
context.contentTypeParser.run(contentType, handler, request, reply)
} else {
handler(request, reply)
}
return
}
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
handler(request, reply)
}
function handler (request, reply) {
try {
if (request[kRouteContext].preValidation !== null) {
preValidationHookRunner(
request[kRouteContext].preValidation,
request,
reply,
preValidationCallback
)
} else {
preValidationCallback(null, request, reply)
}
} catch (err) {
preValidationCallback(err, request, reply)
}
}
function preValidationCallback (err, request, reply) {
if (reply.sent === true) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
const validationErr = validateSchema(reply[kRouteContext], request)
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
if (isAsync) {
const cb = validationCompleted.bind(null, request, reply)
validationErr.then(cb, cb)
} else {
validationCompleted(request, reply, validationErr)
}
}
function validationCompleted (request, reply, validationErr) {
if (validationErr) {
if (reply[kRouteContext].attachValidation === false) {
reply.send(validationErr)
return
}
reply.request.validationError = validationErr
}
// preHandler hook
if (request[kRouteContext].preHandler !== null) {
preHandlerHookRunner(
request[kRouteContext].preHandler,
request,
reply,
preHandlerCallback
)
} else {
preHandlerCallback(null, request, reply)
}
}
function preHandlerCallback (err, request, reply) {
if (reply.sent) return
if (err != null) {
reply[kReplyIsError] = true
reply.send(err)
return
}
let result
try {
result = request[kRouteContext].handler(request, reply)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
return
}
if (result !== undefined) {
if (result !== null && typeof result.then === 'function') {
wrapThenable(result, reply)
} else {
reply.send(result)
}
}
}
module.exports = handleRequest
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }

View File

@@ -3,15 +3,27 @@ function headRouteOnSendHandler (req, reply, payload, done) {
// If payload is undefined
if (payload === undefined) {
reply.header('content-length', '0')
return done(null, null)
done(null, null)
return
}
// node:stream
if (typeof payload.resume === 'function') {
payload.on('error', (err) => {
reply.log.error({ err }, 'Error on Stream found for HEAD route')
})
payload.resume()
return done(null, null)
done(null, null)
return
}
// node:stream/web
if (typeof payload.getReader === 'function') {
payload.cancel('Stream cancelled by HEAD route').catch((err) => {
reply.log.error({ err }, 'Error on Stream found for HEAD route')
})
done(null, null)
return
}
const size = '' + Buffer.byteLength(payload)
@@ -23,7 +35,9 @@ function headRouteOnSendHandler (req, reply, payload, done) {
function parseHeadOnSendHandlers (onSendHandlers) {
if (onSendHandlers == null) return headRouteOnSendHandler
return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
return Array.isArray(onSendHandlers)
? [...onSendHandlers, headRouteOnSendHandler]
: [onSendHandlers, headRouteOnSendHandler]
}
module.exports = {

View File

@@ -98,9 +98,12 @@ function hookRunnerApplication (hookName, boot, server, cb) {
next()
function exit (err) {
const hookFnName = hooks[i - 1]?.name
const hookFnFragment = hookFnName ? ` "${hookFnName}"` : ''
if (err) {
if (err.code === 'AVV_ERR_READY_TIMEOUT') {
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName))
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName, hookFnFragment))
} else {
err = AVVIO_ERRORS_MAP[err.code] != null
? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))

View File

@@ -1,24 +0,0 @@
'use strict'
module.exports = {
supportedMethods: [
'DELETE',
'GET',
'HEAD',
'PATCH',
'POST',
'PUT',
'OPTIONS',
'PROPFIND',
'PROPPATCH',
'MKCOL',
'COPY',
'MOVE',
'LOCK',
'UNLOCK',
'TRACE',
'SEARCH',
'REPORT',
'MKCALENDAR'
]
}

View File

@@ -1,6 +1,6 @@
'use strict'
const validate = require('./configValidator')
const validate = require('./config-validator')
const deepClone = require('rfdc')({ circles: true, proto: false })
const { FST_ERR_INIT_OPTS_INVALID } = require('./errors')

View File

@@ -1,105 +1,48 @@
'use strict'
/**
* Code imported from `pino-http`
* Repo: https://github.com/pinojs/pino-http
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
*/
const nullLogger = require('abstract-logging')
const pino = require('pino')
const { serializersSym } = pino.symbols
const {
FST_ERR_LOG_INVALID_DESTINATION,
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
FST_ERR_LOG_INVALID_LOGGER
} = require('./errors')
function createPinoLogger (opts) {
if (opts.stream && opts.file) {
throw new FST_ERR_LOG_INVALID_DESTINATION()
} else if (opts.file) {
// we do not have stream
opts.stream = pino.destination(opts.file)
delete opts.file
/**
* Utility for creating a child logger with the appropriate bindings, logger factory
* and validation.
* @param {object} context
* @param {import('../fastify').FastifyBaseLogger} logger
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
* @param {string} reqId
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
*
* @returns {object} New logger instance, inheriting all parent bindings,
* with child bindings added.
*/
function createChildLogger (context, logger, req, reqId, loggerOpts) {
const loggerBindings = {
[context.requestIdLogLabel]: reqId
}
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
// Optimization: bypass validation if the factory is our own default factory
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
validateLogger(child, true) // throw if the child is not a valid logger
}
const prevLogger = opts.logger
const prevGenReqId = opts.genReqId
let logger = null
if (prevLogger) {
opts.logger = undefined
opts.genReqId = undefined
// we need to tap into pino internals because in v5 it supports
// adding serializers in child loggers
if (prevLogger[serializersSym]) {
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
}
logger = prevLogger.child({}, opts)
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
logger = pino(opts, opts.stream)
}
return logger
return child
}
const serializers = {
req: function asReqValue (req) {
return {
method: req.method,
url: req.url,
version: req.headers && req.headers['accept-version'],
hostname: req.hostname,
remoteAddress: req.ip,
remotePort: req.socket ? req.socket.remotePort : undefined
}
},
err: pino.stdSerializers.err,
res: function asResValue (reply) {
return {
statusCode: reply.statusCode
}
}
}
function now () {
const ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}
function createLogger (options) {
if (!options.logger) {
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
}
if (validateLogger(options.logger)) {
const logger = createPinoLogger({
logger: options.logger,
serializers: Object.assign({}, serializers, options.logger.serializers)
})
return { logger, hasLogger: true }
}
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
/** Default factory to create child logger instance
*
* @param {import('../fastify.js').FastifyBaseLogger} logger
* @param {import('../types/logger.js').Bindings} bindings
* @param {import('../types/logger.js').ChildLoggerOptions} opts
*
* @returns {import('../types/logger.js').FastifyBaseLogger}
*/
function defaultChildLoggerFactory (logger, bindings, opts) {
return logger.child(bindings, opts)
}
/**
@@ -129,42 +72,65 @@ function validateLogger (logger, strict) {
}
}
/**
* Utility for creating a child logger with the appropriate bindings, logger factory
* and validation.
* @param {object} context
* @param {import('../fastify').FastifyBaseLogger} logger
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
* @param {string} reqId
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
*/
function createChildLogger (context, logger, req, reqId, loggerOpts) {
const loggerBindings = {
[context.requestIdLogLabel]: reqId
}
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
// Optimization: bypass validation if the factory is our own default factory
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
validateLogger(child, true) // throw if the child is not a valid logger
function createLogger (options) {
if (options.logger && options.loggerInstance) {
throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
}
return child
if (!options.loggerInstance && !options.logger) {
const nullLogger = require('abstract-logging')
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
}
const { createPinoLogger, serializers } = require('./logger-pino.js')
// check if the logger instance has all required properties
if (validateLogger(options.loggerInstance)) {
const logger = createPinoLogger({
logger: options.loggerInstance,
serializers: Object.assign({}, serializers, options.loggerInstance.serializers)
})
return { logger, hasLogger: true }
}
// if a logger instance is passed to logger, throw an exception
if (validateLogger(options.logger)) {
throw FST_ERR_LOG_INVALID_LOGGER_CONFIG()
}
if (options.loggerInstance) {
throw FST_ERR_LOG_INVALID_LOGGER_INSTANCE()
}
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
}
/**
* @param {import('../fastify.js').FastifyBaseLogger} logger
* @param {import('../types/logger.js').Bindings} bindings
* @param {import('../types/logger.js').ChildLoggerOptions} opts
*/
function defaultChildLoggerFactory (logger, bindings, opts) {
return logger.child(bindings, opts)
function now () {
const ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}
module.exports = {
createLogger,
createChildLogger,
defaultChildLoggerFactory,
serializers,
createLogger,
validateLogger,
now
}

68
backend/node_modules/fastify/lib/logger-pino.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
'use strict'
/**
* Code imported from `pino-http`
* Repo: https://github.com/pinojs/pino-http
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
*/
const pino = require('pino')
const { serializersSym } = pino.symbols
const {
FST_ERR_LOG_INVALID_DESTINATION
} = require('./errors')
function createPinoLogger (opts) {
if (opts.stream && opts.file) {
throw new FST_ERR_LOG_INVALID_DESTINATION()
} else if (opts.file) {
// we do not have stream
opts.stream = pino.destination(opts.file)
delete opts.file
}
const prevLogger = opts.logger
const prevGenReqId = opts.genReqId
let logger = null
if (prevLogger) {
opts.logger = undefined
opts.genReqId = undefined
// we need to tap into pino internals because in v5 it supports
// adding serializers in child loggers
if (prevLogger[serializersSym]) {
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
}
logger = prevLogger.child({}, opts)
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
logger = pino(opts, opts.stream)
}
return logger
}
const serializers = {
req: function asReqValue (req) {
return {
method: req.method,
url: req.url,
version: req.headers && req.headers['accept-version'],
host: req.host,
remoteAddress: req.ip,
remotePort: req.socket ? req.socket.remotePort : undefined
}
},
err: pino.stdSerializers.err,
res: function asResValue (reply) {
return {
statusCode: reply.statusCode
}
}
}
module.exports = {
serializers,
createPinoLogger
}

View File

@@ -12,15 +12,16 @@ const {
kReply,
kRequest,
kFourOhFour,
kPluginNameChain
kPluginNameChain,
kErrorHandlerAlreadySet
} = require('./symbols.js')
const Reply = require('./reply')
const Request = require('./request')
const SchemaController = require('./schema-controller')
const ContentTypeParser = require('./contentTypeParser')
const ContentTypeParser = require('./content-type-parser.js')
const { buildHooks } = require('./hooks')
const pluginUtils = require('./pluginUtils')
const pluginUtils = require('./plugin-utils.js')
// Function that runs the encapsulation magic.
// Everything that need to be encapsulated must be handled in this function.
@@ -57,6 +58,7 @@ module.exports = function override (old, fn, opts) {
// Track the plugin chain since the root instance.
// When an non-encapsulated plugin is added, the chain will be updated.
instance[kPluginNameChain] = [fnName]
instance[kErrorHandlerAlreadySet] = false
if (instance[kLogSerializers] || opts.logSerializers) {
instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers)
@@ -66,7 +68,7 @@ module.exports = function override (old, fn, opts) {
instance[kFourOhFour].arrange404(instance)
}
for (const hook of instance[kHooks].onRegister) hook.call(this, instance, opts)
for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts)
return instance
}

View File

@@ -6,12 +6,14 @@ const kRegisteredPlugins = Symbol.for('registered-plugin')
const {
kTestInternals
} = require('./symbols.js')
const { exist, existReply, existRequest } = require('./decorate')
const { exist, existReply, existRequest } = require('./decorate.js')
const {
FST_ERR_PLUGIN_VERSION_MISMATCH,
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE
} = require('./errors')
const { FSTWRN002 } = require('./warnings.js')
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
} = require('./errors.js')
const rcRegex = /-(?:rc|pre|alpha).+$/u
function getMeta (fn) {
return fn[Symbol.for('plugin-meta')]
@@ -48,7 +50,7 @@ function getPluginName (func) {
function getFuncPreview (func) {
// takes the first two lines of the function if nothing else works
return func.toString().split('\n').slice(0, 2).map(s => s.trim()).join(' -- ')
return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')
}
function getDisplayName (fn) {
@@ -106,11 +108,11 @@ function _checkDecorators (that, instance, decorators, name) {
function checkVersion (fn) {
const meta = getMeta(fn)
if (!meta) return
if (meta?.fastify == null) return
const requiredVersion = meta.fastify
const fastifyRc = /-rc.+$/.test(this.version)
const fastifyRc = rcRegex.test(this.version)
if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
// A Fastify release candidate phase is taking place. In order to reduce
// the effort needed to test plugins with the RC, we allow plugins targeting
@@ -138,7 +140,7 @@ function registerPluginName (fn) {
function checkPluginHealthiness (fn, pluginName) {
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
FSTWRN002(pluginName || 'anonymous')
throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName)
}
}

23
backend/node_modules/fastify/lib/promise.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
'use strict'
const { kTestInternals } = require('./symbols')
function withResolvers () {
let res, rej
const promise = new Promise((resolve, reject) => {
res = resolve
rej = reject
})
return { promise, resolve: res, reject: rej }
}
module.exports = {
// TODO(20.x): remove when node@20 is not supported
withResolvers: typeof Promise.withResolvers === 'function'
? Promise.withResolvers.bind(Promise) // Promise.withResolvers must bind to itself
/* c8 ignore next */
: withResolvers, // Tested using the kTestInternals
[kTestInternals]: {
withResolvers
}
}

View File

@@ -1,11 +1,9 @@
'use strict'
const eos = require('node:stream').finished
const Readable = require('node:stream').Readable
const {
kFourOhFourContext,
kPublicRouteContext,
kReplyErrorHandlerCalled,
kReplyHijacked,
kReplyStartTime,
@@ -32,8 +30,8 @@ const {
preSerializationHookRunner
} = require('./hooks')
const internals = require('./handleRequest')[Symbol.for('internals')]
const loggerUtils = require('./logger')
const internals = require('./handle-request.js')[Symbol.for('internals')]
const loggerUtils = require('./logger-factory')
const now = loggerUtils.now
const { handleError } = require('./error-handler')
const { getSchemaSerializer } = require('./schemas')
@@ -46,16 +44,17 @@ const CONTENT_TYPE = {
const {
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
FST_ERR_REP_RESPONSE_BODY_CONSUMED,
FST_ERR_REP_READABLE_STREAM_LOCKED,
FST_ERR_REP_ALREADY_SENT,
FST_ERR_REP_SENT_VALUE,
FST_ERR_SEND_INSIDE_ONERR,
FST_ERR_BAD_STATUS_CODE,
FST_ERR_BAD_TRAILER_NAME,
FST_ERR_BAD_TRAILER_VALUE,
FST_ERR_MISSING_SERIALIZATION_FN,
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN,
FST_ERR_DEC_UNDECLARED
} = require('./errors')
const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020, FSTDEP021 } = require('./warnings')
const decorators = require('./decorate')
const toString = Object.prototype.toString
@@ -80,14 +79,6 @@ Object.defineProperties(Reply.prototype, {
return this.request[kRouteContext]
}
},
// TODO: remove once v5 is done
// Is temporary to avoid constant conflicts between `next` and `main`
context: {
get () {
FSTDEP019()
return this.request[kRouteContext]
}
},
elapsedTime: {
get () {
if (this[kReplyStartTime] === undefined) {
@@ -106,20 +97,6 @@ Object.defineProperties(Reply.prototype, {
get () {
// We are checking whether reply was hijacked or the response has ended.
return (this[kReplyHijacked] || this.raw.writableEnded) === true
},
set (value) {
FSTDEP010()
if (value !== true) {
throw new FST_ERR_REP_SENT_VALUE()
}
// We throw only if sent was overwritten from Fastify
if (this.sent && this[kReplyHijacked]) {
throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method)
}
this[kReplyHijacked] = true
}
},
statusCode: {
@@ -130,29 +107,34 @@ Object.defineProperties(Reply.prototype, {
this.code(value)
}
},
[kPublicRouteContext]: {
routeOptions: {
get () {
return this.request[kPublicRouteContext]
return this.request.routeOptions
}
}
})
Reply.prototype.writeEarlyHints = function (hints, callback) {
this.raw.writeEarlyHints(hints, callback)
return this
}
Reply.prototype.hijack = function () {
this[kReplyHijacked] = true
return this
}
Reply.prototype.send = function (payload) {
if (this[kReplyIsRunningOnErrorHook] === true) {
if (this[kReplyIsRunningOnErrorHook]) {
throw new FST_ERR_SEND_INSIDE_ONERR()
}
if (this.sent) {
if (this.sent === true) {
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
return this
}
if (payload instanceof Error || this[kReplyIsError] === true) {
if (this[kReplyIsError] || payload instanceof Error) {
this[kReplyIsError] = false
onErrorHook(this, payload, onSendHook)
return this
@@ -179,16 +161,18 @@ Reply.prototype.send = function (payload) {
return this
}
if (payload?.buffer instanceof ArrayBuffer) {
if (hasContentType === false) {
if (payload.buffer instanceof ArrayBuffer) {
if (!hasContentType) {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
}
const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
const payloadToSend = Buffer.isBuffer(payload)
? payload
: Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
onSendHook(this, payloadToSend)
return this
}
if (hasContentType === false && typeof payload === 'string') {
if (!hasContentType && typeof payload === 'string') {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN
onSendHook(this, payload)
return this
@@ -199,26 +183,24 @@ Reply.prototype.send = function (payload) {
if (typeof payload !== 'string') {
preSerializationHook(this, payload)
return this
} else {
payload = this[kReplySerializer](payload)
}
payload = this[kReplySerializer](payload)
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
} else if (hasContentType === false || contentType.indexOf('json') > -1) {
if (hasContentType === false) {
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
} else if (!hasContentType || contentType.indexOf('json') !== -1) {
if (!hasContentType) {
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
} else {
} else if (contentType.indexOf('charset') === -1) {
// If user doesn't set charset, we will set charset to utf-8
if (contentType.indexOf('charset') === -1) {
const customContentType = contentType.trim()
if (customContentType.endsWith(';')) {
// custom content-type is ended with ';'
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
} else {
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
}
const customContentType = contentType.trim()
if (customContentType.endsWith(';')) {
// custom content-type is ended with ';'
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
} else {
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
}
}
if (typeof payload !== 'string') {
preSerializationHook(this, payload)
return this
@@ -232,12 +214,8 @@ Reply.prototype.send = function (payload) {
Reply.prototype.getHeader = function (key) {
key = key.toLowerCase()
const res = this.raw
let value = this[kReplyHeaders][key]
if (value === undefined && res.hasHeader(key)) {
value = res.getHeader(key)
}
return value
const value = this[kReplyHeaders][key]
return value !== undefined ? value : this.raw.getHeader(key)
}
Reply.prototype.getHeaders = function () {
@@ -283,8 +261,7 @@ Reply.prototype.header = function (key, value = '') {
Reply.prototype.headers = function (headers) {
const keys = Object.keys(headers)
/* eslint-disable no-var */
for (var i = 0; i !== keys.length; ++i) {
for (let i = 0; i !== keys.length; ++i) {
const key = keys[i]
this.header(key, headers[key])
}
@@ -333,12 +310,12 @@ Reply.prototype.removeTrailer = function (key) {
}
Reply.prototype.code = function (code) {
const intValue = Number(code)
if (isNaN(intValue) || intValue < 100 || intValue > 599) {
const statusCode = +code
if (!(statusCode >= 100 && statusCode <= 599)) {
throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
}
this.raw.statusCode = intValue
this.raw.statusCode = statusCode
this[kReplyHasStatusCode] = true
return this
}
@@ -371,13 +348,13 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
}
const serializerCompiler = this[kRouteContext].serializerCompiler ||
this.server[kSchemaController].serializerCompiler ||
(
// We compile the schemas if no custom serializerCompiler is provided
// nor set
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
this.server[kSchemaController].serializerCompiler
)
this.server[kSchemaController].serializerCompiler ||
(
// We compile the schemas if no custom serializerCompiler is provided
// nor set
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
this.server[kSchemaController].serializerCompiler
)
const serializeFn = serializerCompiler({
schema,
@@ -458,13 +435,6 @@ Reply.prototype.type = function (type) {
}
Reply.prototype.redirect = function (url, code) {
if (typeof url === 'number') {
FSTDEP021()
const temp = code
code = url
url = temp
}
if (!code) {
code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
}
@@ -477,13 +447,6 @@ Reply.prototype.callNotFound = function () {
return this
}
// TODO: should be removed in fastify@5
Reply.prototype.getResponseTime = function () {
FSTDEP020()
return this.elapsedTime
}
// Make reply a thenable, so it could be used with async/await.
// See
// - https://github.com/fastify/fastify/issues/1864 for the discussions
@@ -509,6 +472,19 @@ Reply.prototype.then = function (fulfilled, rejected) {
})
}
Reply.prototype.getDecorator = function (name) {
if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'reply')
}
const decorator = this[name]
if (typeof decorator === 'function') {
return decorator.bind(this)
}
return decorator
}
function preSerializationHook (reply, payload) {
if (reply[kRouteContext].preSerialization !== null) {
preSerializationHookRunner(
@@ -519,11 +495,11 @@ function preSerializationHook (reply, payload) {
preSerializationHookEnd
)
} else {
preSerializationHookEnd(null, reply.request, reply, payload)
preSerializationHookEnd(null, undefined, reply, payload)
}
}
function preSerializationHookEnd (err, request, reply, payload) {
function preSerializationHookEnd (err, _request, reply, payload) {
if (err != null) {
onErrorHook(reply, err)
return
@@ -679,9 +655,9 @@ function onSendEnd (reply, payload) {
if (reply[kReplyTrailers] === null) {
const contentLength = reply[kReplyHeaders]['content-length']
if (!contentLength ||
(req.raw.method !== 'HEAD' &&
Number(contentLength) !== Buffer.byteLength(payload)
)
(req.raw.method !== 'HEAD' &&
Number(contentLength) !== Buffer.byteLength(payload)
)
) {
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
}
@@ -705,8 +681,62 @@ function logStreamError (logger, err, res) {
}
function sendWebStream (payload, res, reply) {
const nodeStream = Readable.fromWeb(payload)
sendStream(nodeStream, res, reply)
if (payload.locked) {
throw new FST_ERR_REP_READABLE_STREAM_LOCKED()
}
let sourceOpen = true
let errorLogged = false
const reader = payload.getReader()
eos(res, function (err) {
if (sourceOpen) {
if (err != null && res.headersSent && !errorLogged) {
errorLogged = true
logStreamError(reply.log, err, res)
}
reader.cancel().catch(noop)
}
})
if (!res.headersSent) {
for (const key in reply[kReplyHeaders]) {
res.setHeader(key, reply[kReplyHeaders][key])
}
} else {
reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode')
}
function onRead (result) {
if (result.done) {
sourceOpen = false
sendTrailer(null, res, reply)
return
}
/* c8 ignore next 5 - race condition: eos handler typically fires first */
if (res.destroyed) {
sourceOpen = false
reader.cancel().catch(noop)
return
}
res.write(result.value)
reader.read().then(onRead, onReadError)
}
function onReadError (err) {
sourceOpen = false
if (res.headersSent || reply.request.raw.aborted === true) {
if (!errorLogged) {
errorLogged = true
logStreamError(reply.log, err, reply)
}
res.destroy()
} else {
onErrorHook(reply, err)
}
}
reader.read().then(onRead, onReadError)
}
function sendStream (payload, res, reply) {
@@ -810,10 +840,6 @@ function sendTrailer (payload, res, reply) {
const result = reply[kReplyTrailers][trailerName](reply, payload, cb)
if (typeof result === 'object' && typeof result.then === 'function') {
result.then((v) => cb(null, v), cb)
} else if (result !== null && result !== undefined) {
// TODO: should be removed in fastify@5
FSTDEP013()
cb(null, result)
}
}
@@ -906,10 +932,9 @@ function buildReply (R) {
this[kReplyEndTime] = undefined
this.log = log
// eslint-disable-next-line no-var
var prop
// eslint-disable-next-line no-var
for (var i = 0; i < props.length; i++) {
let prop
for (let i = 0; i < props.length; i++) {
prop = props[i]
this[prop.key] = prop.value
}

View File

@@ -1,15 +1,6 @@
'use strict'
const proxyAddr = require('proxy-addr')
const semver = require('semver')
const {
FSTDEP005,
FSTDEP012,
FSTDEP015,
FSTDEP016,
FSTDEP017,
FSTDEP018
} = require('./warnings')
const proxyAddr = require('@fastify/proxy-addr')
const {
kHasBeenDecorated,
kSchemaBody,
@@ -20,10 +11,10 @@ const {
kOptions,
kRequestCacheValidateFns,
kRouteContext,
kPublicRouteContext,
kRequestOriginalUrl
} = require('./symbols')
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
const decorators = require('./decorate')
const HTTP_PART_SYMBOL_MAP = {
body: kSchemaBody,
@@ -49,8 +40,8 @@ function getTrustProxyFn (tp) {
return tp
}
if (tp === true) {
// Support plain true/false
return function () { return true }
// Support trusting everything
return null
}
if (typeof tp === 'number') {
// Support trusting hop count
@@ -83,10 +74,8 @@ function buildRegularRequest (R) {
this.log = log
this.body = undefined
// eslint-disable-next-line no-var
var prop
// eslint-disable-next-line no-var
for (var i = 0; i < props.length; i++) {
let prop
for (let i = 0; i < props.length; i++) {
prop = props[i]
this[prop.key] = prop.value
}
@@ -115,7 +104,8 @@ function buildRequestWithTrustProxy (R, trustProxy) {
Object.defineProperties(_Request.prototype, {
ip: {
get () {
return proxyAddr(this.raw, proxyFn)
const addrs = proxyAddr.all(this.raw, proxyFn)
return addrs[addrs.length - 1]
}
},
ips: {
@@ -123,12 +113,18 @@ function buildRequestWithTrustProxy (R, trustProxy) {
return proxyAddr.all(this.raw, proxyFn)
}
},
hostname: {
host: {
get () {
if (this.ip !== undefined && this.headers['x-forwarded-host']) {
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
}
return this.headers.host || this.headers[':authority']
/**
* The last fallback supports the following cases:
* 1. http.requireHostHeader === false
* 2. HTTP/1.0 without a Host Header
* 3. Headers schema that may remove the Host Header
*/
return this.headers.host ?? this.headers[':authority'] ?? ''
}
},
protocol: {
@@ -146,6 +142,12 @@ function buildRequestWithTrustProxy (R, trustProxy) {
return _Request
}
function assertsRequestDecoration (request, name) {
if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) {
throw new FST_ERR_DEC_UNDECLARED(name, 'request')
}
}
Object.defineProperties(Request.prototype, {
server: {
get () {
@@ -171,18 +173,6 @@ Object.defineProperties(Request.prototype, {
return this.raw.method
}
},
context: {
get () {
FSTDEP012()
return this[kRouteContext]
}
},
routerPath: {
get () {
FSTDEP017()
return this[kRouteContext].config?.url
}
},
routeOptions: {
get () {
const context = this[kRouteContext]
@@ -198,37 +188,12 @@ Object.defineProperties(Request.prototype, {
exposeHeadRoute: context.exposeHeadRoute,
prefixTrailingSlash: context.prefixTrailingSlash,
handler: context.handler,
config: context.config,
schema: context.schema,
version
}
Object.defineProperties(options, {
config: {
get: () => context.config
},
schema: {
get: () => context.schema
}
})
return Object.freeze(options)
}
},
routerMethod: {
get () {
FSTDEP018()
return this[kRouteContext].config?.method
}
},
routeConfig: {
get () {
FSTDEP016()
return this[kRouteContext][kPublicRouteContext]?.config
}
},
routeSchema: {
get () {
FSTDEP015()
return this[kRouteContext][kPublicRouteContext].schema
return options
}
},
is404: {
@@ -236,15 +201,6 @@ Object.defineProperties(Request.prototype, {
return this[kRouteContext].config?.url === undefined
}
},
connection: {
get () {
/* istanbul ignore next */
if (semver.gte(process.versions.node, '13.0.0')) {
FSTDEP005()
}
return this.raw.connection
}
},
socket: {
get () {
return this.raw.socket
@@ -257,9 +213,42 @@ Object.defineProperties(Request.prototype, {
}
}
},
host: {
get () {
/**
* The last fallback supports the following cases:
* 1. http.requireHostHeader === false
* 2. HTTP/1.0 without a Host Header
* 3. Headers schema that may remove the Host Header
*/
return this.raw.headers.host ?? this.raw.headers[':authority'] ?? ''
}
},
hostname: {
get () {
return this.raw.headers.host || this.raw.headers[':authority']
// Check for IPV6 Host
if (this.host[0] === '[') {
return this.host.slice(0, this.host.indexOf(']') + 1)
}
return this.host.split(':', 1)[0]
}
},
port: {
get () {
// first try taking port from host
const portFromHost = parseInt(this.host.split(':').slice(-1)[0])
if (!isNaN(portFromHost)) {
return portFromHost
}
// now fall back to port from host/:authority header
const host = (this.headers.host ?? this.headers[':authority'] ?? '')
const portFromHeader = parseInt(host.split(':').slice(-1)[0])
if (!isNaN(portFromHeader)) {
return portFromHeader
}
// fall back to null
return null
}
},
protocol: {
@@ -299,13 +288,13 @@ Object.defineProperties(Request.prototype, {
}
const validatorCompiler = this[kRouteContext].validatorCompiler ||
this.server[kSchemaController].validatorCompiler ||
(
// We compile the schemas if no custom validatorCompiler is provided
// nor set
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
this.server[kSchemaController].validatorCompiler
)
this.server[kSchemaController].validatorCompiler ||
(
// We compile the schemas if no custom validatorCompiler is provided
// nor set
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
this.server[kSchemaController].validatorCompiler
)
const validateFn = validatorCompiler({
schema,
@@ -342,8 +331,8 @@ Object.defineProperties(Request.prototype, {
// We cannot compile if the schema is missed
if (validate == null && (schema == null ||
typeof schema !== 'object' ||
Array.isArray(schema))
typeof schema !== 'object' ||
Array.isArray(schema))
) {
throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
}
@@ -359,6 +348,25 @@ Object.defineProperties(Request.prototype, {
return validate(input)
}
},
getDecorator: {
value: function (name) {
assertsRequestDecoration(this, name)
const decorator = this[name]
if (typeof decorator === 'function') {
return decorator.bind(this)
}
return decorator
}
},
setDecorator: {
value: function (name, value) {
assertsRequestDecoration(this, name)
this[name] = value
}
}
})

View File

@@ -2,16 +2,10 @@
const FindMyWay = require('find-my-way')
const Context = require('./context')
const handleRequest = require('./handleRequest')
const handleRequest = require('./handle-request.js')
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
const { supportedMethods } = require('./httpMethods')
const { normalizeSchema } = require('./schemas')
const { parseHeadOnSendHandlers } = require('./headRoute')
const {
FSTDEP007,
FSTDEP008,
FSTDEP014
} = require('./warnings')
const { parseHeadOnSendHandlers } = require('./head-route.js')
const {
compileSchemasForValidation,
@@ -21,7 +15,6 @@ const {
const {
FST_ERR_SCH_VALIDATION_BUILD,
FST_ERR_SCH_SERIALIZATION_BUILD,
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
FST_ERR_DUPLICATED_ROUTE,
FST_ERR_INVALID_URL,
FST_ERR_HOOK_INVALID_HANDLER,
@@ -38,6 +31,7 @@ const {
const {
kRoutePrefix,
kSupportedHTTPMethods,
kLogLevel,
kLogSerializers,
kHooks,
@@ -55,11 +49,26 @@ const {
kRouteContext
} = require('./symbols.js')
const { buildErrorHandler } = require('./error-handler')
const { createChildLogger } = require('./logger')
const { getGenReqId } = require('./reqIdGenFactory.js')
const { createChildLogger } = require('./logger-factory.js')
const { getGenReqId } = require('./req-id-gen-factory.js')
const { FSTDEP022 } = require('./warnings')
const routerKeys = [
'allowUnsafeRegex',
'buildPrettyMeta',
'caseSensitive',
'constraints',
'defaultRoute',
'ignoreDuplicateSlashes',
'ignoreTrailingSlash',
'maxParamLength',
'onBadUrl',
'querystringParser',
'useSemicolonDelimiter'
]
function buildRouting (options) {
const router = FindMyWay(options.config)
const router = FindMyWay(options)
let avvio
let fourOhFour
@@ -68,11 +77,11 @@ function buildRouting (options) {
let setupResponseListeners
let throwIfAlreadyStarted
let disableRequestLogging
let disableRequestLoggingFn
let ignoreTrailingSlash
let ignoreDuplicateSlashes
let return503OnClosing
let globalExposeHeadRoutes
let validateHTTPVersion
let keepAliveConnections
let closing = false
@@ -85,35 +94,26 @@ function buildRouting (options) {
setup (options, fastifyArgs) {
avvio = fastifyArgs.avvio
fourOhFour = fastifyArgs.fourOhFour
logger = fastifyArgs.logger
logger = options.logger
hasLogger = fastifyArgs.hasLogger
setupResponseListeners = fastifyArgs.setupResponseListeners
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
validateHTTPVersion = fastifyArgs.validateHTTPVersion
globalExposeHeadRoutes = options.exposeHeadRoutes
disableRequestLogging = options.disableRequestLogging
ignoreTrailingSlash = options.ignoreTrailingSlash
ignoreDuplicateSlashes = options.ignoreDuplicateSlashes
return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
if (typeof disableRequestLogging === 'function') {
disableRequestLoggingFn = options.disableRequestLogging
}
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
keepAliveConnections = fastifyArgs.keepAliveConnections
},
routing: router.lookup.bind(router), // router func to find the right handler to call
route, // configure a route in the fastify instance
hasRoute,
prepareRoute,
getDefaultRoute: function () {
FSTDEP014()
return router.defaultRoute
},
setDefaultRoute: function (defaultRoute) {
FSTDEP014()
if (typeof defaultRoute !== 'function') {
throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
}
router.defaultRoute = defaultRoute
},
routeHandler,
closeRoutes: () => { closing = true },
printRoutes: router.prettyPrint.bind(router),
@@ -169,10 +169,11 @@ function buildRouting (options) {
function hasRoute ({ options }) {
const normalizedMethod = options.method?.toUpperCase() ?? ''
return findRoute({
...options,
method: normalizedMethod
}) !== null
return router.hasRoute(
normalizedMethod,
options.url || '',
options.constraints
)
}
function findRoute (options) {
@@ -200,36 +201,13 @@ function buildRouting (options) {
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
*/
function route ({ options, isFastify }) {
throwIfAlreadyStarted('Cannot add route!')
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
const opts = { ...options }
const { exposeHeadRoute } = opts
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
const isGetRoute = opts.method === 'GET' ||
(Array.isArray(opts.method) && opts.method.includes('GET'))
const isHeadRoute = opts.method === 'HEAD' ||
(Array.isArray(opts.method) && opts.method.includes('HEAD'))
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
throwIfAlreadyStarted('Cannot add route!')
const path = opts.url || opts.path || ''
if (Array.isArray(opts.method)) {
// eslint-disable-next-line no-var
for (var i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod(opts.method[i])
validateSchemaBodyOption(opts.method[i], path, opts.schema)
}
} else {
opts.method = normalizeAndValidateMethod(opts.method)
validateSchemaBodyOption(opts.method, path, opts.schema)
}
if (!opts.handler) {
throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
}
@@ -240,6 +218,30 @@ function buildRouting (options) {
validateBodyLimitOption(opts.bodyLimit)
const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
let isGetRoute = false
let isHeadRoute = false
if (Array.isArray(opts.method)) {
for (let i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
isGetRoute = opts.method.includes('GET')
isHeadRoute = opts.method.includes('HEAD')
}
} else {
opts.method = normalizeAndValidateMethod.call(this, opts.method)
validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
isGetRoute = opts.method === 'GET'
isHeadRoute = opts.method === 'HEAD'
}
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
const prefix = this[kRoutePrefix]
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
@@ -347,19 +349,9 @@ function buildRouting (options) {
isFastify
})
if (opts.version) {
FSTDEP008()
constraints.version = opts.version
}
const headHandler = router.findRoute('HEAD', opts.url, constraints)
const hasHEADHandler = headHandler !== null
// remove the head route created by fastify
if (isHeadRoute && hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
router.off('HEAD', opts.url, constraints)
}
try {
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
} catch (error) {
@@ -377,13 +369,16 @@ function buildRouting (options) {
this.after((notHandledErr, done) => {
// Send context async
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
context.errorHandler = opts.errorHandler
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
: this[kErrorHandler]
context._parserOptions.limit = opts.bodyLimit || null
context.logLevel = opts.logLevel
context.logSerializers = opts.logSerializers
context.attachValidation = opts.attachValidation
context[kReplySerializerDefault] = this[kReplySerializerDefault]
context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
context.schemaErrorFormatter =
opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
// Run hooks and more
avvio.once('preReady', () => {
@@ -407,15 +402,24 @@ function buildRouting (options) {
fourOhFour.setContext(this, context)
if (opts.schema) {
context.schema = normalizeSchema(opts, context.schema, this.initialConfig)
context.schema = normalizeSchema(context.schema, this.initialConfig)
const schemaController = this[kSchemaController]
if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
const hasValidationSchema = opts.schema.body ||
opts.schema.headers ||
opts.schema.querystring ||
opts.schema.params
if (!opts.validatorCompiler && hasValidationSchema) {
schemaController.setupValidator(this[kOptions])
}
try {
const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
const isCustom = typeof opts?.validatorCompiler === 'function' ||
schemaController.isCustomValidatorCompiler
compileSchemasForValidation(
context,
opts.validatorCompiler || schemaController.validatorCompiler,
isCustom
)
} catch (error) {
throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
}
@@ -440,8 +444,6 @@ function buildRouting (options) {
if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) {
const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
} else if (hasHEADHandler && exposeHeadRoute) {
FSTDEP007()
}
}
}
@@ -458,20 +460,8 @@ function buildRouting (options) {
loggerOpts.serializers = context.logSerializers
}
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
childLogger[kDisableRequestLogging] = disableRequestLogging
// TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
if (!validateHTTPVersion(req.httpVersion)) {
childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version')
const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}'
const headers = {
'Content-Type': 'application/json',
'Content-Length': message.length
}
res.writeHead(505, headers)
res.end(message)
return
}
// Set initial value; will be re-evaluated after FastifyRequest is constructed if it's a function
childLogger[kDisableRequestLogging] = disableRequestLoggingFn ? false : disableRequestLogging
if (closing === true) {
/* istanbul ignore next mac, windows */
@@ -515,7 +505,15 @@ function buildRouting (options) {
const request = new context.Request(id, params, req, query, childLogger, context)
const reply = new context.Reply(res, request, childLogger)
if (disableRequestLogging === false) {
// Evaluate disableRequestLogging after FastifyRequest is constructed
// so the caller has access to decorations and customizations
const resolvedDisableRequestLogging = disableRequestLoggingFn
? disableRequestLoggingFn(request)
: disableRequestLogging
childLogger[kDisableRequestLogging] = resolvedDisableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
@@ -577,7 +575,8 @@ function normalizeAndValidateMethod (method) {
throw new FST_ERR_ROUTE_METHOD_INVALID()
}
method = method.toUpperCase()
if (supportedMethods.indexOf(method) === -1) {
if (!this[kSupportedHTTPMethods].bodyless.has(method) &&
!this[kSupportedHTTPMethods].bodywith.has(method)) {
throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
}
@@ -585,7 +584,7 @@ function normalizeAndValidateMethod (method) {
}
function validateSchemaBodyOption (method, path, schema) {
if ((method === 'GET' || method === 'HEAD') && schema && schema.body) {
if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) {
throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
}
}
@@ -608,12 +607,30 @@ function runPreParsing (err, request, reply) {
request[kRequestPayloadStream] = request.raw
if (request[kRouteContext].preParsing !== null) {
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest)
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server))
} else {
handleRequest(null, request, reply)
handleRequest.call(request.server, null, request, reply)
}
}
function buildRouterOptions (options, defaultOptions) {
const routerOptions = options.routerOptions || Object.create(null)
const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
if (usedDeprecatedOptions.length > 0) {
FSTDEP022(usedDeprecatedOptions.join(', '))
}
for (const key of routerKeys) {
if (!Object.hasOwn(routerOptions, key)) {
routerOptions[key] = options[key] ?? defaultOptions[key]
}
}
return routerOptions
}
/**
* Used within the route handler as a `net.Socket.close` event handler.
* The purpose is to remove a socket from the tracked sockets collection when
@@ -625,4 +642,4 @@ function removeTrackedSocket () {
function noop () { }
module.exports = { buildRouting, validateBodyLimitOption }
module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions }

View File

@@ -1,8 +1,6 @@
'use strict'
const { buildSchemas } = require('./schemas')
const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
const ValidatorSelector = require('@fastify/ajv-compiler')
/**
* Called at every fastify context that is being created.
@@ -21,9 +19,11 @@ function buildSchemaController (parentSchemaCtrl, opts) {
}, opts?.compilersFactory)
if (!compilersFactory.buildValidator) {
const ValidatorSelector = require('@fastify/ajv-compiler')
compilersFactory.buildValidator = ValidatorSelector()
}
if (!compilersFactory.buildSerializer) {
const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
compilersFactory.buildSerializer = SerializerSelector()
}

View File

@@ -4,9 +4,6 @@ const fastClone = require('rfdc')({ circles: false, proto: true })
const { kSchemaVisited, kSchemaResponse } = require('./symbols')
const kFluentSchema = Symbol.for('fluent-schema-object')
const {
FSTDEP022
} = require('./warnings')
const {
FST_ERR_SCH_MISSING_ID,
FST_ERR_SCH_ALREADY_PRESENT,
@@ -57,7 +54,7 @@ function isCustomSchemaPrototype (schema) {
return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
}
function normalizeSchema (opts, routeSchemas, serverOptions) {
function normalizeSchema (routeSchemas, serverOptions) {
if (routeSchemas[kSchemaVisited]) {
return routeSchemas
}
@@ -85,11 +82,9 @@ function normalizeSchema (opts, routeSchemas, serverOptions) {
if (!contentSchema) {
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)
}
routeSchemas.body.content[contentType].schema = getSchemaAnyway(opts.url, contentSchema, serverOptions.jsonShorthand)
}
continue
}
routeSchemas[key] = getSchemaAnyway(opts.url, schema, serverOptions.jsonShorthand)
}
}
@@ -102,25 +97,15 @@ function normalizeSchema (opts, routeSchemas, serverOptions) {
const contentProperty = routeSchemas.response[code].content
let hasContentMultipleContentTypes = false
if (contentProperty) {
const keys = Object.keys(contentProperty)
for (let i = 0; i < keys.length; i++) {
const mediaName = keys[i]
if (!contentProperty[mediaName].schema) {
if (keys.length === 1) { break }
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
}
routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(opts.url, contentProperty[mediaName].schema, serverOptions.jsonShorthand)
if (i === keys.length - 1) {
hasContentMultipleContentTypes = true
}
}
}
if (!hasContentMultipleContentTypes) {
routeSchemas.response[code] = getSchemaAnyway(opts.url, routeSchemas.response[code], serverOptions.jsonShorthand)
}
}
}
@@ -145,18 +130,6 @@ function generateFluentSchema (schema) {
}
}
function getSchemaAnyway (url, schema, jsonShorthand) {
if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
if (!schema.type && !schema.properties) {
FSTDEP022(url)
return {
type: 'object',
properties: schema
}
}
return schema
}
/**
* Search for the right JSON schema compiled function in the request context
* setup by the route configuration `schema.response`.
@@ -180,6 +153,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef[statusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[statusCode]['*/*']) {
return responseSchemaDef[statusCode]['*/*']
}
return false
}
return responseSchemaDef[statusCode]
@@ -192,6 +170,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef[fallbackStatusCode][mediaName]
}
// fallback to match all media-type
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
return responseSchemaDef[fallbackStatusCode]['*/*']
}
return false
}
@@ -204,6 +187,11 @@ function getSchemaSerializer (context, statusCode, contentType) {
return responseSchemaDef.default[mediaName]
}
// fallback to match all media-type
if (responseSchemaDef.default['*/*']) {
return responseSchemaDef.default['*/*']
}
return false
}

View File

@@ -2,20 +2,23 @@
const http = require('node:http')
const https = require('node:https')
const http2 = require('node:http2')
const dns = require('node:dns')
const os = require('node:os')
const { FSTDEP011 } = require('./warnings')
const { kState, kOptions, kServerBindings } = require('./symbols')
const { kState, kOptions, kServerBindings, kHttp2ServerSessions } = require('./symbols')
const { FSTWRN003 } = require('./warnings')
const { onListenHookRunner } = require('./hooks')
const {
FST_ERR_HTTP2_INVALID_VERSION,
FST_ERR_REOPENED_CLOSE_SERVER,
FST_ERR_REOPENED_SERVER,
FST_ERR_LISTEN_OPTIONS_INVALID
FST_ERR_LISTEN_OPTIONS_INVALID,
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE
} = require('./errors')
const noopSet = require('./noop-set')
const PonyPromise = require('./promise')
module.exports.createServer = createServer
module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion
function defaultResolveServerListeningText (address) {
return `Server listening at ${address}`
@@ -25,27 +28,15 @@ function createServer (options, httpHandler) {
const server = getServerInstance(options, httpHandler)
// `this` is the Fastify object
function listen (listenOptions, ...args) {
let cb = args.slice(-1).pop()
// When the variadic signature deprecation is complete, the function
// declaration should become:
// function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined)
// Upon doing so, the `normalizeListenArgs` function is no longer needed,
// and all of this preamble to feed it correctly also no longer needed.
const firstArgType = Object.prototype.toString.call(arguments[0])
if (arguments.length === 0) {
listenOptions = normalizeListenArgs([])
} else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) {
FSTDEP011()
listenOptions = normalizeListenArgs(Array.from(arguments))
cb = listenOptions.cb
} else if (args.length > 1) {
// `.listen(obj, a, ..., n, callback )`
FSTDEP011()
// Deal with `.listen(port, host, backlog, [cb])`
const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost']
Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args]))
} else {
function listen (
listenOptions = { port: 0, host: 'localhost' },
cb = undefined
) {
if (typeof cb === 'function') {
if (cb.constructor.name === 'AsyncFunction') {
FSTWRN003('listen method')
}
listenOptions.cb = cb
}
if (listenOptions.signal) {
@@ -53,10 +44,14 @@ function createServer (options, httpHandler) {
throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal')
}
if (listenOptions.signal.aborted) {
this.close()
// copy the current signal state
this[kState].aborted = listenOptions.signal.aborted
if (this[kState].aborted) {
return this.close()
} else {
const onAborted = () => {
this[kState].aborted = true
this.close()
}
listenOptions.signal.addEventListener('abort', onAborted, { once: true })
@@ -72,7 +67,7 @@ function createServer (options, httpHandler) {
} else {
host = listenOptions.host
}
if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false ||
if (!Object.hasOwn(listenOptions, 'host') ||
listenOptions.host == null) {
listenOptions.host = host
}
@@ -110,27 +105,47 @@ function createServer (options, httpHandler) {
if (cb === undefined) {
const listening = listenPromise.call(this, server, listenOptions)
/* istanbul ignore else */
return listening.then(address => {
return new Promise((resolve, reject) => {
if (host === 'localhost') {
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
this[kState].listening = true
resolve(address)
onListenHookRunner(this)
})
} else {
const { promise, resolve } = PonyPromise.withResolvers()
if (host === 'localhost') {
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
this[kState].listening = true
resolve(address)
onListenHookRunner(this)
}
})
})
} else {
resolve(address)
onListenHookRunner(this)
}
return promise
})
}
this.ready(listenCallback.call(this, server, listenOptions))
}
return { server, listen }
const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function'
let forceCloseConnections = options.forceCloseConnections
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
} else if (typeof forceCloseConnections !== 'boolean') {
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
}
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
return {
server,
listen,
forceCloseConnections,
serverHasCloseAllConnections,
serverHasCloseHttp2Sessions,
keepAliveConnections
}
}
function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) {
@@ -139,7 +154,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
// let's check if we need to bind additional addresses
dns.lookup(listenOptions.host, { all: true }, (dnsErr, addresses) => {
if (dnsErr) {
if (dnsErr || this[kState].aborted) {
// not blocking the main server listening
// this.log.warn('dns.lookup error:', dnsErr)
onListen()
@@ -161,7 +176,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
cb: (_ignoreErr) => {
bound++
/* istanbul ignore next: the else won't be taken unless listening fails */
if (!_ignoreErr) {
this[kServerBindings].push(secondaryServer)
}
@@ -175,20 +189,18 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
const secondaryServer = getServerInstance(serverOpts, httpHandler)
const closeSecondary = () => {
// To avoid fall into situations where the close of the
// To avoid falling into situations where the close of the
// secondary server is triggered before the preClose hook
// is done running, we better wait until the main server
// is closed.
// is done running, we better wait until the main server is closed.
// No new TCP connections are accepted
// We swallow any error from the secondary
// server
// We swallow any error from the secondary server
secondaryServer.close(() => {})
if (serverOpts.forceCloseConnections === 'idle') {
// Not needed in Node 19
secondaryServer.closeIdleConnections()
} else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) {
if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) {
secondaryServer.closeAllConnections()
}
if (typeof secondaryServer.closeHttp2Sessions === 'function') {
secondaryServer.closeHttp2Sessions()
}
}
secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
@@ -210,7 +222,6 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
// to the secondary servers. It is valid only when the user is
// listening on localhost
const originUnref = mainServer.unref
/* c8 ignore next 4 */
mainServer.unref = function () {
originUnref.call(mainServer)
mainServer.emit('unref')
@@ -223,7 +234,11 @@ function listenCallback (server, listenOptions) {
server.removeListener('error', wrap)
server.removeListener('listening', wrap)
if (!err) {
const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)
const address = logServerAddress.call(
this,
server,
listenOptions.listenTextResolver || defaultResolveServerListeningText
)
listenOptions.cb(null, address)
} else {
this[kState].listening = false
@@ -236,7 +251,8 @@ function listenCallback (server, listenOptions) {
if (this[kState].listening && this[kState].closing) {
return listenOptions.cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null)
} else if (this[kState].listening) {
}
if (this[kState].listening) {
return listenOptions.cb(new FST_ERR_REOPENED_SERVER(), null)
}
@@ -252,196 +268,174 @@ function listenCallback (server, listenOptions) {
function listenPromise (server, listenOptions) {
if (this[kState].listening && this[kState].closing) {
return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER())
} else if (this[kState].listening) {
}
if (this[kState].listening) {
return Promise.reject(new FST_ERR_REOPENED_SERVER())
}
return this.ready().then(() => {
let errEventHandler
let listeningEventHandler
// skip listen when aborted during ready
if (this[kState].aborted) return
const { promise, resolve, reject } = PonyPromise.withResolvers()
const errEventHandler = (err) => {
cleanup()
this[kState].listening = false
reject(err)
}
const listeningEventHandler = () => {
cleanup()
this[kState].listening = true
resolve(logServerAddress.call(
this,
server,
listenOptions.listenTextResolver || defaultResolveServerListeningText
))
}
function cleanup () {
server.removeListener('error', errEventHandler)
server.removeListener('listening', listeningEventHandler)
}
const errEvent = new Promise((resolve, reject) => {
errEventHandler = (err) => {
cleanup()
this[kState].listening = false
reject(err)
}
server.once('error', errEventHandler)
})
const listeningEvent = new Promise((resolve, reject) => {
listeningEventHandler = () => {
cleanup()
this[kState].listening = true
resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText))
}
server.once('listening', listeningEventHandler)
})
server.once('error', errEventHandler)
server.once('listening', listeningEventHandler)
server.listen(listenOptions)
return Promise.race([
errEvent, // e.g invalid port range error is always emitted before the server listening
listeningEvent
])
return promise
})
}
/**
* Creates a function that, based upon initial configuration, will
* verify that every incoming request conforms to allowed
* HTTP versions for the Fastify instance, e.g. a Fastify HTTP/1.1
* server will not serve HTTP/2 requests upon the result of the
* verification function.
*
* @param {object} options fastify option
* @param {function} [options.serverFactory] If present, the
* validator function will skip all checks.
* @param {boolean} [options.http2 = false] If true, the validator
* function will allow HTTP/2 requests.
* @param {object} [options.https = null] https server options
* @param {boolean} [options.https.allowHTTP1] If true and use
* with options.http2 the validator function will allow HTTP/1
* request to http2 server.
*
* @returns {function} HTTP version validator function.
*/
function compileValidateHTTPVersion (options) {
let bypass = false
// key-value map to store valid http version
const map = new Map()
if (options.serverFactory) {
// When serverFactory is passed, we cannot identify how to check http version reliably
// So, we should skip the http version check
bypass = true
}
if (options.http2) {
// HTTP2 must serve HTTP/2.0
map.set('2.0', true)
if (options.https && options.https.allowHTTP1 === true) {
// HTTP2 with HTTPS.allowHTTP1 allow fallback to HTTP/1.1 and HTTP/1.0
map.set('1.1', true)
map.set('1.0', true)
}
} else {
// HTTP must server HTTP/1.1 and HTTP/1.0
map.set('1.1', true)
map.set('1.0', true)
}
// The compiled function here placed in one of the hottest path inside fastify
// the implementation here must be as performant as possible
return function validateHTTPVersion (httpVersion) {
// `bypass` skip the check when custom server factory provided
// `httpVersion in obj` check for the valid http version we should support
return bypass || map.has(httpVersion)
}
}
function getServerInstance (options, httpHandler) {
let server = null
// node@20 do not accepts options as boolean
// we need to provide proper https option
const httpsOptions = options.https === true ? {} : options.https
if (options.serverFactory) {
server = options.serverFactory(httpHandler, options)
} else if (options.http2) {
if (typeof httpsOptions === 'object') {
server = http2().createSecureServer(httpsOptions, httpHandler)
} else {
server = http2().createServer(httpHandler)
}
server.on('session', sessionTimeout(options.http2SessionTimeout))
} else {
// this is http1
if (httpsOptions) {
server = https.createServer(httpsOptions, httpHandler)
} else {
server = http.createServer(options.http, httpHandler)
}
server.keepAliveTimeout = options.keepAliveTimeout
server.requestTimeout = options.requestTimeout
// we treat zero as null
// and null is the default setting from nodejs
// so we do not pass the option to server
if (options.maxRequestsPerSocket > 0) {
server.maxRequestsPerSocket = options.maxRequestsPerSocket
}
// User provided server instance
return options.serverFactory(httpHandler, options)
}
if (!options.serverFactory) {
// We have accepted true as a valid way to init https but node requires an options obj
const httpsOptions = options.https === true ? {} : options.https
if (options.http2) {
const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler)
server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, () => {
session.close()
}))
// This is only needed for Node.js versions < 24.0.0 since Node.js added native
// closeAllSessions() on server.close() support for HTTP/2 servers in v24.0.0
if (options.forceCloseConnections === true) {
server.closeHttp2Sessions = createCloseHttp2SessionsByHttp2Server(server)
}
server.setTimeout(options.connectionTimeout)
return server
}
// HTTP1 server instance
const server = httpsOptions
? https.createServer(httpsOptions, httpHandler)
: http.createServer(options.http, httpHandler)
server.keepAliveTimeout = options.keepAliveTimeout
server.requestTimeout = options.requestTimeout
server.setTimeout(options.connectionTimeout)
// We treat zero as null(node default) so we do not pass zero to the server instance
if (options.maxRequestsPerSocket > 0) {
server.maxRequestsPerSocket = options.maxRequestsPerSocket
}
return server
}
function normalizeListenArgs (args) {
if (args.length === 0) {
return { port: 0, host: 'localhost' }
/**
* Inspects the provided `server.address` object and returns a
* normalized list of IP address strings. Normalization in this
* case refers to mapping wildcard `0.0.0.0` to the list of IP
* addresses the wildcard refers to.
*
* @see https://nodejs.org/docs/latest/api/net.html#serveraddress
*
* @param {object} A server address object as described in the
* linked docs.
*
* @returns {string[]}
*/
function getAddresses (address) {
if (address.address === '0.0.0.0') {
return Object.values(os.networkInterfaces()).flatMap((iface) => {
return iface.filter((iface) => iface.family === 'IPv4')
}).sort((iface) => {
/* c8 ignore next 2 */
// Order the interfaces so that internal ones come first
return iface.internal ? -1 : 1
}).map((iface) => { return iface.address })
}
const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined
const options = { cb }
const firstArg = args[0]
const argsLength = args.length
const lastArg = args[argsLength - 1]
if (typeof firstArg === 'string' && isNaN(firstArg)) {
/* Deal with listen (pipe[, backlog]) */
options.path = firstArg
options.backlog = argsLength > 1 ? lastArg : undefined
} else {
/* Deal with listen ([port[, host[, backlog]]]) */
options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg)
// This will listen to what localhost is.
// It can be 127.0.0.1 or ::1, depending on the operating system.
// Fixes https://github.com/fastify/fastify/issues/1022.
options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost'
options.backlog = argsLength >= 3 ? args[2] : undefined
}
return options
}
function normalizePort (firstArg) {
const port = Number(firstArg)
return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0
return [address.address]
}
function logServerAddress (server, listenTextResolver) {
let address = server.address()
const isUnixSocket = typeof address === 'string'
/* istanbul ignore next */
let addresses
const isUnixSocket = typeof server.address() === 'string'
if (!isUnixSocket) {
if (address.address.indexOf(':') === -1) {
address = address.address + ':' + address.port
if (server.address().address.indexOf(':') === -1) {
// IPv4
addresses = getAddresses(server.address()).map((address) => address + ':' + server.address().port)
} else {
address = '[' + address.address + ']:' + address.port
// IPv6
addresses = ['[' + server.address().address + ']:' + server.address().port]
}
addresses = addresses.map((address) => ('http' + (this[kOptions].https ? 's' : '') + '://') + address)
} else {
addresses = [server.address()]
}
for (const address of addresses) {
this.log.info(listenTextResolver(address))
}
return addresses[0]
}
/**
* @param {http2.Http2Server} http2Server
* @returns {() => void}
*/
function createCloseHttp2SessionsByHttp2Server (http2Server) {
/**
* @type {Set<http2.Http2Session>}
*/
http2Server[kHttp2ServerSessions] = new Set()
http2Server.on('session', function (session) {
session.once('connect', function () {
http2Server[kHttp2ServerSessions].add(session)
})
session.once('close', function () {
http2Server[kHttp2ServerSessions].delete(session)
})
session.once('frameError', function (type, code, streamId) {
if (streamId === 0) {
// The stream ID is 0, which means that the error is related to the session itself.
// If the event is not associated with a stream, the Http2Session will be shut down immediately
http2Server[kHttp2ServerSessions].delete(session)
}
})
session.once('goaway', function () {
// The Http2Session instance will be shut down automatically when the 'goaway' event is emitted.
http2Server[kHttp2ServerSessions].delete(session)
})
})
return function closeHttp2Sessions () {
if (http2Server[kHttp2ServerSessions].size === 0) {
return
}
for (const session of http2Server[kHttp2ServerSessions]) {
session.close()
}
}
/* istanbul ignore next */
address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address
const serverListeningText = listenTextResolver(address)
this.log.info(serverListeningText)
return address
}
function http2 () {
try {
return require('node:http2')
} catch (err) {
throw new FST_ERR_HTTP2_INVALID_VERSION()
}
}
function sessionTimeout (timeout) {
return function (session) {
session.setTimeout(timeout, close)
}
}
function close () {
this.close()
}

View File

@@ -5,6 +5,7 @@ const keys = {
kChildren: Symbol('fastify.children'),
kServerBindings: Symbol('fastify.serverBindings'),
kBodyLimit: Symbol('fastify.bodyLimit'),
kSupportedHTTPMethods: Symbol('fastify.acceptedHTTPMethods'),
kRoutePrefix: Symbol('fastify.routePrefix'),
kLogLevel: Symbol('fastify.logLevel'),
kLogSerializers: Symbol('fastify.logSerializers'),
@@ -15,8 +16,8 @@ const keys = {
kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
kPluginNameChain: Symbol('fastify.pluginNameChain'),
kRouteContext: Symbol('fastify.context'),
kPublicRouteContext: Symbol('fastify.routeOptions'),
kGenReqId: Symbol('fastify.genReqId'),
kHttp2ServerSessions: Symbol('fastify.http2ServerSessions'),
// Schema
kSchemaController: Symbol('fastify.schemaController'),
kSchemaHeaders: Symbol('headers-schema'),
@@ -56,6 +57,7 @@ const keys = {
// This symbol is only meant to be used for fastify tests and should not be used for any other purpose
kTestInternals: Symbol('fastify.testInternals'),
kErrorHandler: Symbol('fastify.errorHandler'),
kErrorHandlerAlreadySet: Symbol('fastify.errorHandlerAlreadySet'),
kChildLoggerFactory: Symbol('fastify.childLoggerFactory'),
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),

View File

@@ -7,7 +7,7 @@ const {
kSchemaBody: bodySchema,
kSchemaResponse: responseSchema
} = require('./symbols')
const scChecker = /^[1-5]{1}[0-9]{2}$|^[1-5]xx$|^default$/
const scChecker = /^[1-5](?:\d{2}|xx)$|^default$/
const {
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX
@@ -24,7 +24,7 @@ function compileSchemasForSerialization (context, compile) {
.reduce(function (acc, statusCode) {
const schema = context.schema.response[statusCode]
statusCode = statusCode.toLowerCase()
if (!scChecker.exec(statusCode)) {
if (!scChecker.test(statusCode)) {
throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX()
}
@@ -82,7 +82,7 @@ function compileSchemasForValidation (context, compile, isCustom) {
})
}
context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'headers')) {
} else if (Object.hasOwn(schema, 'headers')) {
FSTWRN001('headers', method, url)
}
@@ -98,28 +98,36 @@ function compileSchemasForValidation (context, compile, isCustom) {
} else {
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
}
} else if (Object.prototype.hasOwnProperty.call(schema, 'body')) {
} else if (Object.hasOwn(schema, 'body')) {
FSTWRN001('body', method, url)
}
if (schema.querystring) {
context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'querystring')) {
} else if (Object.hasOwn(schema, 'querystring')) {
FSTWRN001('querystring', method, url)
}
if (schema.params) {
context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' })
} else if (Object.prototype.hasOwnProperty.call(schema, 'params')) {
} else if (Object.hasOwn(schema, 'params')) {
FSTWRN001('params', method, url)
}
}
function validateParam (validatorFunction, request, paramName) {
const isUndefined = request[paramName] === undefined
const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName])
let ret
if (ret?.then) {
try {
ret = validatorFunction?.(isUndefined ? null : request[paramName])
} catch (err) {
// If validator throws synchronously, ensure it propagates as an internal error
err.statusCode = 500
return err
}
if (ret && typeof ret.then === 'function') {
return ret
.then((res) => { return answer(res) })
.catch(err => { return err }) // return as simple error (not throw)

View File

@@ -1,96 +1,17 @@
'use strict'
const { createDeprecation, createWarning } = require('process-warning')
const { createWarning } = require('process-warning')
const FSTDEP005 = createDeprecation({
code: 'FSTDEP005',
message: 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.'
})
const FSTDEP006 = createDeprecation({
code: 'FSTDEP006',
message: 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s'
})
const FSTDEP007 = createDeprecation({
code: 'FSTDEP007',
message: 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.'
})
const FSTDEP008 = createDeprecation({
code: 'FSTDEP008',
message: 'You are using route constraints via the route { version: "..." } option, use { constraints: { version: "..." } } option instead.'
})
const FSTDEP009 = createDeprecation({
code: 'FSTDEP009',
message: 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.'
})
const FSTDEP010 = createDeprecation({
code: 'FSTDEP010',
message: 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.'
})
const FSTDEP011 = createDeprecation({
code: 'FSTDEP011',
message: 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.'
})
const FSTDEP012 = createDeprecation({
code: 'FSTDEP012',
message: 'request.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "request.context" will be removed in `fastify@5`.'
})
const FSTDEP013 = createDeprecation({
code: 'FSTDEP013',
message: 'Direct return of "trailers" function is deprecated. Please use "callback" or "async-await" for return value. The support of direct return will removed in `fastify@5`.'
})
const FSTDEP014 = createDeprecation({
code: 'FSTDEP014',
message: 'You are trying to set/access the default route. This property is deprecated. Please, use setNotFoundHandler if you want to custom a 404 handler or the wildcard (*) to match all routes.'
})
const FSTDEP015 = createDeprecation({
code: 'FSTDEP015',
message: 'You are accessing the deprecated "request.routeSchema" property. Use "request.routeOptions.schema" instead. Property "req.routeSchema" will be removed in `fastify@5`.'
})
const FSTDEP016 = createDeprecation({
code: 'FSTDEP016',
message: 'You are accessing the deprecated "request.routeConfig" property. Use "request.routeOptions.config" instead. Property "req.routeConfig" will be removed in `fastify@5`.'
})
const FSTDEP017 = createDeprecation({
code: 'FSTDEP017',
message: 'You are accessing the deprecated "request.routerPath" property. Use "request.routeOptions.url" instead. Property "req.routerPath" will be removed in `fastify@5`.'
})
const FSTDEP018 = createDeprecation({
code: 'FSTDEP018',
message: 'You are accessing the deprecated "request.routerMethod" property. Use "request.routeOptions.method" instead. Property "req.routerMethod" will be removed in `fastify@5`.'
})
const FSTDEP019 = createDeprecation({
code: 'FSTDEP019',
message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.'
})
const FSTDEP020 = createDeprecation({
code: 'FSTDEP020',
message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.'
})
const FSTDEP021 = createDeprecation({
code: 'FSTDEP021',
message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'
})
const FSTDEP022 = createDeprecation({
code: 'FSTDEP021',
message: 'You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5`'
})
/**
* Deprecation codes:
* - FSTWRN001
* - FSTSEC001
* - FSTDEP022
*
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused.
* - FSTDEP022 is used by v5 and MUST NOT be reused.
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused.
*/
const FSTWRN001 = createWarning({
name: 'FastifyWarning',
@@ -99,32 +20,38 @@ const FSTWRN001 = createWarning({
unlimited: true
})
const FSTWRN002 = createWarning({
const FSTWRN003 = createWarning({
name: 'FastifyWarning',
code: 'FSTWRN002',
message: 'The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`',
code: 'FSTWRN003',
message: 'The %s mixes async and callback styles that may lead to unhandled rejections. Please use only one of them.',
unlimited: true
})
const FSTWRN004 = createWarning({
name: 'FastifyWarning',
code: 'FSTWRN004',
message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs.',
unlimited: true
})
const FSTSEC001 = createWarning({
name: 'FastifySecurity',
code: 'FSTSEC001',
message: 'You are using /%s/ Content-Type which may be vulnerable to CORS attack. Please make sure your RegExp start with "^" or include ";?" to proper detection of the essence MIME type.',
unlimited: true
})
const FSTDEP022 = createWarning({
name: 'FastifyWarning',
code: 'FSTDEP022',
message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.',
unlimited: true
})
module.exports = {
FSTDEP005,
FSTDEP006,
FSTDEP007,
FSTDEP008,
FSTDEP009,
FSTDEP010,
FSTDEP011,
FSTDEP012,
FSTDEP013,
FSTDEP014,
FSTDEP015,
FSTDEP016,
FSTDEP017,
FSTDEP018,
FSTDEP019,
FSTDEP020,
FSTDEP021,
FSTDEP022,
FSTWRN001,
FSTWRN002
FSTWRN003,
FSTWRN004,
FSTSEC001,
FSTDEP022
}

84
backend/node_modules/fastify/lib/wrap-thenable.js generated vendored Normal file
View File

@@ -0,0 +1,84 @@
'use strict'
const {
kReplyIsError,
kReplyHijacked
} = require('./symbols')
const { setErrorStatusCode } = require('./error-status')
const diagnostics = require('node:diagnostics_channel')
const channels = diagnostics.tracingChannel('fastify.request.handler')
function wrapThenable (thenable, reply, store) {
if (store) store.async = true
thenable.then(function (payload) {
if (reply[kReplyHijacked] === true) {
return
}
if (store) {
channels.asyncStart.publish(store)
}
try {
// this is for async functions that are using reply.send directly
//
// since wrap-thenable will be called when using reply.send directly
// without actual return. the response can be sent already or
// the request may be terminated during the reply. in this situation,
// it require an extra checking of request.aborted to see whether
// the request is killed by client.
if (payload !== undefined || //
(reply.sent === false && //
reply.raw.headersSent === false &&
reply.request.raw.aborted === false &&
reply.request.socket &&
!reply.request.socket.destroyed
)
) {
// we use a try-catch internally to avoid adding a catch to another
// promise, increase promise perf by 10%
try {
reply.send(payload)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
}
}
} finally {
if (store) {
channels.asyncEnd.publish(store)
}
}
}, function (err) {
if (store) {
store.error = err
// Set status code before publishing so subscribers see the correct value
setErrorStatusCode(reply, err)
channels.error.publish(store) // note that error happens before asyncStart
channels.asyncStart.publish(store)
}
try {
if (reply.sent === true) {
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
return
}
reply[kReplyIsError] = true
reply.send(err)
// The following should not happen
/* c8 ignore next 3 */
} catch (err) {
// try-catch allow to re-throw error in error handler for async handler
reply.send(err)
} finally {
if (store) {
channels.asyncEnd.publish(store)
}
}
})
}
module.exports = wrapThenable

View File

@@ -1,50 +0,0 @@
'use strict'
const {
kReplyIsError,
kReplyHijacked
} = require('./symbols')
function wrapThenable (thenable, reply) {
thenable.then(function (payload) {
if (reply[kReplyHijacked] === true) {
return
}
// this is for async functions that are using reply.send directly
//
// since wrap-thenable will be called when using reply.send directly
// without actual return. the response can be sent already or
// the request may be terminated during the reply. in this situation,
// it require an extra checking of request.aborted to see whether
// the request is killed by client.
if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
// we use a try-catch internally to avoid adding a catch to another
// promise, increase promise perf by 10%
try {
reply.send(payload)
} catch (err) {
reply[kReplyIsError] = true
reply.send(err)
}
}
}, function (err) {
if (reply.sent === true) {
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
return
}
reply[kReplyIsError] = true
// try-catch allow to re-throw error in error handler for async handler
try {
reply.send(err)
// The following should not happen
/* c8 ignore next 3 */
} catch (err) {
reply.send(err)
}
})
}
module.exports = wrapThenable