fastify 2.7.1 → 2.11.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 (72) hide show
  1. package/README.md +15 -4
  2. package/build/build-validation.js +8 -0
  3. package/docs/Benchmarking.md +2 -2
  4. package/docs/ContentTypeParser.md +12 -10
  5. package/docs/Decorators.md +14 -14
  6. package/docs/Ecosystem.md +7 -1
  7. package/docs/Errors.md +13 -8
  8. package/docs/Fluent-Schema.md +9 -12
  9. package/docs/Getting-Started.md +29 -25
  10. package/docs/HTTP2.md +1 -1
  11. package/docs/Hooks.md +201 -186
  12. package/docs/LTS.md +6 -7
  13. package/docs/Logging.md +10 -10
  14. package/docs/Middleware.md +59 -0
  15. package/docs/Plugins-Guide.md +52 -52
  16. package/docs/Plugins.md +3 -0
  17. package/docs/Reply.md +47 -3
  18. package/docs/Routes.md +120 -8
  19. package/docs/Server.md +69 -3
  20. package/docs/Serverless.md +76 -4
  21. package/docs/TypeScript.md +33 -10
  22. package/docs/Validation-and-Serialization.md +137 -1
  23. package/examples/typescript-server.ts +1 -1
  24. package/fastify.d.ts +52 -13
  25. package/fastify.js +68 -7
  26. package/lib/configValidator.js +99 -52
  27. package/lib/contentTypeParser.js +4 -4
  28. package/lib/context.js +2 -1
  29. package/lib/errors.js +21 -18
  30. package/lib/fourOhFour.js +10 -10
  31. package/lib/handleRequest.js +1 -2
  32. package/lib/logger.js +2 -2
  33. package/lib/pluginUtils.js +32 -0
  34. package/lib/reply.js +41 -6
  35. package/lib/route.js +37 -9
  36. package/lib/schemas.js +23 -12
  37. package/lib/symbols.js +4 -1
  38. package/lib/validation.js +15 -9
  39. package/lib/wrapThenable.js +1 -1
  40. package/package.json +34 -26
  41. package/test/404s.test.js +41 -1
  42. package/test/async-await.js +66 -0
  43. package/test/custom-parser.test.js +1 -1
  44. package/test/custom-querystring-parser.test.js +1 -1
  45. package/test/decorator.test.js +48 -0
  46. package/test/emit-warning.test.js +3 -3
  47. package/test/fastify-instance.test.js +29 -0
  48. package/test/helper.js +7 -7
  49. package/test/hooks-async.js +4 -3
  50. package/test/hooks.test.js +27 -8
  51. package/test/input-validation.test.js +126 -0
  52. package/test/internals/errors.test.js +9 -1
  53. package/test/internals/initialConfig.test.js +4 -2
  54. package/test/internals/plugin.test.js +4 -4
  55. package/test/internals/reply.test.js +78 -6
  56. package/test/internals/schemas.test.js +30 -0
  57. package/test/internals/validation.test.js +18 -0
  58. package/test/listen.test.js +1 -1
  59. package/test/logger.test.js +314 -1
  60. package/test/plugin.test.js +171 -0
  61. package/test/promises.test.js +55 -0
  62. package/test/proto-poisoning.test.js +76 -0
  63. package/test/route-hooks.test.js +109 -91
  64. package/test/route-prefix.test.js +1 -1
  65. package/test/schemas.test.js +450 -0
  66. package/test/shared-schemas.test.js +2 -2
  67. package/test/stream.test.js +10 -6
  68. package/test/throw.test.js +48 -2
  69. package/test/types/index.ts +86 -1
  70. package/test/validation-error-handling.test.js +3 -3
  71. package/test/versioned-routes.test.js +1 -1
  72. package/docs/Middlewares.md +0 -59
package/lib/route.js CHANGED
@@ -6,17 +6,28 @@ 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', 'onSend']
9
10
  const validation = require('./validation')
10
11
  const buildSchema = validation.build
11
12
  const { buildSchemaCompiler } = validation
12
13
  const { beforeHandlerWarning } = require('./warnings')
13
14
 
15
+ const {
16
+ codes: {
17
+ FST_ERR_SCH_BUILD,
18
+ FST_ERR_SCH_MISSING_COMPILER
19
+ }
20
+ } = require('./errors')
21
+
14
22
  const {
15
23
  kRoutePrefix,
16
24
  kLogLevel,
25
+ kLogSerializers,
17
26
  kHooks,
18
27
  kSchemas,
28
+ kOptions,
19
29
  kSchemaCompiler,
30
+ kSchemaResolver,
20
31
  kContentTypeParser,
21
32
  kReply,
22
33
  kReplySerializerDefault,
@@ -171,6 +182,10 @@ function buildRouting (options) {
171
182
  opts.prefix = prefix
172
183
  opts.logLevel = opts.logLevel || this[kLogLevel]
173
184
 
185
+ if (this[kLogSerializers] || opts.logSerializers) {
186
+ opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
187
+ }
188
+
174
189
  if (opts.attachValidation == null) {
175
190
  opts.attachValidation = false
176
191
  }
@@ -198,6 +213,7 @@ function buildRouting (options) {
198
213
  this._errorHandler,
199
214
  opts.bodyLimit,
200
215
  opts.logLevel,
216
+ opts.logSerializers,
201
217
  opts.attachValidation,
202
218
  this[kReplySerializerDefault]
203
219
  )
@@ -206,22 +222,26 @@ function buildRouting (options) {
206
222
  // not called for every single route. Creating a new one for every route
207
223
  // is going to be very expensive.
208
224
  if (opts.schema) {
225
+ if (this[kSchemaCompiler] == null && this[kSchemaResolver]) {
226
+ done(new FST_ERR_SCH_MISSING_COMPILER(opts.method, url))
227
+ return
228
+ }
229
+
209
230
  try {
210
231
  if (opts.schemaCompiler == null && this[kSchemaCompiler] == null) {
211
232
  const externalSchemas = this[kSchemas].getJsonSchemas({ onlyAbsoluteUri: true })
212
- this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, schemaCache))
233
+ this.setSchemaCompiler(buildSchemaCompiler(externalSchemas, this[kOptions].ajv, schemaCache))
213
234
  }
214
235
 
215
- buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas])
236
+ buildSchema(context, opts.schemaCompiler || this[kSchemaCompiler], this[kSchemas], this[kSchemaResolver])
216
237
  } catch (error) {
217
- done(error)
238
+ // bubble up the FastifyError instance
239
+ done(error.code ? error : new FST_ERR_SCH_BUILD(opts.method, url, error.message))
218
240
  return
219
241
  }
220
242
  }
221
243
 
222
- const hooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization']
223
-
224
- for (const hook of hooks) {
244
+ for (const hook of supportedHooks) {
225
245
  if (opts[hook]) {
226
246
  if (Array.isArray(opts[hook])) {
227
247
  opts[hook] = opts[hook].map(fn => fn.bind(this))
@@ -250,7 +270,7 @@ function buildRouting (options) {
250
270
  context.onError = onError.length ? onError : null
251
271
  context.onResponse = onResponse.length ? onResponse : null
252
272
 
253
- for (const hook of hooks) {
273
+ for (const hook of supportedHooks) {
254
274
  const toSet = this[kHooks][hook].concat(opts[hook] || [])
255
275
  context[hook] = toSet.length ? toSet : null
256
276
  }
@@ -287,7 +307,7 @@ function buildRouting (options) {
287
307
 
288
308
  req.id = req.headers[requestIdHeader] || genReqId(req)
289
309
  req.originalUrl = req.url
290
- var hostname = req.headers['host']
310
+ var hostname = req.headers.host
291
311
  var ip = req.connection.remoteAddress
292
312
  var ips
293
313
 
@@ -299,7 +319,15 @@ function buildRouting (options) {
299
319
  }
300
320
  }
301
321
 
302
- var childLogger = logger.child({ [requestIdLogLabel]: req.id, level: context.logLevel })
322
+ var loggerOpts = {
323
+ [requestIdLogLabel]: req.id,
324
+ level: context.logLevel
325
+ }
326
+
327
+ if (context.logSerializers) {
328
+ loggerOpts.serializers = context.logSerializers
329
+ }
330
+ var childLogger = logger.child(loggerOpts)
303
331
  childLogger[kDisableRequestLogging] = disableRequestLogging
304
332
 
305
333
  // added hostname, ip, and ips back to the Node req object to maintain backward compatibility
package/lib/schemas.js CHANGED
@@ -18,12 +18,12 @@ function Schemas () {
18
18
  this.store = {}
19
19
  }
20
20
 
21
- Schemas.prototype.add = function (inputSchema) {
22
- var schema = fastClone(inputSchema[kFluentSchema]
21
+ Schemas.prototype.add = function (inputSchema, refResolver) {
22
+ var schema = fastClone((inputSchema.isFluentSchema || inputSchema[kFluentSchema])
23
23
  ? inputSchema.valueOf()
24
24
  : inputSchema
25
25
  )
26
- const id = schema['$id']
26
+ const id = schema.$id
27
27
  if (id === undefined) {
28
28
  throw new FST_ERR_SCH_MISSING_ID()
29
29
  }
@@ -32,17 +32,17 @@ Schemas.prototype.add = function (inputSchema) {
32
32
  throw new FST_ERR_SCH_ALREADY_PRESENT(id)
33
33
  }
34
34
 
35
- this.store[id] = this.resolveRefs(schema, true)
35
+ this.store[id] = this.resolveRefs(schema, true, refResolver)
36
36
  }
37
37
 
38
38
  Schemas.prototype.resolve = function (id) {
39
39
  if (this.store[id] === undefined) {
40
40
  throw new FST_ERR_SCH_NOT_PRESENT(id)
41
41
  }
42
- return this.store[id]
42
+ return Object.assign({}, this.store[id])
43
43
  }
44
44
 
45
- Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
45
+ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId, refResolver) {
46
46
  // alias query to querystring schema
47
47
  if (routeSchemas.query) {
48
48
  // check if our schema has both querystring and query
@@ -65,7 +65,7 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
65
65
  try {
66
66
  // this will work only for standard json schemas
67
67
  // other compilers such as Joi will fail
68
- this.traverse(routeSchemas)
68
+ this.traverse(routeSchemas, refResolver)
69
69
 
70
70
  // when a plugin uses the 'skip-override' and call addSchema
71
71
  // the same JSON will be pass throug all the avvio tree. In this case
@@ -99,15 +99,26 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
99
99
  return routeSchemas
100
100
  }
101
101
 
102
- Schemas.prototype.traverse = function (schema) {
102
+ Schemas.prototype.traverse = function (schema, refResolver) {
103
103
  for (var key in schema) {
104
104
  // resolve the `sharedSchemaId#' only if is not a standard $ref JSON Pointer
105
105
  if (typeof schema[key] === 'string' && key !== '$schema' && key !== '$ref' && schema[key].slice(-1) === '#') {
106
106
  schema[key] = this.resolve(schema[key].slice(0, -1))
107
+ } else if (key === '$ref' && refResolver) {
108
+ const refValue = schema[key]
109
+
110
+ const framePos = refValue.indexOf('#')
111
+ const refId = framePos >= 0 ? refValue.slice(0, framePos) : refValue
112
+ if (refId.length > 0 && !this.store[refId]) {
113
+ const resolvedSchema = refResolver(refId)
114
+ if (resolvedSchema) {
115
+ this.add(resolvedSchema, refResolver)
116
+ }
117
+ }
107
118
  }
108
119
 
109
120
  if (schema[key] !== null && typeof schema[key] === 'object') {
110
- this.traverse(schema[key])
121
+ this.traverse(schema[key], refResolver)
111
122
  }
112
123
  }
113
124
  }
@@ -124,7 +135,7 @@ Schemas.prototype.cleanId = function (schema) {
124
135
  }
125
136
 
126
137
  Schemas.prototype.getSchemaAnyway = function (schema) {
127
- if (schema.oneOf || schema.allOf || schema.anyOf) return schema
138
+ if (schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
128
139
  if (!schema.type || !schema.properties) {
129
140
  return {
130
141
  type: 'object',
@@ -142,8 +153,8 @@ Schemas.prototype.getJsonSchemas = function (options) {
142
153
  const store = this.getSchemas()
143
154
  const schemasArray = Object.keys(store).map(schemaKey => {
144
155
  // if the shared-schema "replace-way" has been used, the $id field has been removed
145
- if (store[schemaKey]['$id'] === undefined) {
146
- store[schemaKey]['$id'] = schemaKey
156
+ if (store[schemaKey].$id === undefined) {
157
+ store[schemaKey].$id = schemaKey
147
158
  }
148
159
  return store[schemaKey]
149
160
  })
package/lib/symbols.js CHANGED
@@ -5,9 +5,11 @@ 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'),
12
+ kSchemaResolver: Symbol('fastify.schemaRefResolver'),
11
13
  kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
12
14
  kContentTypeParser: Symbol('fastify.contentTypeParser'),
13
15
  kReply: Symbol('fastify.Reply'),
@@ -30,7 +32,8 @@ const keys = {
30
32
  kState: Symbol('fastify.state'),
31
33
  kOptions: Symbol('fastify.options'),
32
34
  kGlobalHooks: Symbol('fastify.globalHooks'),
33
- kDisableRequestLogging: Symbol('fastify.disableRequestLogging')
35
+ kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
36
+ kPluginNameChain: Symbol('fastify.pluginNameChain')
34
37
  }
35
38
 
36
39
  module.exports = keys
package/lib/validation.js CHANGED
@@ -22,13 +22,13 @@ function getResponseSchema (responseSchemaDefinition, sharedSchemas) {
22
22
  }, {})
23
23
  }
24
24
 
25
- function build (context, compile, schemas) {
25
+ function build (context, compile, schemas, schemaResolver) {
26
26
  if (!context.schema) {
27
27
  return
28
28
  }
29
29
 
30
30
  generateFluentSchema(context.schema)
31
- context.schema = schemas.resolveRefs(context.schema)
31
+ context.schema = schemas.resolveRefs(context.schema, false, schemaResolver)
32
32
 
33
33
  const headers = context.schema.headers
34
34
 
@@ -44,6 +44,7 @@ function build (context, compile, schemas) {
44
44
  headersSchemaLowerCase.required = headersSchemaLowerCase.required.map(h => h.toLowerCase())
45
45
  }
46
46
  if (headers.properties) {
47
+ headersSchemaLowerCase.properties = {}
47
48
  Object.keys(headers.properties).forEach(k => {
48
49
  headersSchemaLowerCase.properties[k.toLowerCase()] = headers.properties[k]
49
50
  })
@@ -70,14 +71,14 @@ function build (context, compile, schemas) {
70
71
 
71
72
  function generateFluentSchema (schema) {
72
73
  ;['params', 'body', 'querystring', 'query', 'headers'].forEach(key => {
73
- if (schema[key] && schema[key][kFluentSchema]) {
74
+ if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
74
75
  schema[key] = schema[key].valueOf()
75
76
  }
76
77
  })
77
78
 
78
79
  if (schema.response) {
79
80
  Object.keys(schema.response).forEach(code => {
80
- if (schema.response[code][kFluentSchema]) {
81
+ if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
81
82
  schema.response[code] = schema.response[code].valueOf()
82
83
  }
83
84
  })
@@ -162,17 +163,22 @@ function schemaErrorsText (errors, dataVar) {
162
163
  return text.slice(0, -separator.length)
163
164
  }
164
165
 
165
- function buildSchemaCompiler (externalSchemas, cache) {
166
+ function buildSchemaCompiler (externalSchemas, options, cache) {
166
167
  // This instance of Ajv is private
167
168
  // it should not be customized or used
168
- const ajv = new Ajv({
169
+ const ajv = new Ajv(Object.assign({
169
170
  coerceTypes: true,
170
171
  useDefaults: true,
171
172
  removeAdditional: true,
172
173
  allErrors: true,
173
- nullable: true,
174
- cache
175
- })
174
+ nullable: true
175
+ }, options.customOptions, { cache }))
176
+
177
+ if (options.plugins && options.plugins.length > 0) {
178
+ for (const plugin of options.plugins) {
179
+ plugin[0](ajv, plugin[1])
180
+ }
181
+ }
176
182
 
177
183
  if (Array.isArray(externalSchemas)) {
178
184
  externalSchemas.forEach(s => ajv.addSchema(s))
@@ -31,7 +31,7 @@ function wrapThenable (thenable, reply) {
31
31
  reply.send(err)
32
32
  }
33
33
  } else if (reply[kReplySent] === false) {
34
- reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULLFILLED() }, `Promise may not be fulfilled with 'undefined' when statusCode is not 204`)
34
+ reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULLFILLED() }, "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
35
35
  }
36
36
  }, function (err) {
37
37
  if (reply[kReplySentOverwritten] === true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "2.7.1",
3
+ "version": "2.11.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "typings": "fastify.d.ts",
@@ -15,10 +15,10 @@
15
15
  "test:report": "npm run lint && npm run unit:report && npm run typescript",
16
16
  "test": "npm run lint && npm run unit && npm run typescript",
17
17
  "coverage": "npm run unit -- --cov --coverage-report=html",
18
- "coveralls": "npm run unit -- --cov",
18
+ "test:ci": "npm run lint && npm run unit -- --cov --coverage-report=lcovonly && npm run typescript",
19
19
  "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
20
20
  "benchmark": "npx concurrently -k -s first \"node ./examples/simple.js\" \"npx autocannon -c 100 -d 5 -p 10 localhost:3000/\"",
21
- "license-checker": "license-checker --production --onlyAllow='MIT;ISC;BSD-3-Clause;BSD-2-Clause'"
21
+ "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\""
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
@@ -77,6 +77,10 @@
77
77
  {
78
78
  "name": "James Sumners",
79
79
  "url": "https://james.sumners.info"
80
+ },
81
+ {
82
+ "name": "Denis Fäcke",
83
+ "url": "https://github.com/SerayaEryn"
80
84
  }
81
85
  ],
82
86
  "license": "MIT",
@@ -88,24 +92,26 @@
88
92
  "node": ">=6"
89
93
  },
90
94
  "devDependencies": {
91
- "@types/node": "^11.13.18",
92
- "@typescript-eslint/eslint-plugin": "^1.13.0",
93
- "@typescript-eslint/parser": "^1.13.0",
95
+ "@types/node": "^12.0.0",
96
+ "@typescript-eslint/eslint-plugin": "^2.10.0",
97
+ "@typescript-eslint/parser": "^2.10.0",
94
98
  "JSONStream": "^1.3.5",
99
+ "ajv-merge-patch": "^4.1.0",
95
100
  "ajv-pack": "^0.3.1",
96
- "autocannon": "^3.2.0",
101
+ "autocannon": "^3.2.2",
97
102
  "branch-comparer": "^0.4.0",
98
- "concurrently": "^4.1.1",
103
+ "concurrently": "^5.0.0",
99
104
  "cors": "^2.8.5",
100
- "coveralls": "^3.0.5",
105
+ "coveralls": "^3.0.9",
101
106
  "dns-prefetch-control": "^0.2.0",
107
+ "eslint": "^6.7.2",
102
108
  "eslint-import-resolver-node": "^0.3.2",
103
109
  "fast-json-body": "^1.1.0",
104
110
  "fastify-plugin": "^1.5.0",
105
- "fluent-schema": "^0.7.3",
106
- "form-data": "^2.5.0",
111
+ "fluent-schema": "^0.9.0",
112
+ "form-data": "^3.0.0",
107
113
  "frameguard": "^3.0.0",
108
- "h2url": "^0.1.2",
114
+ "h2url": "^0.2.0",
109
115
  "helmet": "^3.20.0",
110
116
  "hide-powered-by": "^1.0.0",
111
117
  "hsts": "^2.1.0",
@@ -113,9 +119,9 @@
113
119
  "ienoopen": "^1.0.0",
114
120
  "joi": "^12.0.0",
115
121
  "license-checker": "^25.0.1",
116
- "lolex": "^4.0.1",
122
+ "lolex": "^4.2.0",
117
123
  "pre-commit": "^1.2.2",
118
- "proxyquire": "^2.1.1",
124
+ "proxyquire": "^2.1.3",
119
125
  "pump": "^3.0.0",
120
126
  "semver": "^6.3.0",
121
127
  "send": "^0.17.0",
@@ -123,28 +129,28 @@
123
129
  "simple-get": "^3.0.3",
124
130
  "snazzy": "^8.0.0",
125
131
  "split2": "^3.1.0",
126
- "standard": "^13.0.1",
132
+ "standard": "^14.0.0",
127
133
  "tap": "^12.5.2",
128
134
  "tap-mocha-reporter": "^3.0.7",
129
135
  "then-sleep": "^1.0.1",
130
- "typescript": "^3.5.3",
136
+ "typescript": "^3.7.3",
131
137
  "x-xss-protection": "^1.1.0"
132
138
  },
133
139
  "dependencies": {
134
- "abstract-logging": "^1.0.0",
140
+ "abstract-logging": "^2.0.0",
135
141
  "ajv": "^6.10.2",
136
- "avvio": "^6.1.1",
137
- "fast-json-stringify": "^1.15.0",
138
- "find-my-way": "^2.0.0",
142
+ "avvio": "^6.2.2",
143
+ "fast-json-stringify": "^1.15.7",
144
+ "find-my-way": "^2.2.0",
139
145
  "flatstr": "^1.0.12",
140
- "light-my-request": "^3.4.1",
141
- "middie": "^4.0.1",
142
- "pino": "^5.13.1",
146
+ "light-my-request": "^3.6.2",
147
+ "middie": "^4.1.0",
148
+ "pino": "^5.14.0",
143
149
  "proxy-addr": "^2.0.4",
144
150
  "readable-stream": "^3.1.1",
145
151
  "rfdc": "^1.1.2",
146
- "secure-json-parse": "^1.0.0",
147
- "tiny-lru": "^6.0.1"
152
+ "secure-json-parse": "^2.0.0",
153
+ "tiny-lru": "^7.0.2"
148
154
  },
149
155
  "greenkeeper": {
150
156
  "ignore": [
@@ -153,7 +159,9 @@
153
159
  "joi",
154
160
  "@types/node",
155
161
  "tap",
156
- "tap-mocha-reporter"
162
+ "tap-mocha-reporter",
163
+ "@typescript-eslint/eslint-plugin",
164
+ "lolex"
157
165
  ]
158
166
  },
159
167
  "standard": {
package/test/404s.test.js CHANGED
@@ -979,7 +979,7 @@ test('log debug for 404', t => {
979
979
 
980
980
  const INFO_LEVEL = 30
981
981
  t.strictEqual(JSON.parse(logStream.logs[0]).msg, 'incoming request')
982
- t.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Not Found')
982
+ t.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found')
983
983
  t.strictEqual(JSON.parse(logStream.logs[1]).level, INFO_LEVEL)
984
984
  t.strictEqual(JSON.parse(logStream.logs[2]).msg, 'request completed')
985
985
  t.strictEqual(logStream.logs.length, 3)
@@ -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
+ })
@@ -205,6 +205,72 @@ function asyncTest (t) {
205
205
  })
206
206
  })
207
207
 
208
+ test('await reply if we will be calling reply.send in the future', t => {
209
+ const lines = ['incoming request', 'request completed']
210
+ t.plan(lines.length + 2)
211
+
212
+ const splitStream = split(JSON.parse)
213
+ splitStream.on('data', (line) => {
214
+ t.is(line.msg, lines.shift())
215
+ })
216
+
217
+ const server = Fastify({
218
+ logger: {
219
+ stream: splitStream
220
+ }
221
+ })
222
+ const payload = { hello: 'world' }
223
+
224
+ server.get('/', async function awaitMyFunc (req, reply) {
225
+ setImmediate(function () {
226
+ reply.send(payload)
227
+ })
228
+
229
+ await reply
230
+ })
231
+
232
+ server.inject({
233
+ method: 'GET',
234
+ url: '/'
235
+ }, (err, res) => {
236
+ t.error(err)
237
+ const payload = JSON.parse(res.payload)
238
+ t.deepEqual(payload, { hello: 'world' })
239
+ })
240
+ })
241
+
242
+ test('await reply if we will be calling reply.send in the future (error case)', t => {
243
+ const lines = ['incoming request', 'kaboom', 'request completed']
244
+ t.plan(lines.length + 2)
245
+
246
+ const splitStream = split(JSON.parse)
247
+ splitStream.on('data', (line) => {
248
+ t.is(line.msg, lines.shift())
249
+ })
250
+
251
+ const server = Fastify({
252
+ logger: {
253
+ stream: splitStream
254
+ }
255
+ })
256
+
257
+ server.get('/', async function awaitMyFunc (req, reply) {
258
+ setImmediate(function () {
259
+ reply.send(new Error('kaboom'))
260
+ })
261
+
262
+ await reply
263
+ })
264
+
265
+ server.inject({
266
+ method: 'GET',
267
+ url: '/'
268
+ }, (err, res) => {
269
+ t.error(err)
270
+ t.equal(res.statusCode, 500)
271
+ })
272
+ })
273
+
208
274
  test('support reply decorators with await', t => {
209
275
  t.plan(2)
210
276
 
@@ -1060,7 +1060,7 @@ test('Wrong parseAs parameter', t => {
1060
1060
  fastify.addContentTypeParser('application/json', { parseAs: 'fireworks' }, () => {})
1061
1061
  t.fail('should throw')
1062
1062
  } catch (err) {
1063
- t.is(err.message, `FST_ERR_CTP_INVALID_PARSE_TYPE: The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.`)
1063
+ t.is(err.message, "FST_ERR_CTP_INVALID_PARSE_TYPE: The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.")
1064
1064
  }
1065
1065
  })
1066
1066
 
@@ -131,7 +131,7 @@ test('Custom querystring parser should be a function', t => {
131
131
  } catch (err) {
132
132
  t.strictEqual(
133
133
  err.message,
134
- `querystringParser option should be a function, instead got 'number'`
134
+ "querystringParser option should be a function, instead got 'number'"
135
135
  )
136
136
  }
137
137
  })
@@ -660,3 +660,51 @@ 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
+ })
@@ -7,7 +7,7 @@ test('should emit warning using genReqId prop in logger options', t => {
7
7
  t.plan(1)
8
8
 
9
9
  process.once('warning', warning => {
10
- t.strictEqual(warning.message, `Using 'genReqId' in logger options is deprecated. Use fastify options instead. See: https://www.fastify.io/docs/latest/Server/#gen-request-id`)
10
+ t.strictEqual(warning.message, "Using 'genReqId' in logger options is deprecated. Use fastify options instead. See: https://www.fastify.io/docs/latest/Server/#gen-request-id")
11
11
  })
12
12
 
13
13
  Fastify({ logger: { genReqId: 'test' } })
@@ -17,7 +17,7 @@ test('should emit warning if basePath prop is used', t => {
17
17
  t.plan(1)
18
18
 
19
19
  process.once('warning', warning => {
20
- t.strictEqual(warning.message, `basePath is deprecated. Use prefix instead. See: https://www.fastify.io/docs/latest/Server/#prefix`)
20
+ t.strictEqual(warning.message, 'basePath is deprecated. Use prefix instead. See: https://www.fastify.io/docs/latest/Server/#prefix')
21
21
  })
22
22
 
23
23
  const fastify = Fastify({ basePath: '/test' })
@@ -28,7 +28,7 @@ test('should emit warning if preHandler is used', t => {
28
28
  t.plan(1)
29
29
 
30
30
  process.once('warning', warning => {
31
- t.strictEqual(warning.message, `The route option \`beforeHandler\` has been deprecated, use \`preHandler\` instead`)
31
+ t.strictEqual(warning.message, 'The route option `beforeHandler` has been deprecated, use `preHandler` instead')
32
32
  })
33
33
 
34
34
  const fastify = Fastify()