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.
- package/README.md +15 -4
- package/build/build-validation.js +8 -0
- package/docs/Benchmarking.md +2 -2
- package/docs/ContentTypeParser.md +12 -10
- package/docs/Decorators.md +14 -14
- package/docs/Ecosystem.md +7 -1
- package/docs/Errors.md +13 -8
- package/docs/Fluent-Schema.md +9 -12
- package/docs/Getting-Started.md +29 -25
- package/docs/HTTP2.md +1 -1
- package/docs/Hooks.md +201 -186
- package/docs/LTS.md +6 -7
- package/docs/Logging.md +10 -10
- package/docs/Middleware.md +59 -0
- package/docs/Plugins-Guide.md +52 -52
- package/docs/Plugins.md +3 -0
- package/docs/Reply.md +47 -3
- package/docs/Routes.md +120 -8
- package/docs/Server.md +69 -3
- package/docs/Serverless.md +76 -4
- package/docs/TypeScript.md +33 -10
- package/docs/Validation-and-Serialization.md +137 -1
- package/examples/typescript-server.ts +1 -1
- package/fastify.d.ts +52 -13
- package/fastify.js +68 -7
- package/lib/configValidator.js +99 -52
- package/lib/contentTypeParser.js +4 -4
- package/lib/context.js +2 -1
- package/lib/errors.js +21 -18
- package/lib/fourOhFour.js +10 -10
- package/lib/handleRequest.js +1 -2
- package/lib/logger.js +2 -2
- package/lib/pluginUtils.js +32 -0
- package/lib/reply.js +41 -6
- package/lib/route.js +37 -9
- package/lib/schemas.js +23 -12
- package/lib/symbols.js +4 -1
- package/lib/validation.js +15 -9
- package/lib/wrapThenable.js +1 -1
- package/package.json +34 -26
- package/test/404s.test.js +41 -1
- package/test/async-await.js +66 -0
- package/test/custom-parser.test.js +1 -1
- package/test/custom-querystring-parser.test.js +1 -1
- package/test/decorator.test.js +48 -0
- package/test/emit-warning.test.js +3 -3
- package/test/fastify-instance.test.js +29 -0
- package/test/helper.js +7 -7
- package/test/hooks-async.js +4 -3
- package/test/hooks.test.js +27 -8
- package/test/input-validation.test.js +126 -0
- package/test/internals/errors.test.js +9 -1
- package/test/internals/initialConfig.test.js +4 -2
- package/test/internals/plugin.test.js +4 -4
- package/test/internals/reply.test.js +78 -6
- package/test/internals/schemas.test.js +30 -0
- package/test/internals/validation.test.js +18 -0
- package/test/listen.test.js +1 -1
- package/test/logger.test.js +314 -1
- package/test/plugin.test.js +171 -0
- package/test/promises.test.js +55 -0
- package/test/proto-poisoning.test.js +76 -0
- package/test/route-hooks.test.js +109 -91
- package/test/route-prefix.test.js +1 -1
- package/test/schemas.test.js +450 -0
- package/test/shared-schemas.test.js +2 -2
- package/test/stream.test.js +10 -6
- package/test/throw.test.js +48 -2
- package/test/types/index.ts +86 -1
- package/test/validation-error-handling.test.js +3 -3
- package/test/versioned-routes.test.js +1 -1
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
146
|
-
store[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
|
-
|
|
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))
|
package/lib/wrapThenable.js
CHANGED
|
@@ -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() },
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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": "^
|
|
92
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
93
|
-
"@typescript-eslint/parser": "^
|
|
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.
|
|
101
|
+
"autocannon": "^3.2.2",
|
|
97
102
|
"branch-comparer": "^0.4.0",
|
|
98
|
-
"concurrently": "^
|
|
103
|
+
"concurrently": "^5.0.0",
|
|
99
104
|
"cors": "^2.8.5",
|
|
100
|
-
"coveralls": "^3.0.
|
|
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.
|
|
106
|
-
"form-data": "^
|
|
111
|
+
"fluent-schema": "^0.9.0",
|
|
112
|
+
"form-data": "^3.0.0",
|
|
107
113
|
"frameguard": "^3.0.0",
|
|
108
|
-
"h2url": "^0.
|
|
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
|
|
122
|
+
"lolex": "^4.2.0",
|
|
117
123
|
"pre-commit": "^1.2.2",
|
|
118
|
-
"proxyquire": "^2.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": "^
|
|
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.
|
|
136
|
+
"typescript": "^3.7.3",
|
|
131
137
|
"x-xss-protection": "^1.1.0"
|
|
132
138
|
},
|
|
133
139
|
"dependencies": {
|
|
134
|
-
"abstract-logging": "^
|
|
140
|
+
"abstract-logging": "^2.0.0",
|
|
135
141
|
"ajv": "^6.10.2",
|
|
136
|
-
"avvio": "^6.
|
|
137
|
-
"fast-json-stringify": "^1.15.
|
|
138
|
-
"find-my-way": "^2.
|
|
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.
|
|
141
|
-
"middie": "^4.0
|
|
142
|
-
"pino": "^5.
|
|
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": "^
|
|
147
|
-
"tiny-lru": "^
|
|
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, '
|
|
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
|
+
})
|
package/test/async-await.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
134
|
+
"querystringParser option should be a function, instead got 'number'"
|
|
135
135
|
)
|
|
136
136
|
}
|
|
137
137
|
})
|
package/test/decorator.test.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
31
|
+
t.strictEqual(warning.message, 'The route option `beforeHandler` has been deprecated, use `preHandler` instead')
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
const fastify = Fastify()
|