fastify 4.2.0 → 4.4.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 (63) hide show
  1. package/README.md +13 -14
  2. package/docs/Guides/Database.md +2 -2
  3. package/docs/Guides/Ecosystem.md +42 -18
  4. package/docs/Guides/Migration-Guide-V4.md +29 -0
  5. package/docs/Guides/Plugins-Guide.md +5 -0
  6. package/docs/Reference/HTTP2.md +1 -3
  7. package/docs/Reference/Hooks.md +4 -1
  8. package/docs/Reference/Logging.md +1 -1
  9. package/docs/Reference/Reply.md +177 -0
  10. package/docs/Reference/Request.md +171 -0
  11. package/docs/Reference/Routes.md +4 -2
  12. package/docs/Reference/Server.md +4 -1
  13. package/docs/Reference/Type-Providers.md +1 -15
  14. package/docs/Reference/TypeScript.md +27 -3
  15. package/fastify.d.ts +1 -1
  16. package/fastify.js +3 -3
  17. package/lib/contentTypeParser.js +10 -2
  18. package/lib/context.js +10 -1
  19. package/lib/error-serializer.js +12 -12
  20. package/lib/errors.js +8 -0
  21. package/lib/handleRequest.js +2 -2
  22. package/lib/httpMethods.js +22 -0
  23. package/lib/reply.js +101 -24
  24. package/lib/request.js +97 -1
  25. package/lib/route.js +3 -1
  26. package/lib/symbols.js +15 -9
  27. package/package.json +7 -7
  28. package/test/build/error-serializer.test.js +3 -1
  29. package/test/content-parser.test.js +15 -0
  30. package/test/copy.test.js +41 -0
  31. package/test/internals/all.test.js +8 -2
  32. package/test/internals/reply-serialize.test.js +583 -0
  33. package/test/internals/reply.test.js +4 -1
  34. package/test/internals/request-validate.test.js +1269 -0
  35. package/test/internals/request.test.js +11 -2
  36. package/test/lock.test.js +73 -0
  37. package/test/mkcol.test.js +38 -0
  38. package/test/move.test.js +45 -0
  39. package/test/propfind.test.js +108 -0
  40. package/test/proppatch.test.js +78 -0
  41. package/test/request-error.test.js +44 -1
  42. package/test/schema-validation.test.js +71 -0
  43. package/test/search.test.js +100 -0
  44. package/test/trace.test.js +21 -0
  45. package/test/types/fastify.test-d.ts +12 -1
  46. package/test/types/hooks.test-d.ts +1 -2
  47. package/test/types/import.ts +1 -1
  48. package/test/types/instance.test-d.ts +4 -1
  49. package/test/types/logger.test-d.ts +4 -5
  50. package/test/types/reply.test-d.ts +44 -3
  51. package/test/types/request.test-d.ts +10 -29
  52. package/test/types/type-provider.test-d.ts +79 -7
  53. package/test/unlock.test.js +41 -0
  54. package/test/validation-error-handling.test.js +6 -1
  55. package/types/hooks.d.ts +19 -39
  56. package/types/instance.d.ts +78 -130
  57. package/types/logger.d.ts +7 -4
  58. package/types/reply.d.ts +7 -1
  59. package/types/request.d.ts +16 -3
  60. package/types/route.d.ts +23 -29
  61. package/types/schema.d.ts +4 -1
  62. package/types/type-provider.d.ts +8 -20
  63. package/types/utils.d.ts +2 -1
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
 
@@ -203,10 +208,8 @@ Reply.prototype.getHeaders = function () {
203
208
 
204
209
  Reply.prototype.hasHeader = function (key) {
205
210
  key = key.toLowerCase()
206
- if (this[kReplyHeaders][key] !== undefined) {
207
- return true
208
- }
209
- return this.raw.hasHeader(key)
211
+
212
+ return this[kReplyHeaders][key] !== undefined || this.raw.hasHeader(key)
210
213
  }
211
214
 
212
215
  Reply.prototype.removeHeader = function (key) {
@@ -216,25 +219,24 @@ Reply.prototype.removeHeader = function (key) {
216
219
  return this
217
220
  }
218
221
 
219
- Reply.prototype.header = function (key, value) {
220
- const _key = key.toLowerCase()
221
-
222
- // default the value to ''
223
- value = value === undefined ? '' : value
222
+ Reply.prototype.header = function (key, value = '') {
223
+ key = key.toLowerCase()
224
224
 
225
- if (this[kReplyHeaders][_key] && _key === 'set-cookie') {
225
+ if (this[kReplyHeaders][key] && key === 'set-cookie') {
226
226
  // https://tools.ietf.org/html/rfc7230#section-3.2.2
227
- if (typeof this[kReplyHeaders][_key] === 'string') {
228
- this[kReplyHeaders][_key] = [this[kReplyHeaders][_key]]
227
+ if (typeof this[kReplyHeaders][key] === 'string') {
228
+ this[kReplyHeaders][key] = [this[kReplyHeaders][key]]
229
229
  }
230
+
230
231
  if (Array.isArray(value)) {
231
- Array.prototype.push.apply(this[kReplyHeaders][_key], value)
232
+ this[kReplyHeaders][key].push(...value)
232
233
  } else {
233
- this[kReplyHeaders][_key].push(value)
234
+ this[kReplyHeaders][key].push(value)
234
235
  }
235
236
  } else {
236
- this[kReplyHeaders][_key] = value
237
+ this[kReplyHeaders][key] = value
237
238
  }
239
+
238
240
  return this
239
241
  }
240
242
 
@@ -245,6 +247,7 @@ Reply.prototype.headers = function (headers) {
245
247
  const key = keys[i]
246
248
  this.header(key, headers[key])
247
249
  }
250
+
248
251
  return this
249
252
  }
250
253
 
@@ -279,8 +282,7 @@ Reply.prototype.trailer = function (key, fn) {
279
282
  }
280
283
 
281
284
  Reply.prototype.hasTrailer = function (key) {
282
- if (this[kReplyTrailers] === null) return false
283
- return this[kReplyTrailers][key.toLowerCase()] !== undefined
285
+ return this[kReplyTrailers]?.[key.toLowerCase()] !== undefined
284
286
  }
285
287
 
286
288
  Reply.prototype.removeTrailer = function (key) {
@@ -302,6 +304,79 @@ Reply.prototype.code = function (code) {
302
304
 
303
305
  Reply.prototype.status = Reply.prototype.code
304
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
+
305
380
  Reply.prototype.serialize = function (payload) {
306
381
  if (this[kReplySerializer] !== null) {
307
382
  return this[kReplySerializer](payload)
@@ -330,8 +405,7 @@ Reply.prototype.redirect = function (code, url) {
330
405
  code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
331
406
  }
332
407
 
333
- this.header('location', url).code(code).send()
334
- return this
408
+ return this.header('location', url).code(code).send()
335
409
  }
336
410
 
337
411
  Reply.prototype.callNotFound = function () {
@@ -485,9 +559,12 @@ function onSendEnd (reply, payload) {
485
559
  }
486
560
 
487
561
  if (reply[kReplyTrailers] === null) {
488
- if (!reply[kReplyHeaders]['content-length']) {
489
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
490
- } else if (req.raw.method !== 'HEAD' && reply[kReplyHeaders]['content-length'] !== Buffer.byteLength(payload)) {
562
+ const contentLength = reply[kReplyHeaders]['content-length']
563
+ if (!contentLength ||
564
+ (req.raw.method !== 'HEAD' &&
565
+ parseInt(contentLength, 10) !== Buffer.byteLength(payload)
566
+ )
567
+ ) {
491
568
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
492
569
  }
493
570
  }
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')
@@ -241,6 +241,8 @@ function buildRouting (options) {
241
241
  attachValidation: opts.attachValidation,
242
242
  schemaErrorFormatter: opts.schemaErrorFormatter,
243
243
  replySerializer: this[kReplySerializerDefault],
244
+ validatorCompiler: opts.validatorCompiler,
245
+ serializerCompiler: opts.serializerCompiler,
244
246
  server: this,
245
247
  isFastify
246
248
  })
package/lib/symbols.js CHANGED
@@ -9,6 +9,12 @@ const keys = {
9
9
  kLogLevel: Symbol('fastify.logLevel'),
10
10
  kLogSerializers: Symbol('fastify.logSerializers'),
11
11
  kHooks: Symbol('fastify.hooks'),
12
+ kContentTypeParser: Symbol('fastify.contentTypeParser'),
13
+ kState: Symbol('fastify.state'),
14
+ kOptions: Symbol('fastify.options'),
15
+ kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
16
+ kPluginNameChain: Symbol('fastify.pluginNameChain'),
17
+ // Schema
12
18
  kSchemaController: Symbol('fastify.schemaController'),
13
19
  kSchemaHeaders: Symbol('headers-schema'),
14
20
  kSchemaParams: Symbol('params-schema'),
@@ -16,17 +22,20 @@ const keys = {
16
22
  kSchemaBody: Symbol('body-schema'),
17
23
  kSchemaResponse: Symbol('response-schema'),
18
24
  kSchemaErrorFormatter: Symbol('fastify.schemaErrorFormatter'),
19
- kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
20
- kContentTypeParser: Symbol('fastify.contentTypeParser'),
21
- kReply: Symbol('fastify.Reply'),
25
+ kSchemaVisited: Symbol('fastify.schemas.visited'),
26
+ // Request
22
27
  kRequest: Symbol('fastify.Request'),
28
+ kRequestValidateFns: Symbol('fastify.request.cache.validateFns'),
23
29
  kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'),
24
30
  kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'),
25
- kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'),
31
+ // 404
26
32
  kFourOhFour: Symbol('fastify.404'),
33
+ kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'),
27
34
  kFourOhFourLevelInstance: Symbol('fastify.404LogLevelInstance'),
28
35
  kFourOhFourContext: Symbol('fastify.404ContextKey'),
29
36
  kDefaultJsonParse: Symbol('fastify.defaultJSONParse'),
37
+ // Reply
38
+ kReply: Symbol('fastify.Reply'),
30
39
  kReplySerializer: Symbol('fastify.reply.serializer'),
31
40
  kReplyIsError: Symbol('fastify.reply.isError'),
32
41
  kReplyHeaders: Symbol('fastify.reply.headers'),
@@ -38,11 +47,8 @@ const keys = {
38
47
  kReplyEndTime: Symbol('fastify.reply.endTime'),
39
48
  kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'),
40
49
  kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'),
41
- kSchemaVisited: Symbol('fastify.schemas.visited'),
42
- kState: Symbol('fastify.state'),
43
- kOptions: Symbol('fastify.options'),
44
- kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
45
- kPluginNameChain: Symbol('fastify.pluginNameChain'),
50
+ kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
51
+ kReplySerializeWeakMap: Symbol('fastify.reply.cache.serializeFns'),
46
52
  // This symbol is only meant to be used for fastify tests and should not be used for any other purpose
47
53
  kTestInternals: Symbol('fastify.testInternals'),
48
54
  kErrorHandler: Symbol('fastify.errorHandler'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -17,7 +17,7 @@
17
17
  "lint:markdown": "markdownlint-cli2",
18
18
  "lint:standard": "standard | snazzy",
19
19
  "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
20
- "prepublishOnly": "tap --no-check-coverage test/build/**.test.js",
20
+ "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js",
21
21
  "test": "npm run lint && npm run unit && npm run test:typescript",
22
22
  "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
23
23
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
@@ -126,7 +126,7 @@
126
126
  "homepage": "https://www.fastify.io/",
127
127
  "devDependencies": {
128
128
  "@fastify/pre-commit": "^2.0.2",
129
- "@sinclair/typebox": "^0.23.5",
129
+ "@sinclair/typebox": "^0.24.9",
130
130
  "@sinonjs/fake-timers": "^9.1.2",
131
131
  "@types/node": "^18.0.0",
132
132
  "@typescript-eslint/eslint-plugin": "^5.27.0",
@@ -147,7 +147,7 @@
147
147
  "eslint-plugin-promise": "^6.0.0",
148
148
  "fast-json-body": "^1.1.0",
149
149
  "fast-json-stringify": "^5.0.0",
150
- "fastify-plugin": "^3.0.1",
150
+ "fastify-plugin": "^4.0.0",
151
151
  "fluent-json-schema": "^3.1.0",
152
152
  "form-data": "^4.0.0",
153
153
  "frameguard": "^4.0.0",
@@ -159,7 +159,7 @@
159
159
  "json-schema-to-ts": "^2.5.3",
160
160
  "JSONStream": "^1.3.5",
161
161
  "license-checker": "^25.0.1",
162
- "markdownlint-cli2": "^0.4.0",
162
+ "markdownlint-cli2": "^0.5.0",
163
163
  "proxyquire": "^2.1.3",
164
164
  "pump": "^3.0.0",
165
165
  "self-cert": "^2.0.0",
@@ -170,14 +170,14 @@
170
170
  "split2": "^4.1.0",
171
171
  "standard": "^17.0.0-2",
172
172
  "tap": "^16.2.0",
173
- "tsd": "^0.21.0",
173
+ "tsd": "^0.22.0",
174
174
  "typescript": "^4.7.2",
175
175
  "undici": "^5.4.0",
176
176
  "x-xss-protection": "^2.0.0",
177
177
  "yup": "^0.32.11"
178
178
  },
179
179
  "dependencies": {
180
- "@fastify/ajv-compiler": "^3.1.0",
180
+ "@fastify/ajv-compiler": "^3.1.1",
181
181
  "@fastify/error": "^3.0.0",
182
182
  "@fastify/fast-json-stringify-compiler": "^4.0.0",
183
183
  "abstract-logging": "^2.0.1",
@@ -23,7 +23,9 @@ test('check generated code syntax', async (t) => {
23
23
  t.equal(result[0].fatalErrorCount, 0)
24
24
  })
25
25
 
26
- test('ensure the current error serializer is latest', async (t) => {
26
+ const isPrebublish = !!process.env.PREPUBLISH
27
+
28
+ test('ensure the current error serializer is latest', { skip: !isPrebublish }, async (t) => {
27
29
  t.plan(1)
28
30
 
29
31
  const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js'))
@@ -58,6 +58,21 @@ test('getParser', t => {
58
58
  t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, third)
59
59
  })
60
60
 
61
+ test('should return matching parser with caching', t => {
62
+ t.plan(6)
63
+
64
+ const fastify = Fastify()
65
+
66
+ fastify.addContentTypeParser('text/html', first)
67
+
68
+ t.equal(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)
69
+ t.equal(fastify[keys.kContentTypeParser].cache.size, 0)
70
+ t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)
71
+ t.equal(fastify[keys.kContentTypeParser].cache.size, 1)
72
+ t.equal(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)
73
+ t.equal(fastify[keys.kContentTypeParser].cache.size, 1)
74
+ })
75
+
61
76
  test('should prefer content type parser with string value', t => {
62
77
  t.plan(2)
63
78
 
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const sget = require('simple-get').concat
6
+ const fastify = require('..')()
7
+
8
+ test('can be created - copy', t => {
9
+ t.plan(1)
10
+ try {
11
+ fastify.route({
12
+ method: 'COPY',
13
+ url: '*',
14
+ handler: function (req, reply) {
15
+ reply.code(204).send()
16
+ }
17
+ })
18
+ t.pass()
19
+ } catch (e) {
20
+ t.fail()
21
+ }
22
+ })
23
+
24
+ fastify.listen({ port: 0 }, err => {
25
+ t.error(err)
26
+ t.teardown(() => { fastify.close() })
27
+
28
+ test('request - copy', t => {
29
+ t.plan(2)
30
+ sget({
31
+ url: `http://localhost:${fastify.server.address().port}/test.txt`,
32
+ method: 'COPY',
33
+ headers: {
34
+ Destination: '/test2.txt'
35
+ }
36
+ }, (err, response, body) => {
37
+ t.error(err)
38
+ t.equal(response.statusCode, 204)
39
+ })
40
+ })
41
+ })
@@ -3,9 +3,15 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('../..')
6
- const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
6
+ const { supportedMethods } = require('../../lib/httpMethods')
7
7
 
8
8
  test('fastify.all should add all the methods to the same url', t => {
9
+ const requirePayload = [
10
+ 'POST',
11
+ 'PUT',
12
+ 'PATCH'
13
+ ]
14
+
9
15
  t.plan(supportedMethods.length * 2)
10
16
 
11
17
  const fastify = Fastify()
@@ -22,7 +28,7 @@ test('fastify.all should add all the methods to the same url', t => {
22
28
  method
23
29
  }
24
30
 
25
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
31
+ if (requirePayload.includes(method)) {
26
32
  options.payload = { hello: 'world' }
27
33
  }
28
34