fastify 4.2.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/build/build-validation.js +1 -1
  2. package/docs/Guides/Ecosystem.md +25 -0
  3. package/docs/Reference/HTTP2.md +1 -3
  4. package/docs/Reference/Hooks.md +4 -1
  5. package/docs/Reference/Logging.md +1 -1
  6. package/docs/Reference/Reply.md +177 -0
  7. package/docs/Reference/Request.md +171 -0
  8. package/docs/Reference/Routes.md +6 -3
  9. package/docs/Reference/Server.md +20 -7
  10. package/docs/Reference/Validation-and-Serialization.md +1 -1
  11. package/fastify.d.ts +2 -2
  12. package/fastify.js +5 -5
  13. package/lib/configValidator.js +68 -19
  14. package/lib/contentTypeParser.js +10 -2
  15. package/lib/context.js +10 -1
  16. package/lib/errors.js +8 -0
  17. package/lib/handleRequest.js +2 -2
  18. package/lib/httpMethods.js +22 -0
  19. package/lib/reply.js +80 -2
  20. package/lib/reqIdGenFactory.js +13 -2
  21. package/lib/request.js +97 -1
  22. package/lib/route.js +4 -4
  23. package/lib/server.js +1 -0
  24. package/lib/symbols.js +15 -9
  25. package/package.json +3 -9
  26. package/test/content-parser.test.js +15 -0
  27. package/test/copy.test.js +41 -0
  28. package/test/internals/all.test.js +8 -2
  29. package/test/internals/reply-serialize.test.js +583 -0
  30. package/test/internals/reply.test.js +4 -1
  31. package/test/internals/request-validate.test.js +1269 -0
  32. package/test/internals/request.test.js +11 -2
  33. package/test/lock.test.js +73 -0
  34. package/test/logger.test.js +108 -0
  35. package/test/mkcol.test.js +38 -0
  36. package/test/move.test.js +45 -0
  37. package/test/propfind.test.js +108 -0
  38. package/test/proppatch.test.js +78 -0
  39. package/test/request-error.test.js +44 -1
  40. package/test/schema-examples.test.js +54 -0
  41. package/test/search.test.js +100 -0
  42. package/test/trace.test.js +21 -0
  43. package/test/types/fastify.test-d.ts +1 -0
  44. package/test/types/hooks.test-d.ts +1 -2
  45. package/test/types/import.ts +1 -1
  46. package/test/types/instance.test-d.ts +1 -1
  47. package/test/types/logger.test-d.ts +4 -5
  48. package/test/types/reply.test-d.ts +44 -3
  49. package/test/types/request.test-d.ts +10 -29
  50. package/test/types/type-provider.test-d.ts +79 -7
  51. package/test/unlock.test.js +41 -0
  52. package/test/upgrade.test.js +53 -0
  53. package/types/hooks.d.ts +19 -39
  54. package/types/instance.d.ts +20 -40
  55. package/types/logger.d.ts +7 -4
  56. package/types/reply.d.ts +7 -1
  57. package/types/request.d.ts +16 -3
  58. package/types/route.d.ts +23 -29
  59. package/types/type-provider.d.ts +8 -20
  60. package/types/utils.d.ts +2 -1
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.2.1'
3
+ const VERSION = '4.5.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -34,7 +34,7 @@ const {
34
34
  const { createServer, compileValidateHTTPVersion } = require('./lib/server')
35
35
  const Reply = require('./lib/reply')
36
36
  const Request = require('./lib/request')
37
- const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
37
+ const { supportedMethods } = require('./lib/httpMethods')
38
38
  const decorator = require('./lib/decorate')
39
39
  const ContentTypeParser = require('./lib/contentTypeParser')
40
40
  const SchemaController = require('./lib/schema-controller')
@@ -98,8 +98,8 @@ function fastify (options) {
98
98
 
99
99
  validateBodyLimitOption(options.bodyLimit)
100
100
 
101
- const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader
102
- const genReqId = options.genReqId || reqIdGenFactory()
101
+ const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader)
102
+ const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
103
103
  const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
104
104
  const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
105
105
  const disableRequestLogging = options.disableRequestLogging || false
@@ -621,7 +621,7 @@ function fastify (options) {
621
621
  // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
622
622
 
623
623
  // If the socket is not writable, there is no reason to try to send data.
624
- if (socket.writable && socket.bytesWritten === 0) {
624
+ if (socket.writable) {
625
625
  socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
626
626
  }
627
627
  socket.destroy(err)
@@ -3,7 +3,7 @@
3
3
  "use strict";
4
4
  module.exports = validate10;
5
5
  module.exports.default = validate10;
6
- const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"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":{"type":"string","default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"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":{}}}}}};
6
+ const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"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},"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":{}}}}}};
7
7
  const func2 = Object.prototype.hasOwnProperty;
8
8
  const pattern0 = new RegExp("idle", "u");
9
9
 
@@ -837,6 +837,23 @@ var valid0 = _errs55 === errors;
837
837
  if(valid0){
838
838
  let data19 = data.requestIdHeader;
839
839
  const _errs57 = errors;
840
+ const _errs58 = errors;
841
+ let valid6 = false;
842
+ const _errs59 = errors;
843
+ if(!(data19 === false)){
844
+ 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"};
845
+ if(vErrors === null){
846
+ vErrors = [err12];
847
+ }
848
+ else {
849
+ vErrors.push(err12);
850
+ }
851
+ errors++;
852
+ }
853
+ var _valid3 = _errs59 === errors;
854
+ valid6 = valid6 || _valid3;
855
+ if(!valid6){
856
+ const _errs60 = errors;
840
857
  if(typeof data19 !== "string"){
841
858
  let dataType21 = typeof data19;
842
859
  let coerced21 = undefined;
@@ -848,8 +865,14 @@ else if(data19 === null){
848
865
  coerced21 = "";
849
866
  }
850
867
  else {
851
- validate10.errors = [{instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/type",keyword:"type",params:{type: "string"},message:"must be string"}];
852
- return false;
868
+ const err13 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};
869
+ if(vErrors === null){
870
+ vErrors = [err13];
871
+ }
872
+ else {
873
+ vErrors.push(err13);
874
+ }
875
+ errors++;
853
876
  }
854
877
  }
855
878
  if(coerced21 !== undefined){
@@ -859,10 +882,36 @@ data["requestIdHeader"] = coerced21;
859
882
  }
860
883
  }
861
884
  }
885
+ var _valid3 = _errs60 === errors;
886
+ valid6 = valid6 || _valid3;
887
+ }
888
+ if(!valid6){
889
+ const err14 = {instancePath:instancePath+"/requestIdHeader",schemaPath:"#/properties/requestIdHeader/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};
890
+ if(vErrors === null){
891
+ vErrors = [err14];
892
+ }
893
+ else {
894
+ vErrors.push(err14);
895
+ }
896
+ errors++;
897
+ validate10.errors = vErrors;
898
+ return false;
899
+ }
900
+ else {
901
+ errors = _errs58;
902
+ if(vErrors !== null){
903
+ if(_errs58){
904
+ vErrors.length = _errs58;
905
+ }
906
+ else {
907
+ vErrors = null;
908
+ }
909
+ }
910
+ }
862
911
  var valid0 = _errs57 === errors;
863
912
  if(valid0){
864
913
  let data20 = data.requestIdLogLabel;
865
- const _errs59 = errors;
914
+ const _errs62 = errors;
866
915
  if(typeof data20 !== "string"){
867
916
  let dataType22 = typeof data20;
868
917
  let coerced22 = undefined;
@@ -885,10 +934,10 @@ data["requestIdLogLabel"] = coerced22;
885
934
  }
886
935
  }
887
936
  }
888
- var valid0 = _errs59 === errors;
937
+ var valid0 = _errs62 === errors;
889
938
  if(valid0){
890
939
  let data21 = data.http2SessionTimeout;
891
- const _errs61 = errors;
940
+ const _errs64 = errors;
892
941
  if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){
893
942
  let dataType23 = typeof data21;
894
943
  let coerced23 = undefined;
@@ -909,10 +958,10 @@ data["http2SessionTimeout"] = coerced23;
909
958
  }
910
959
  }
911
960
  }
912
- var valid0 = _errs61 === errors;
961
+ var valid0 = _errs64 === errors;
913
962
  if(valid0){
914
963
  let data22 = data.exposeHeadRoutes;
915
- const _errs63 = errors;
964
+ const _errs66 = errors;
916
965
  if(typeof data22 !== "boolean"){
917
966
  let coerced24 = undefined;
918
967
  if(!(coerced24 !== undefined)){
@@ -934,12 +983,12 @@ data["exposeHeadRoutes"] = coerced24;
934
983
  }
935
984
  }
936
985
  }
937
- var valid0 = _errs63 === errors;
986
+ var valid0 = _errs66 === errors;
938
987
  if(valid0){
939
988
  if(data.versioning !== undefined){
940
989
  let data23 = data.versioning;
941
- const _errs65 = errors;
942
- if(errors === _errs65){
990
+ const _errs68 = errors;
991
+ if(errors === _errs68){
943
992
  if(data23 && typeof data23 == "object" && !Array.isArray(data23)){
944
993
  let missing1;
945
994
  if(((data23.storage === undefined) && (missing1 = "storage")) || ((data23.deriveVersion === undefined) && (missing1 = "deriveVersion"))){
@@ -952,7 +1001,7 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop
952
1001
  return false;
953
1002
  }
954
1003
  }
955
- var valid0 = _errs65 === errors;
1004
+ var valid0 = _errs68 === errors;
956
1005
  }
957
1006
  else {
958
1007
  var valid0 = true;
@@ -960,13 +1009,13 @@ var valid0 = true;
960
1009
  if(valid0){
961
1010
  if(data.constraints !== undefined){
962
1011
  let data24 = data.constraints;
963
- const _errs68 = errors;
964
- if(errors === _errs68){
1012
+ const _errs71 = errors;
1013
+ if(errors === _errs71){
965
1014
  if(data24 && typeof data24 == "object" && !Array.isArray(data24)){
966
1015
  for(const key2 in data24){
967
1016
  let data25 = data24[key2];
968
- const _errs71 = errors;
969
- if(errors === _errs71){
1017
+ const _errs74 = errors;
1018
+ if(errors === _errs74){
970
1019
  if(data25 && typeof data25 == "object" && !Array.isArray(data25)){
971
1020
  let missing2;
972
1021
  if(((((data25.name === undefined) && (missing2 = "name")) || ((data25.storage === undefined) && (missing2 = "storage"))) || ((data25.validate === undefined) && (missing2 = "validate"))) || ((data25.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){
@@ -1006,8 +1055,8 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/
1006
1055
  return false;
1007
1056
  }
1008
1057
  }
1009
- var valid6 = _errs71 === errors;
1010
- if(!valid6){
1058
+ var valid7 = _errs74 === errors;
1059
+ if(!valid7){
1011
1060
  break;
1012
1061
  }
1013
1062
  }
@@ -1017,7 +1066,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro
1017
1066
  return false;
1018
1067
  }
1019
1068
  }
1020
- var valid0 = _errs68 === errors;
1069
+ var valid0 = _errs71 === errors;
1021
1070
  }
1022
1071
  else {
1023
1072
  var valid0 = true;
@@ -92,10 +92,18 @@ ContentTypeParser.prototype.existingParser = function (contentType) {
92
92
  }
93
93
 
94
94
  ContentTypeParser.prototype.getParser = function (contentType) {
95
+ if (contentType in this.customParsers) {
96
+ return this.customParsers[contentType]
97
+ }
98
+
99
+ if (this.cache.has(contentType)) {
100
+ return this.cache.get(contentType)
101
+ }
102
+
95
103
  // eslint-disable-next-line no-var
96
104
  for (var i = 0; i !== this.parserList.length; ++i) {
97
105
  const parserName = this.parserList[i]
98
- if (contentType.indexOf(parserName) > -1) {
106
+ if (contentType.indexOf(parserName) !== -1) {
99
107
  const parser = this.customParsers[parserName]
100
108
  this.cache.set(contentType, parser)
101
109
  return parser
@@ -137,7 +145,7 @@ ContentTypeParser.prototype.remove = function (contentType) {
137
145
  }
138
146
 
139
147
  ContentTypeParser.prototype.run = function (contentType, handler, request, reply) {
140
- const parser = this.cache.get(contentType) || this.getParser(contentType)
148
+ const parser = this.getParser(contentType)
141
149
  const resource = new AsyncResource('content-type-parser:run', request)
142
150
 
143
151
  if (parser === undefined) {
package/lib/context.js CHANGED
@@ -10,7 +10,9 @@ const {
10
10
  kBodyLimit,
11
11
  kLogLevel,
12
12
  kContentTypeParser,
13
- kRouteByFastify
13
+ kRouteByFastify,
14
+ kRequestValidateWeakMap,
15
+ kReplySerializeWeakMap
14
16
  } = require('./symbols.js')
15
17
 
16
18
  // Objects that holds the context of every request
@@ -24,6 +26,8 @@ function Context ({
24
26
  logLevel,
25
27
  logSerializers,
26
28
  attachValidation,
29
+ validatorCompiler,
30
+ serializerCompiler,
27
31
  replySerializer,
28
32
  schemaErrorFormatter,
29
33
  server,
@@ -54,6 +58,11 @@ function Context ({
54
58
  this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
55
59
  this[kRouteByFastify] = isFastify
56
60
 
61
+ this[kRequestValidateWeakMap] = null
62
+ this[kReplySerializeWeakMap] = null
63
+ this.validatorCompiler = validatorCompiler || null
64
+ this.serializerCompiler = serializerCompiler || null
65
+
57
66
  this.server = server
58
67
  }
59
68
 
package/lib/errors.js CHANGED
@@ -160,6 +160,14 @@ const codes = {
160
160
  'FST_ERR_BAD_TRAILER_VALUE',
161
161
  "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function."
162
162
  ),
163
+ FST_ERR_MISSING_SERIALIZATION_FN: createError(
164
+ 'FST_ERR_MISSING_SERIALIZATION_FN',
165
+ 'Missing serialization function. Key "%s"'
166
+ ),
167
+ FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError(
168
+ 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION',
169
+ 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.'
170
+ ),
163
171
 
164
172
  /**
165
173
  * schemas
@@ -18,14 +18,14 @@ function handleRequest (err, request, reply) {
18
18
  const method = request.raw.method
19
19
  const headers = request.headers
20
20
 
21
- if (method === 'GET' || method === 'HEAD') {
21
+ if (method === 'GET' || method === 'HEAD' || method === 'SEARCH') {
22
22
  handler(request, reply)
23
23
  return
24
24
  }
25
25
 
26
26
  const contentType = headers['content-type']
27
27
 
28
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
28
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE') {
29
29
  if (contentType === undefined) {
30
30
  if (
31
31
  headers['transfer-encoding'] === undefined &&
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ supportedMethods: [
5
+ 'DELETE',
6
+ 'GET',
7
+ 'HEAD',
8
+ 'PATCH',
9
+ 'POST',
10
+ 'PUT',
11
+ 'OPTIONS',
12
+ 'PROPFIND',
13
+ 'PROPPATCH',
14
+ 'MKCOL',
15
+ 'COPY',
16
+ 'MOVE',
17
+ 'LOCK',
18
+ 'UNLOCK',
19
+ 'TRACE',
20
+ 'SEARCH'
21
+ ]
22
+ }
package/lib/reply.js CHANGED
@@ -16,7 +16,11 @@ const {
16
16
  kReplyHasStatusCode,
17
17
  kReplyIsRunningOnErrorHook,
18
18
  kReplyNextErrorHandler,
19
- kDisableRequestLogging
19
+ kDisableRequestLogging,
20
+ kSchemaResponse,
21
+ kReplySerializeWeakMap,
22
+ kSchemaController,
23
+ kOptions
20
24
  } = require('./symbols.js')
21
25
  const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
22
26
 
@@ -38,7 +42,8 @@ const {
38
42
  FST_ERR_SEND_INSIDE_ONERR,
39
43
  FST_ERR_BAD_STATUS_CODE,
40
44
  FST_ERR_BAD_TRAILER_NAME,
41
- FST_ERR_BAD_TRAILER_VALUE
45
+ FST_ERR_BAD_TRAILER_VALUE,
46
+ FST_ERR_MISSING_SERIALIZATION_FN
42
47
  } = require('./errors')
43
48
  const warning = require('./warnings')
44
49
 
@@ -299,6 +304,79 @@ Reply.prototype.code = function (code) {
299
304
 
300
305
  Reply.prototype.status = Reply.prototype.code
301
306
 
307
+ Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
308
+ let serialize
309
+
310
+ if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
311
+ serialize = this.context[kSchemaResponse]?.[schemaOrStatus]
312
+ } else if (typeof schemaOrStatus === 'object') {
313
+ serialize = this.context[kReplySerializeWeakMap]?.get(schemaOrStatus)
314
+ }
315
+
316
+ return serialize
317
+ }
318
+
319
+ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null) {
320
+ const { request } = this
321
+ const { method, url } = request
322
+
323
+ // Check if serialize function already compiled
324
+ if (this.context[kReplySerializeWeakMap]?.has(schema)) {
325
+ return this.context[kReplySerializeWeakMap].get(schema)
326
+ }
327
+
328
+ const serializerCompiler = this.context.serializerCompiler ||
329
+ this.server[kSchemaController].serializerCompiler ||
330
+ (
331
+ // We compile the schemas if no custom serializerCompiler is provided
332
+ // nor set
333
+ this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
334
+ this.server[kSchemaController].serializerCompiler
335
+ )
336
+
337
+ const serializeFn = serializerCompiler({
338
+ schema,
339
+ method,
340
+ url,
341
+ httpStatus
342
+ })
343
+
344
+ // We create a WeakMap to compile the schema only once
345
+ // Its done leazily to avoid add overhead by creating the WeakMap
346
+ // if it is not used
347
+ // TODO: Explore a central cache for all the schemas shared across
348
+ // encapsulated contexts
349
+ if (this.context[kReplySerializeWeakMap] == null) {
350
+ this.context[kReplySerializeWeakMap] = new WeakMap()
351
+ }
352
+
353
+ this.context[kReplySerializeWeakMap].set(schema, serializeFn)
354
+
355
+ return serializeFn
356
+ }
357
+
358
+ Reply.prototype.serializeInput = function (input, schema, httpStatus) {
359
+ let serialize
360
+ httpStatus = typeof schema === 'string' || typeof schema === 'number'
361
+ ? schema
362
+ : httpStatus
363
+
364
+ if (httpStatus != null) {
365
+ serialize = this.context[kSchemaResponse]?.[httpStatus]
366
+
367
+ if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
368
+ } else {
369
+ // Check if serialize function already compiled
370
+ if (this.context[kReplySerializeWeakMap]?.has(schema)) {
371
+ serialize = this.context[kReplySerializeWeakMap].get(schema)
372
+ } else {
373
+ serialize = this.compileSerializationSchema(schema, httpStatus)
374
+ }
375
+ }
376
+
377
+ return serialize(input)
378
+ }
379
+
302
380
  Reply.prototype.serialize = function (payload) {
303
381
  if (this[kReplySerializer] !== null) {
304
382
  return this[kReplySerializer](payload)
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- module.exports = function () {
3
+ module.exports = function (requestIdHeader, optGenReqId) {
4
4
  // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
5
5
  // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
6
6
  // This is very likely to happen in real-world applications, hence the limit is enforced.
@@ -8,8 +8,19 @@ module.exports = function () {
8
8
  // In the worst cases, it will become a float, losing accuracy.
9
9
  const maxInt = 2147483647
10
10
  let nextReqId = 0
11
- return function genReqId (req) {
11
+ function defaultGenReqId (req) {
12
12
  nextReqId = (nextReqId + 1) & maxInt
13
13
  return `req-${nextReqId.toString(36)}`
14
14
  }
15
+
16
+ const genReqId = optGenReqId || defaultGenReqId
17
+
18
+ if (requestIdHeader) {
19
+ // requestIdHeader = typeof requestIdHeader === 'string' ? requestIdHeader : 'request-id'
20
+ return function (req) {
21
+ return req.headers[requestIdHeader] || genReqId(req)
22
+ }
23
+ }
24
+
25
+ return genReqId
15
26
  }
package/lib/request.js CHANGED
@@ -4,8 +4,24 @@ const proxyAddr = require('proxy-addr')
4
4
  const semver = require('semver')
5
5
  const warning = require('./warnings')
6
6
  const {
7
- kHasBeenDecorated
7
+ kHasBeenDecorated,
8
+ kSchemaBody,
9
+ kSchemaHeaders,
10
+ kSchemaParams,
11
+ kSchemaQuerystring,
12
+ kSchemaController,
13
+ kOptions,
14
+ kRequestValidateWeakMap
8
15
  } = require('./symbols')
16
+ const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
17
+
18
+ const HTTP_PART_SYMBOL_MAP = {
19
+ body: kSchemaBody,
20
+ headers: kSchemaHeaders,
21
+ params: kSchemaParams,
22
+ querystring: kSchemaQuerystring,
23
+ query: kSchemaQuerystring
24
+ }
9
25
 
10
26
  function Request (id, params, req, query, log, context) {
11
27
  this.id = id
@@ -194,6 +210,86 @@ Object.defineProperties(Request.prototype, {
194
210
  set (headers) {
195
211
  this.additionalHeaders = headers
196
212
  }
213
+ },
214
+ getValidationFunction: {
215
+ value: function (httpPartOrSchema) {
216
+ if (typeof httpPartOrSchema === 'string') {
217
+ const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema]
218
+ return this.context[symbol]
219
+ } else if (typeof httpPartOrSchema === 'object') {
220
+ return this.context[kRequestValidateWeakMap]?.get(httpPartOrSchema)
221
+ }
222
+ }
223
+ },
224
+ compileValidationSchema: {
225
+ value: function (schema, httpPart = null) {
226
+ const { method, url } = this
227
+
228
+ if (this.context[kRequestValidateWeakMap]?.has(schema)) {
229
+ return this.context[kRequestValidateWeakMap].get(schema)
230
+ }
231
+
232
+ const validatorCompiler = this.context.validatorCompiler ||
233
+ this.server[kSchemaController].validatorCompiler ||
234
+ (
235
+ // We compile the schemas if no custom validatorCompiler is provided
236
+ // nor set
237
+ this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
238
+ this.server[kSchemaController].validatorCompiler
239
+ )
240
+
241
+ const validateFn = validatorCompiler({
242
+ schema,
243
+ method,
244
+ url,
245
+ httpPart
246
+ })
247
+
248
+ // We create a WeakMap to compile the schema only once
249
+ // Its done leazily to avoid add overhead by creating the WeakMap
250
+ // if it is not used
251
+ // TODO: Explore a central cache for all the schemas shared across
252
+ // encapsulated contexts
253
+ if (this.context[kRequestValidateWeakMap] == null) {
254
+ this.context[kRequestValidateWeakMap] = new WeakMap()
255
+ }
256
+
257
+ this.context[kRequestValidateWeakMap].set(schema, validateFn)
258
+
259
+ return validateFn
260
+ }
261
+ },
262
+ validateInput: {
263
+ value: function (input, schema, httpPart) {
264
+ httpPart = typeof schema === 'string' ? schema : httpPart
265
+
266
+ const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart]
267
+ let validate
268
+
269
+ if (symbol) {
270
+ // Validate using the HTTP Request Part schema
271
+ validate = this.context[symbol]
272
+ }
273
+
274
+ // We cannot compile if the schema is missed
275
+ if (validate == null && (schema == null ||
276
+ typeof schema !== 'object' ||
277
+ Array.isArray(schema))
278
+ ) {
279
+ throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
280
+ }
281
+
282
+ if (validate == null) {
283
+ if (this.context[kRequestValidateWeakMap]?.has(schema)) {
284
+ validate = this.context[kRequestValidateWeakMap].get(schema)
285
+ } else {
286
+ // We proceed to compile if there's no validate function yet
287
+ validate = this.compileValidationSchema(schema, httpPart)
288
+ }
289
+ }
290
+
291
+ return validate(input)
292
+ }
197
293
  }
198
294
  })
199
295
 
package/lib/route.js CHANGED
@@ -4,7 +4,7 @@ const FindMyWay = require('find-my-way')
4
4
  const Context = require('./context')
5
5
  const handleRequest = require('./handleRequest')
6
6
  const { hookRunner, hookIterator, lifecycleHooks } = require('./hooks')
7
- const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
7
+ const { supportedMethods } = require('./httpMethods')
8
8
  const { normalizeSchema } = require('./schemas')
9
9
  const { parseHeadOnSendHandlers } = require('./headRoute')
10
10
  const warning = require('./warnings')
@@ -46,7 +46,6 @@ function buildRouting (options) {
46
46
 
47
47
  let avvio
48
48
  let fourOhFour
49
- let requestIdHeader
50
49
  let requestIdLogLabel
51
50
  let logger
52
51
  let hasLogger
@@ -74,7 +73,6 @@ function buildRouting (options) {
74
73
  validateHTTPVersion = fastifyArgs.validateHTTPVersion
75
74
 
76
75
  globalExposeHeadRoutes = options.exposeHeadRoutes
77
- requestIdHeader = options.requestIdHeader
78
76
  requestIdLogLabel = options.requestIdLogLabel
79
77
  genReqId = options.genReqId
80
78
  disableRequestLogging = options.disableRequestLogging
@@ -241,6 +239,8 @@ function buildRouting (options) {
241
239
  attachValidation: opts.attachValidation,
242
240
  schemaErrorFormatter: opts.schemaErrorFormatter,
243
241
  replySerializer: this[kReplySerializerDefault],
242
+ validatorCompiler: opts.validatorCompiler,
243
+ serializerCompiler: opts.serializerCompiler,
244
244
  server: this,
245
245
  isFastify
246
246
  })
@@ -395,7 +395,7 @@ function buildRouting (options) {
395
395
  req.headers[kRequestAcceptVersion] = undefined
396
396
  }
397
397
 
398
- const id = req.headers[requestIdHeader] || genReqId(req)
398
+ const id = genReqId(req)
399
399
 
400
400
  const loggerBinding = {
401
401
  [requestIdLogLabel]: id
package/lib/server.js CHANGED
@@ -132,6 +132,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
132
132
 
133
133
  const secondaryServer = getServerInstance(serverOpts, httpHandler)
134
134
  const closeSecondary = () => { secondaryServer.close(() => {}) }
135
+ secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
135
136
  mainServer.on('unref', closeSecondary)
136
137
  mainServer.on('close', closeSecondary)
137
138
  mainServer.on('error', closeSecondary)