fastify 2.10.0 → 2.13.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/LICENSE +1 -1
  2. package/README.md +52 -28
  3. package/build/build-validation.js +12 -2
  4. package/docs/Decorators.md +136 -75
  5. package/docs/Ecosystem.md +13 -2
  6. package/docs/Errors.md +10 -0
  7. package/docs/Fluent-Schema.md +1 -3
  8. package/docs/Hooks.md +75 -37
  9. package/docs/Plugins-Guide.md +19 -2
  10. package/docs/Plugins.md +27 -0
  11. package/docs/Recommendations.md +159 -0
  12. package/docs/Reply.md +19 -2
  13. package/docs/Routes.md +96 -6
  14. package/docs/Server.md +86 -4
  15. package/docs/Serverless.md +1 -1
  16. package/docs/Testing.md +18 -3
  17. package/docs/TypeScript.md +37 -14
  18. package/docs/Validation-and-Serialization.md +214 -13
  19. package/fastify.d.ts +33 -40
  20. package/fastify.js +65 -6
  21. package/lib/configValidator.js +129 -52
  22. package/lib/contentTypeParser.js +4 -4
  23. package/lib/context.js +2 -1
  24. package/lib/decorate.js +13 -2
  25. package/lib/errors.js +8 -0
  26. package/lib/hooks.js +3 -1
  27. package/lib/logger.js +2 -2
  28. package/lib/reply.js +10 -4
  29. package/lib/route.js +37 -27
  30. package/lib/schemas.js +10 -2
  31. package/lib/server.js +11 -0
  32. package/lib/symbols.js +3 -1
  33. package/lib/validation.js +11 -5
  34. package/package.json +33 -29
  35. package/test/404s.test.js +40 -0
  36. package/test/decorator.test.js +80 -0
  37. package/test/esm/esm.mjs +13 -0
  38. package/test/esm/index.test.js +19 -0
  39. package/test/esm/other.mjs +7 -0
  40. package/test/esm/plugin.mjs +7 -0
  41. package/test/fastify-instance.test.js +29 -0
  42. package/test/genReqId.test.js +1 -1
  43. package/test/hooks-async.js +19 -0
  44. package/test/hooks.test.js +139 -7
  45. package/test/http2/closing.js +53 -0
  46. package/test/http2/plain.js +15 -0
  47. package/test/input-validation.js +63 -0
  48. package/test/input-validation.test.js +161 -0
  49. package/test/internals/decorator.test.js +37 -2
  50. package/test/internals/initialConfig.test.js +6 -2
  51. package/test/internals/reply.test.js +27 -3
  52. package/test/logger.test.js +310 -1
  53. package/test/proto-poisoning.test.js +76 -0
  54. package/test/reply-error.test.js +30 -0
  55. package/test/route-hooks.test.js +23 -14
  56. package/test/route.test.js +62 -24
  57. package/test/router-options.test.js +34 -0
  58. package/test/schemas.test.js +156 -0
  59. package/test/shared-schemas.test.js +65 -0
  60. package/test/types/index.ts +75 -2
package/lib/decorate.js CHANGED
@@ -4,13 +4,15 @@
4
4
 
5
5
  const {
6
6
  kReply,
7
- kRequest
7
+ kRequest,
8
+ kState
8
9
  } = require('./symbols.js')
9
10
 
10
11
  const {
11
12
  codes: {
12
13
  FST_ERR_DEC_ALREADY_PRESENT,
13
- FST_ERR_DEC_MISSING_DEPENDENCY
14
+ FST_ERR_DEC_MISSING_DEPENDENCY,
15
+ FST_ERR_DEC_AFTER_START
14
16
  }
15
17
  } = require('./errors')
16
18
 
@@ -34,6 +36,7 @@ function decorate (instance, name, fn, dependencies) {
34
36
  }
35
37
 
36
38
  function decorateFastify (name, fn, dependencies) {
39
+ assertNotStarted(this, name)
37
40
  decorate(this, name, fn, dependencies)
38
41
  return this
39
42
  }
@@ -63,15 +66,23 @@ function checkDependencies (instance, deps) {
63
66
  }
64
67
 
65
68
  function decorateReply (name, fn, dependencies) {
69
+ assertNotStarted(this, name)
66
70
  decorate(this[kReply].prototype, name, fn, dependencies)
67
71
  return this
68
72
  }
69
73
 
70
74
  function decorateRequest (name, fn, dependencies) {
75
+ assertNotStarted(this, name)
71
76
  decorate(this[kRequest].prototype, name, fn, dependencies)
72
77
  return this
73
78
  }
74
79
 
80
+ function assertNotStarted (instance, name) {
81
+ if (instance[kState].started) {
82
+ throw new FST_ERR_DEC_AFTER_START(name)
83
+ }
84
+ }
85
+
75
86
  module.exports = {
76
87
  add: decorateFastify,
77
88
  exist: checkExistence,
package/lib/errors.js CHANGED
@@ -27,6 +27,7 @@ createError('FST_ERR_CTP_EMPTY_JSON_BODY', "Body cannot be empty when content-ty
27
27
  */
28
28
  createError('FST_ERR_DEC_ALREADY_PRESENT', "The decorator '%s' has already been added!")
29
29
  createError('FST_ERR_DEC_MISSING_DEPENDENCY', "The decorator is missing dependency '%s'.")
30
+ createError('FST_ERR_DEC_AFTER_START', "The decorator '%s' has been added after start!")
30
31
 
31
32
  /**
32
33
  * hooks
@@ -46,6 +47,8 @@ createError('FST_ERR_REP_INVALID_PAYLOAD_TYPE', "Attempted to send payload of in
46
47
  createError('FST_ERR_REP_ALREADY_SENT', 'Reply was already sent.')
47
48
  createError('FST_ERR_REP_SENT_VALUE', 'The only possible value for reply.sent is true.')
48
49
  createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onError` hook')
50
+ createError('FST_ERR_SEND_UNDEFINED_ERR', 'Undefined error has occured')
51
+ createError('FST_ERR_BAD_STATUS_CODE', 'Called reply with malformed status code')
49
52
 
50
53
  /**
51
54
  * schemas
@@ -72,6 +75,11 @@ createError('FST_ERR_HTTP2_INVALID_VERSION', 'HTTP2 is available only from node
72
75
  */
73
76
  createError('FST_ERR_INIT_OPTS_INVALID', "Invalid initialization options: '%s'")
74
77
 
78
+ /**
79
+ * router
80
+ */
81
+ createError('FST_ERR_BAD_URL', "'%s' is not a valid url component", 400)
82
+
75
83
  function createError (code, message, statusCode = 500, Base = Error) {
76
84
  if (!code) throw new Error('Fastify error code must not be empty')
77
85
  if (!message) throw new Error('Fastify error message must not be empty')
package/lib/hooks.js CHANGED
@@ -17,7 +17,8 @@ const supportedHooks = [
17
17
  const {
18
18
  codes: {
19
19
  FST_ERR_HOOK_INVALID_TYPE,
20
- FST_ERR_HOOK_INVALID_HANDLER
20
+ FST_ERR_HOOK_INVALID_HANDLER,
21
+ FST_ERR_SEND_UNDEFINED_ERR
21
22
  }
22
23
  } = require('./errors')
23
24
 
@@ -78,6 +79,7 @@ function hookRunner (functions, runner, request, reply, cb) {
78
79
  }
79
80
 
80
81
  function handleReject (err) {
82
+ if (err === undefined) err = new FST_ERR_SEND_UNDEFINED_ERR()
81
83
  cb(err, request, reply)
82
84
  }
83
85
 
package/lib/logger.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
7
7
  */
8
8
 
9
- const abstractLogging = require('abstract-logging')
9
+ const nullLogger = require('abstract-logging')
10
10
  const pino = require('pino')
11
11
  const { serializersSym } = pino.symbols
12
12
  const { isValidLogger } = require('./validation')
@@ -82,7 +82,7 @@ function createLogger (options) {
82
82
  })
83
83
  return { logger, hasLogger: true }
84
84
  } else if (!options.logger) {
85
- const logger = Object.create(abstractLogging)
85
+ const logger = nullLogger
86
86
  logger.child = () => logger
87
87
  return { logger, hasLogger: false }
88
88
  } else {
package/lib/reply.js CHANGED
@@ -46,7 +46,8 @@ const {
46
46
  FST_ERR_REP_INVALID_PAYLOAD_TYPE,
47
47
  FST_ERR_REP_ALREADY_SENT,
48
48
  FST_ERR_REP_SENT_VALUE,
49
- FST_ERR_SEND_INSIDE_ONERR
49
+ FST_ERR_SEND_INSIDE_ONERR,
50
+ FST_ERR_BAD_STATUS_CODE
50
51
  }
51
52
  } = require('./errors')
52
53
 
@@ -140,9 +141,10 @@ Reply.prototype.send = function (payload) {
140
141
  if (hasContentType === false || contentType.indexOf('charset') === -1) {
141
142
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
142
143
  }
143
-
144
- preserializeHook(this, payload)
145
- return this
144
+ if (typeof payload !== 'string') {
145
+ preserializeHook(this, payload)
146
+ return this
147
+ }
146
148
  }
147
149
 
148
150
  onSendHook(this, payload)
@@ -196,6 +198,10 @@ Reply.prototype.headers = function (headers) {
196
198
  }
197
199
 
198
200
  Reply.prototype.code = function (code) {
201
+ if (statusCodes[code] === undefined) {
202
+ throw new FST_ERR_BAD_STATUS_CODE()
203
+ }
204
+
199
205
  this.res.statusCode = code
200
206
  this[kReplyHasStatusCode] = true
201
207
  return this
package/lib/route.js CHANGED
@@ -6,7 +6,7 @@ const Context = require('./context')
6
6
  const { buildMiddie, onRunMiddlewares } = require('./middleware')
7
7
  const { hookRunner, hookIterator } = require('./hooks')
8
8
  const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
9
- const supportedHooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization', 'onResponse']
9
+ const supportedHooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization', 'onResponse', 'onSend']
10
10
  const validation = require('./validation')
11
11
  const buildSchema = validation.build
12
12
  const { buildSchemaCompiler } = validation
@@ -22,8 +22,10 @@ const {
22
22
  const {
23
23
  kRoutePrefix,
24
24
  kLogLevel,
25
+ kLogSerializers,
25
26
  kHooks,
26
27
  kSchemas,
28
+ kOptions,
27
29
  kSchemaCompiler,
28
30
  kSchemaResolver,
29
31
  kContentTypeParser,
@@ -180,6 +182,10 @@ function buildRouting (options) {
180
182
  opts.prefix = prefix
181
183
  opts.logLevel = opts.logLevel || this[kLogLevel]
182
184
 
185
+ if (this[kLogSerializers] || opts.logSerializers) {
186
+ opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
187
+ }
188
+
183
189
  if (opts.attachValidation == null) {
184
190
  opts.attachValidation = false
185
191
  }
@@ -207,33 +213,11 @@ function buildRouting (options) {
207
213
  this._errorHandler,
208
214
  opts.bodyLimit,
209
215
  opts.logLevel,
216
+ opts.logSerializers,
210
217
  opts.attachValidation,
211
218
  this[kReplySerializerDefault]
212
219
  )
213
220
 
214
- // TODO this needs to be refactored so that buildSchemaCompiler is
215
- // not called for every single route. Creating a new one for every route
216
- // is going to be very expensive.
217
- if (opts.schema) {
218
- if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
219
- done(new FST_ERR_SCH_MISSING_COMPILER(opts.method, url))
220
- return
221
- }
222
-
223
- try {
224
- if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
225
- const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
226
- this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, schemaCache))
227
- }
228
-
229
- buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
230
- } catch (error) {
231
- // bubble up the FastifyError instance
232
- done(error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
233
- return
234
- }
235
- }
236
-
237
221
  for (const hook of supportedHooks) {
238
222
  if (opts[hook]) {
239
223
  if (Array.isArray(opts[hook])) {
@@ -245,7 +229,7 @@ function buildRouting (options) {
245
229
  }
246
230
 
247
231
  try {
248
- router.on(opts.method, url, { version: opts.version }, routeHandler, context)
232
+ router.on(opts.method, opts.url, { version: opts.version }, routeHandler, context)
249
233
  } catch (err) {
250
234
  done(err)
251
235
  return
@@ -273,6 +257,24 @@ function buildRouting (options) {
273
257
  // Must store the 404 Context in 'preReady' because it is only guaranteed to
274
258
  // be available after all of the plugins and routes have been loaded.
275
259
  fourOhFour.setContext(this, context)
260
+
261
+ if (opts.schema) {
262
+ if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
263
+ throw new FST_ERR_SCH_MISSING_COMPILER(opts.method, url)
264
+ }
265
+
266
+ try {
267
+ if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
268
+ const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
269
+ this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
270
+ }
271
+
272
+ buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
273
+ } catch (error) {
274
+ // bubble up the FastifyError instance
275
+ throw (error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
276
+ }
277
+ }
276
278
  })
277
279
 
278
280
  done(notHandledErr)
@@ -300,7 +302,7 @@ function buildRouting (options) {
300
302
 
301
303
  req.id = req.headers[requestIdHeader] || genReqId(req)
302
304
  req.originalUrl = req.url
303
- var hostname = req.headers.host
305
+ var hostname = req.headers.host || req.headers[':authority']
304
306
  var ip = req.connection.remoteAddress
305
307
  var ips
306
308
 
@@ -312,7 +314,15 @@ function buildRouting (options) {
312
314
  }
313
315
  }
314
316
 
315
- var childLogger = logger.child({ [requestIdLogLabel]: req.id, level: context.logLevel })
317
+ var loggerOpts = {
318
+ [requestIdLogLabel]: req.id,
319
+ level: context.logLevel
320
+ }
321
+
322
+ if (context.logSerializers) {
323
+ loggerOpts.serializers = context.logSerializers
324
+ }
325
+ var childLogger = logger.child(loggerOpts)
316
326
  childLogger[kDisableRequestLogging] = disableRequestLogging
317
327
 
318
328
  // added hostname, ip, and ips back to the Node req object to maintain backward compatibility
package/lib/schemas.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const fastClone = require('rfdc')({ circles: false, proto: true })
4
+ const { kSchemaVisited } = require('./symbols')
4
5
  const kFluentSchema = Symbol.for('fluent-schema-object')
5
6
 
6
7
  const {
@@ -43,6 +44,10 @@ Schemas.prototype.resolve = function (id) {
43
44
  }
44
45
 
45
46
  Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId, refResolver) {
47
+ if (routeSchemas[kSchemaVisited]) {
48
+ return routeSchemas
49
+ }
50
+
46
51
  // alias query to querystring schema
47
52
  if (routeSchemas.query) {
48
53
  // check if our schema has both querystring and query
@@ -96,6 +101,7 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId, refResolver
96
101
  routeSchemas.params = this.getSchemaAnyway(routeSchemas.params)
97
102
  }
98
103
 
104
+ routeSchemas[kSchemaVisited] = true
99
105
  return routeSchemas
100
106
  }
101
107
 
@@ -117,7 +123,9 @@ Schemas.prototype.traverse = function (schema, refResolver) {
117
123
  }
118
124
  }
119
125
 
120
- if (schema[key] !== null && typeof schema[key] === 'object') {
126
+ if (schema[key] !== null && typeof schema[key] === 'object' &&
127
+ (key !== 'enum' || (key === 'enum' && schema.type !== 'string'))) {
128
+ // don't traverse non-object values and the `enum` keyword when used for string type
121
129
  this.traverse(schema[key], refResolver)
122
130
  }
123
131
  }
@@ -135,7 +143,7 @@ Schemas.prototype.cleanId = function (schema) {
135
143
  }
136
144
 
137
145
  Schemas.prototype.getSchemaAnyway = function (schema) {
138
- if (schema.oneOf || schema.allOf || schema.anyOf) return schema
146
+ if (schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
139
147
  if (!schema.type || !schema.properties) {
140
148
  return {
141
149
  type: 'object',
package/lib/server.js CHANGED
@@ -24,6 +24,7 @@ function createServer (options, httpHandler) {
24
24
  }
25
25
  } else if (options.http2) {
26
26
  server = http2().createServer(httpHandler)
27
+ server.on('session', sessionTimeout(options.http2SessionTimeout))
27
28
  } else {
28
29
  server = http.createServer(httpHandler)
29
30
  }
@@ -148,4 +149,14 @@ function http2 () {
148
149
  }
149
150
  }
150
151
 
152
+ function sessionTimeout (timeout) {
153
+ return function (session) {
154
+ session.setTimeout(timeout, close)
155
+ }
156
+ }
157
+
158
+ function close () {
159
+ this.close()
160
+ }
161
+
151
162
  module.exports = { createServer }
package/lib/symbols.js CHANGED
@@ -5,6 +5,7 @@ const keys = {
5
5
  kBodyLimit: Symbol('fastify.bodyLimit'),
6
6
  kRoutePrefix: Symbol('fastify.routePrefix'),
7
7
  kLogLevel: Symbol('fastify.logLevel'),
8
+ kLogSerializers: Symbol('fastify.logSerializers'),
8
9
  kHooks: Symbol('fastify.hooks'),
9
10
  kSchemas: Symbol('fastify.schemas'),
10
11
  kSchemaCompiler: Symbol('fastify.schemaCompiler'),
@@ -32,7 +33,8 @@ const keys = {
32
33
  kOptions: Symbol('fastify.options'),
33
34
  kGlobalHooks: Symbol('fastify.globalHooks'),
34
35
  kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
35
- kPluginNameChain: Symbol('fastify.pluginNameChain')
36
+ kPluginNameChain: Symbol('fastify.pluginNameChain'),
37
+ kSchemaVisited: Symbol('fastify.schemaVisited')
36
38
  }
37
39
 
38
40
  module.exports = keys
package/lib/validation.js CHANGED
@@ -119,6 +119,7 @@ function wrapValidationError (result, dataVar) {
119
119
  }
120
120
  var error = new Error(schemaErrorsText(result, dataVar))
121
121
  error.validation = result
122
+ error.validationContext = dataVar
122
123
  return error
123
124
  }
124
125
 
@@ -163,17 +164,22 @@ function schemaErrorsText (errors, dataVar) {
163
164
  return text.slice(0, -separator.length)
164
165
  }
165
166
 
166
- function buildSchemaCompiler (externalSchemas, cache) {
167
+ function buildSchemaCompiler (externalSchemas, options, cache) {
167
168
  // This instance of Ajv is private
168
169
  // it should not be customized or used
169
- const ajv = new Ajv({
170
+ const ajv = new Ajv(Object.assign({
170
171
  coerceTypes: true,
171
172
  useDefaults: true,
172
173
  removeAdditional: true,
173
174
  allErrors: true,
174
- nullable: true,
175
- cache
176
- })
175
+ nullable: true
176
+ }, options.customOptions, { cache }))
177
+
178
+ if (options.plugins && options.plugins.length > 0) {
179
+ for (const plugin of options.plugins) {
180
+ plugin[0](ajv, plugin[1])
181
+ }
182
+ }
177
183
 
178
184
  if (Array.isArray(externalSchemas)) {
179
185
  externalSchemas.forEach(s => ajv.addSchema(s))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "2.10.0",
3
+ "version": "2.13.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "typings": "fastify.d.ts",
@@ -92,26 +92,28 @@
92
92
  "node": ">=6"
93
93
  },
94
94
  "devDependencies": {
95
- "@types/node": "^11.13.19",
96
- "@typescript-eslint/eslint-plugin": "^2.3.0",
97
- "@typescript-eslint/parser": "^2.3.0",
95
+ "@types/node": "^12.12.30",
96
+ "@typescript-eslint/eslint-plugin": "^2.24.0",
97
+ "@typescript-eslint/parser": "^2.24.0",
98
98
  "JSONStream": "^1.3.5",
99
+ "ajv-merge-patch": "^4.1.0",
99
100
  "ajv-pack": "^0.3.1",
100
- "autocannon": "^3.2.0",
101
+ "autocannon": "^3.2.2",
101
102
  "branch-comparer": "^0.4.0",
102
- "concurrently": "^5.0.0",
103
+ "concurrently": "^5.1.0",
103
104
  "cors": "^2.8.5",
104
- "coveralls": "^3.0.6",
105
+ "coveralls": "^3.0.11",
105
106
  "dns-prefetch-control": "^0.2.0",
106
- "eslint": "^6.4.0",
107
- "eslint-import-resolver-node": "^0.3.2",
107
+ "eslint": "^6.7.2",
108
+ "eslint-import-resolver-node": "^0.3.3",
109
+ "events.once": "^2.0.2",
108
110
  "fast-json-body": "^1.1.0",
109
- "fastify-plugin": "^1.5.0",
110
- "fluent-schema": "^0.7.4",
111
- "form-data": "^2.5.0",
111
+ "fastify-plugin": "^1.6.1",
112
+ "fluent-schema": "^0.10.0",
113
+ "form-data": "^3.0.0",
112
114
  "frameguard": "^3.0.0",
113
- "h2url": "^0.1.2",
114
- "helmet": "^3.20.0",
115
+ "h2url": "^0.2.0",
116
+ "helmet": "^3.21.3",
115
117
  "hide-powered-by": "^1.0.0",
116
118
  "hsts": "^2.1.0",
117
119
  "http-errors": "^1.7.1",
@@ -128,28 +130,29 @@
128
130
  "simple-get": "^3.0.3",
129
131
  "snazzy": "^8.0.0",
130
132
  "split2": "^3.1.0",
131
- "standard": "^14.0.0",
133
+ "standard": "^14.3.3",
132
134
  "tap": "^12.5.2",
133
135
  "tap-mocha-reporter": "^3.0.7",
134
136
  "then-sleep": "^1.0.1",
135
- "typescript": "^3.6.3",
136
- "x-xss-protection": "^1.1.0"
137
+ "typescript": "^3.8.3",
138
+ "x-xss-protection": "^1.1.0",
139
+ "yup": "^0.28.3"
137
140
  },
138
141
  "dependencies": {
139
- "abstract-logging": "^1.0.0",
140
- "ajv": "^6.10.2",
141
- "avvio": "^6.2.2",
142
- "fast-json-stringify": "^1.15.5",
143
- "find-my-way": "^2.0.0",
142
+ "abstract-logging": "^2.0.0",
143
+ "ajv": "^6.12.0",
144
+ "avvio": "^6.3.1",
145
+ "fast-json-stringify": "^1.18.0",
146
+ "find-my-way": "^2.2.2",
144
147
  "flatstr": "^1.0.12",
145
- "light-my-request": "^3.4.1",
146
- "middie": "^4.0.1",
147
- "pino": "^5.13.2",
148
- "proxy-addr": "^2.0.4",
149
- "readable-stream": "^3.1.1",
148
+ "light-my-request": "^3.7.2",
149
+ "middie": "^4.1.0",
150
+ "pino": "^5.17.0",
151
+ "proxy-addr": "^2.0.6",
152
+ "readable-stream": "^3.6.0",
150
153
  "rfdc": "^1.1.2",
151
- "secure-json-parse": "^1.0.0",
152
- "tiny-lru": "^7.0.0"
154
+ "secure-json-parse": "^2.1.0",
155
+ "tiny-lru": "^7.0.2"
153
156
  },
154
157
  "greenkeeper": {
155
158
  "ignore": [
@@ -157,6 +160,7 @@
157
160
  "boom",
158
161
  "joi",
159
162
  "@types/node",
163
+ "semver",
160
164
  "tap",
161
165
  "tap-mocha-reporter",
162
166
  "@typescript-eslint/eslint-plugin",
package/test/404s.test.js CHANGED
@@ -1733,3 +1733,43 @@ test('Should fail to invoke callNotFound inside a 404 handler', t => {
1733
1733
  t.is(res.payload, '404 Not Found')
1734
1734
  })
1735
1735
  })
1736
+
1737
+ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => {
1738
+ t.test('Dyamic route', t => {
1739
+ t.plan(3)
1740
+ const fastify = Fastify()
1741
+ fastify.get('/hello/:id', () => t.fail('we should not be here'))
1742
+ fastify.inject({
1743
+ url: '/hello/%world',
1744
+ method: 'GET'
1745
+ }, (err, response) => {
1746
+ t.error(err)
1747
+ t.strictEqual(response.statusCode, 400)
1748
+ t.deepEqual(JSON.parse(response.payload), {
1749
+ error: 'Bad Request',
1750
+ message: "'%world' is not a valid url component",
1751
+ statusCode: 400
1752
+ })
1753
+ })
1754
+ })
1755
+
1756
+ t.test('Wildcard', t => {
1757
+ t.plan(3)
1758
+ const fastify = Fastify()
1759
+ fastify.get('*', () => t.fail('we should not be here'))
1760
+ fastify.inject({
1761
+ url: '/hello/%world',
1762
+ method: 'GET'
1763
+ }, (err, response) => {
1764
+ t.error(err)
1765
+ t.strictEqual(response.statusCode, 400)
1766
+ t.deepEqual(JSON.parse(response.payload), {
1767
+ error: 'Bad Request',
1768
+ message: "'/hello/%world' is not a valid url component",
1769
+ statusCode: 400
1770
+ })
1771
+ })
1772
+ })
1773
+
1774
+ t.end()
1775
+ })
@@ -660,3 +660,83 @@ test('a decorator should addSchema to all the encapsulated tree', t => {
660
660
 
661
661
  fastify.ready(t.error)
662
662
  })
663
+
664
+ test('after can access to a decorated instance and previous plugin decoration', t => {
665
+ t.plan(11)
666
+ const TEST_VALUE = {}
667
+ const OTHER_TEST_VALUE = {}
668
+ const NEW_TEST_VALUE = {}
669
+
670
+ const fastify = Fastify()
671
+
672
+ fastify.register(fp(function (instance, options, next) {
673
+ instance.decorate('test', TEST_VALUE)
674
+
675
+ next()
676
+ })).after(function (err, instance, done) {
677
+ t.error(err)
678
+ t.equal(instance.test, TEST_VALUE)
679
+
680
+ instance.decorate('test2', OTHER_TEST_VALUE)
681
+ done()
682
+ })
683
+
684
+ fastify.register(fp(function (instance, options, next) {
685
+ t.equal(instance.test, TEST_VALUE)
686
+ t.equal(instance.test2, OTHER_TEST_VALUE)
687
+
688
+ instance.decorate('test3', NEW_TEST_VALUE)
689
+
690
+ next()
691
+ })).after(function (err, instance, done) {
692
+ t.error(err)
693
+ t.equal(instance.test, TEST_VALUE)
694
+ t.equal(instance.test2, OTHER_TEST_VALUE)
695
+ t.equal(instance.test3, NEW_TEST_VALUE)
696
+
697
+ done()
698
+ })
699
+
700
+ fastify.get('/', function (req, res) {
701
+ t.equal(this.test, TEST_VALUE)
702
+ t.equal(this.test2, OTHER_TEST_VALUE)
703
+ res.send({})
704
+ })
705
+
706
+ fastify.inject('/')
707
+ .then(response => {
708
+ t.equal(response.statusCode, 200)
709
+ })
710
+ })
711
+
712
+ test('decorate* should throw if called after ready', t => {
713
+ t.plan(3)
714
+ const fastify = Fastify()
715
+
716
+ fastify.get('/', (request, reply) => {
717
+ reply.send({
718
+ hello: 'world'
719
+ })
720
+ })
721
+
722
+ fastify.listen(0)
723
+ .then(() => {
724
+ try {
725
+ fastify.decorate('test', true)
726
+ } catch (e) {
727
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
728
+ }
729
+ try {
730
+ fastify.decorateRequest('test', true)
731
+ } catch (e) {
732
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
733
+ }
734
+ try {
735
+ fastify.decorateReply('test', true)
736
+ } catch (e) {
737
+ t.is(e.message, "FST_ERR_DEC_AFTER_START: The decorator 'test' has been added after start!")
738
+ }
739
+ return fastify.close()
740
+ })
741
+ .catch(err => t.fail(err))
742
+ })
@@ -0,0 +1,13 @@
1
+ import t from 'tap'
2
+ import Fastify from '../../fastify.js'
3
+
4
+ t.test('esm support', async t => {
5
+ const fastify = Fastify()
6
+
7
+ fastify.register(import('./plugin.mjs'), { foo: 'bar' })
8
+ fastify.register(import('./other.mjs'))
9
+
10
+ await fastify.ready()
11
+
12
+ t.strictEqual(fastify.foo, 'bar')
13
+ })
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const semver = require('semver')
5
+
6
+ if (semver.lt(process.versions.node, '13.3.0')) {
7
+ t.skip('Skip because Node version <= 13.3.0')
8
+ t.end()
9
+ } else {
10
+ // Node v8 throw a `SyntaxError: Unexpected token import`
11
+ // even if this branch is never touch in the code,
12
+ // by using `eval` we can avoid this issue.
13
+ // eslint-disable-next-line
14
+ new Function('module', 'return import(module)')('./esm.mjs').catch((err) => {
15
+ process.nextTick(() => {
16
+ throw err
17
+ })
18
+ })
19
+ }
@@ -0,0 +1,7 @@
1
+ import t from 'tap'
2
+
3
+ async function other (fastify, opts) {
4
+ t.strictEqual(fastify.foo, 'bar')
5
+ }
6
+
7
+ export default other
@@ -0,0 +1,7 @@
1
+ async function plugin (fastify, opts) {
2
+ fastify.decorate('foo', opts.foo)
3
+ }
4
+
5
+ plugin[Symbol.for('skip-override')] = true
6
+
7
+ export default plugin