fastify 2.4.1 → 2.7.1
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 +1 -1
- package/SECURITY.md +33 -0
- package/build/build-validation.js +1 -1
- package/docs/Decorators.md +2 -2
- package/docs/Ecosystem.md +5 -1
- package/docs/Fluent-Schema.md +5 -7
- package/docs/Hooks.md +40 -40
- package/docs/Logging.md +15 -3
- package/docs/Plugins-Guide.md +21 -21
- package/docs/Plugins.md +11 -11
- package/docs/Reply.md +14 -7
- package/docs/Routes.md +6 -6
- package/docs/Server.md +40 -19
- package/docs/Serverless.md +127 -28
- package/docs/Validation-and-Serialization.md +15 -12
- package/fastify.d.ts +22 -14
- package/fastify.js +21 -0
- package/lib/context.js +5 -4
- package/lib/decorate.js +2 -0
- package/lib/errors.js +1 -0
- package/lib/handleRequest.js +2 -2
- package/lib/reply.js +23 -6
- package/lib/request.js +2 -2
- package/lib/route.js +24 -15
- package/lib/schemas.js +24 -3
- package/lib/symbols.js +1 -0
- package/lib/validation.js +19 -0
- package/package.json +19 -18
- package/test/async-await.js +1 -1
- package/test/close-pipelining.test.js +43 -2
- package/test/close.test.js +69 -12
- package/test/content-length.test.js +2 -2
- package/test/decorator.test.js +2 -0
- package/test/fluent-schema.js +54 -0
- package/test/hooks-async.js +80 -1
- package/test/hooks.test.js +4 -4
- package/test/http2/closing.js +86 -33
- package/test/input-validation.test.js +1 -1
- package/test/internals/decorator.test.js +2 -0
- package/test/internals/initialConfig.test.js +1 -1
- package/test/internals/reply.test.js +276 -1
- package/test/internals/validation.test.js +57 -0
- package/test/logger.test.js +33 -1
- package/test/nullable-validation.test.js +52 -0
- package/test/plugin.test.js +2 -0
- package/test/register.test.js +2 -0
- package/test/route-prefix.test.js +31 -0
- package/test/route.test.js +35 -0
- package/test/shared-schemas.test.js +3 -3
- package/test/types/index.ts +33 -2
- package/test/versioned-routes.test.js +3 -3
|
@@ -74,6 +74,37 @@ test('build schema - payload schema', t => {
|
|
|
74
74
|
t.is(typeof opts[symbols.bodySchema], 'function')
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
+
test('build schema - query schema', t => {
|
|
78
|
+
t.plan(2)
|
|
79
|
+
const opts = {
|
|
80
|
+
schema: {
|
|
81
|
+
query: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
hello: { type: 'string' }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
validation.build(opts, schema => ajv.compile(schema), new Schemas())
|
|
90
|
+
t.type(opts[symbols.querystringSchema].schema.type, 'string')
|
|
91
|
+
t.is(typeof opts[symbols.querystringSchema], 'function')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('build schema - query schema abbreviated', t => {
|
|
95
|
+
t.plan(2)
|
|
96
|
+
const opts = {
|
|
97
|
+
schema: {
|
|
98
|
+
query: {
|
|
99
|
+
hello: { type: 'string' }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
validation.build(opts, schema => ajv.compile(schema), new Schemas())
|
|
104
|
+
t.type(opts[symbols.querystringSchema].schema.type, 'string')
|
|
105
|
+
t.is(typeof opts[symbols.querystringSchema], 'function')
|
|
106
|
+
})
|
|
107
|
+
|
|
77
108
|
test('build schema - querystring schema', t => {
|
|
78
109
|
t.plan(2)
|
|
79
110
|
const opts = {
|
|
@@ -105,6 +136,32 @@ test('build schema - querystring schema abbreviated', t => {
|
|
|
105
136
|
t.is(typeof opts[symbols.querystringSchema], 'function')
|
|
106
137
|
})
|
|
107
138
|
|
|
139
|
+
test('build schema - must throw if querystring and query schema exist', t => {
|
|
140
|
+
t.plan(2)
|
|
141
|
+
try {
|
|
142
|
+
const opts = {
|
|
143
|
+
schema: {
|
|
144
|
+
query: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {
|
|
147
|
+
hello: { type: 'string' }
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
querystring: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
hello: { type: 'string' }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
validation.build(opts, schema => ajv.compile(schema), new Schemas())
|
|
159
|
+
} catch (err) {
|
|
160
|
+
t.is(err.code, 'FST_ERR_SCH_DUPLICATE')
|
|
161
|
+
t.is(err.message, 'FST_ERR_SCH_DUPLICATE: Schema with \'querystring\' already present!')
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
108
165
|
test('build schema - params schema', t => {
|
|
109
166
|
t.plan(1)
|
|
110
167
|
const opts = {
|
package/test/logger.test.js
CHANGED
|
@@ -1072,6 +1072,38 @@ test('should not log the error if error handler is defined', t => {
|
|
|
1072
1072
|
})
|
|
1073
1073
|
})
|
|
1074
1074
|
|
|
1075
|
+
test('should not rely on raw request to log errors', t => {
|
|
1076
|
+
t.plan(7)
|
|
1077
|
+
const stream = split(JSON.parse)
|
|
1078
|
+
const fastify = Fastify({
|
|
1079
|
+
modifyCoreObjects: false,
|
|
1080
|
+
logger: {
|
|
1081
|
+
stream: stream,
|
|
1082
|
+
level: 'info'
|
|
1083
|
+
}
|
|
1084
|
+
})
|
|
1085
|
+
fastify.get('/error', function (req, reply) {
|
|
1086
|
+
t.ok(req.log)
|
|
1087
|
+
reply.status(415).send(new Error('something happened'))
|
|
1088
|
+
})
|
|
1089
|
+
fastify.listen(0, err => {
|
|
1090
|
+
t.error(err)
|
|
1091
|
+
fastify.server.unref()
|
|
1092
|
+
http.get('http://localhost:' + fastify.server.address().port + '/error')
|
|
1093
|
+
stream.once('data', listenAtLogLine => {
|
|
1094
|
+
t.ok(listenAtLogLine, 'listen at log message is ok')
|
|
1095
|
+
stream.once('data', line => {
|
|
1096
|
+
t.equal(line.msg, 'incoming request', 'message is set')
|
|
1097
|
+
stream.once('data', line => {
|
|
1098
|
+
t.equal(line.level, 30, 'level is correct')
|
|
1099
|
+
t.equal(line.msg, 'something happened', 'message is set')
|
|
1100
|
+
t.deepEqual(line.res, { statusCode: 415 }, 'status code is set')
|
|
1101
|
+
})
|
|
1102
|
+
})
|
|
1103
|
+
})
|
|
1104
|
+
})
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1075
1107
|
test('should redact the authorization header if so specified', t => {
|
|
1076
1108
|
t.plan(7)
|
|
1077
1109
|
const stream = split(JSON.parse)
|
|
@@ -1111,7 +1143,7 @@ test('should redact the authorization header if so specified', t => {
|
|
|
1111
1143
|
method: 'GET',
|
|
1112
1144
|
url: 'http://localhost:' + fastify.server.address().port,
|
|
1113
1145
|
headers: {
|
|
1114
|
-
|
|
1146
|
+
authorization: 'Bearer abcde'
|
|
1115
1147
|
}
|
|
1116
1148
|
}, (err, response, body) => {
|
|
1117
1149
|
t.error(err)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const test = t.test
|
|
5
|
+
const Fastify = require('..')
|
|
6
|
+
|
|
7
|
+
test('nullable string', t => {
|
|
8
|
+
t.plan(3)
|
|
9
|
+
const fastify = Fastify()
|
|
10
|
+
fastify.route({
|
|
11
|
+
method: 'POST',
|
|
12
|
+
url: '/',
|
|
13
|
+
handler: (req, reply) => {
|
|
14
|
+
t.same(req.body.hello, null)
|
|
15
|
+
reply.code(200).send(req.body)
|
|
16
|
+
},
|
|
17
|
+
schema: {
|
|
18
|
+
body: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
hello: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
format: 'email',
|
|
24
|
+
nullable: true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
response: {
|
|
29
|
+
200: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
hello: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
format: 'email',
|
|
35
|
+
nullable: true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
fastify.inject({
|
|
43
|
+
method: 'POST',
|
|
44
|
+
url: '/',
|
|
45
|
+
body: {
|
|
46
|
+
hello: null
|
|
47
|
+
}
|
|
48
|
+
}, (err, res) => {
|
|
49
|
+
t.error(err)
|
|
50
|
+
t.same(res.payload.hello, null)
|
|
51
|
+
})
|
|
52
|
+
})
|
package/test/plugin.test.js
CHANGED
package/test/register.test.js
CHANGED
|
@@ -459,6 +459,37 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "
|
|
|
459
459
|
})
|
|
460
460
|
})
|
|
461
461
|
|
|
462
|
+
test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', t => {
|
|
463
|
+
t.plan(2)
|
|
464
|
+
const fastify = Fastify({
|
|
465
|
+
ignoreTrailingSlash: true
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
fastify.register(function (fastify, opts, next) {
|
|
469
|
+
fastify.route({
|
|
470
|
+
method: 'GET',
|
|
471
|
+
url: '/',
|
|
472
|
+
handler: (req, reply) => {
|
|
473
|
+
reply.send({ hello: 'world' })
|
|
474
|
+
}
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
next()
|
|
478
|
+
}, { prefix: '/prefix/' })
|
|
479
|
+
|
|
480
|
+
fastify.inject({
|
|
481
|
+
method: 'GET',
|
|
482
|
+
url: '/prefix//'
|
|
483
|
+
}, (err, res) => {
|
|
484
|
+
t.error(err)
|
|
485
|
+
t.same(JSON.parse(res.payload), {
|
|
486
|
+
error: 'Not Found',
|
|
487
|
+
message: 'Not Found',
|
|
488
|
+
statusCode: 404
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
})
|
|
492
|
+
|
|
462
493
|
test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', t => {
|
|
463
494
|
t.plan(4)
|
|
464
495
|
const fastify = Fastify({
|
package/test/route.test.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const t = require('tap')
|
|
4
4
|
const test = t.test
|
|
5
5
|
const sget = require('simple-get').concat
|
|
6
|
+
const joi = require('joi')
|
|
6
7
|
const Fastify = require('..')
|
|
7
8
|
|
|
8
9
|
test('route', t => {
|
|
@@ -255,3 +256,37 @@ test('handler function in options of shorthand route should works correctly', t
|
|
|
255
256
|
t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
|
|
256
257
|
})
|
|
257
258
|
})
|
|
259
|
+
|
|
260
|
+
test('does not mutate joi schemas', t => {
|
|
261
|
+
t.plan(4)
|
|
262
|
+
|
|
263
|
+
const fastify = Fastify()
|
|
264
|
+
function schemaCompiler (schema) {
|
|
265
|
+
return function (data, opts) {
|
|
266
|
+
return joi.validate(data, schema)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fastify.setSchemaCompiler(schemaCompiler)
|
|
271
|
+
|
|
272
|
+
fastify.route({
|
|
273
|
+
path: '/foo/:an_id',
|
|
274
|
+
method: 'GET',
|
|
275
|
+
schema: {
|
|
276
|
+
params: { an_id: joi.number() }
|
|
277
|
+
},
|
|
278
|
+
handler (req, res) {
|
|
279
|
+
t.deepEqual(req.params, { an_id: 42 })
|
|
280
|
+
res.send({ hello: 'world' })
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
fastify.inject({
|
|
285
|
+
method: 'GET',
|
|
286
|
+
url: '/foo/42'
|
|
287
|
+
}, (err, result) => {
|
|
288
|
+
t.error(err)
|
|
289
|
+
t.strictEqual(result.statusCode, 200)
|
|
290
|
+
t.deepEqual(JSON.parse(result.payload), { hello: 'world' })
|
|
291
|
+
})
|
|
292
|
+
})
|
|
@@ -565,7 +565,7 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => {
|
|
|
565
565
|
$id: '#address',
|
|
566
566
|
type: 'object',
|
|
567
567
|
properties: {
|
|
568
|
-
city: {
|
|
568
|
+
city: { type: 'string' }
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
},
|
|
@@ -624,7 +624,7 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => {
|
|
|
624
624
|
$id: '#address',
|
|
625
625
|
type: 'object',
|
|
626
626
|
properties: {
|
|
627
|
-
city: {
|
|
627
|
+
city: { type: 'string' }
|
|
628
628
|
}
|
|
629
629
|
}
|
|
630
630
|
},
|
|
@@ -1089,7 +1089,7 @@ test('Use shared schema and $ref to /definitions', t => {
|
|
|
1089
1089
|
$id: '#otherId',
|
|
1090
1090
|
type: 'object',
|
|
1091
1091
|
properties: {
|
|
1092
|
-
city: {
|
|
1092
|
+
city: { type: 'string' }
|
|
1093
1093
|
}
|
|
1094
1094
|
}
|
|
1095
1095
|
},
|
package/test/types/index.ts
CHANGED
|
@@ -58,11 +58,13 @@ const cors = require('cors')
|
|
|
58
58
|
|
|
59
59
|
// other simple options
|
|
60
60
|
const otherServer = fastify({
|
|
61
|
+
caseSensitive: false,
|
|
61
62
|
ignoreTrailingSlash: true,
|
|
62
63
|
bodyLimit: 1000,
|
|
63
64
|
maxParamLength: 200,
|
|
64
65
|
querystringParser: (str: string) => ({ str: str, strArray: [str] }),
|
|
65
|
-
modifyCoreObjects: true
|
|
66
|
+
modifyCoreObjects: true,
|
|
67
|
+
return503OnClosing: true
|
|
66
68
|
})
|
|
67
69
|
|
|
68
70
|
// custom types
|
|
@@ -188,7 +190,7 @@ const schema: fastify.RouteSchema = {
|
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
const opts: fastify.RouteShorthandOptions<http2.
|
|
193
|
+
const opts: fastify.RouteShorthandOptions<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> = {
|
|
192
194
|
schema,
|
|
193
195
|
preValidation: [
|
|
194
196
|
(request, reply, next) => {
|
|
@@ -345,6 +347,10 @@ server
|
|
|
345
347
|
.get('/deprecatedpath/*', (req, reply) => {
|
|
346
348
|
reply.callNotFound()
|
|
347
349
|
})
|
|
350
|
+
.get('/getResponseTime', function (req, reply) {
|
|
351
|
+
const milliseconds : number = reply.getResponseTime()
|
|
352
|
+
reply.send({ milliseconds })
|
|
353
|
+
})
|
|
348
354
|
|
|
349
355
|
// Generics example
|
|
350
356
|
interface Query {
|
|
@@ -440,6 +446,13 @@ server.setErrorHandler((err, request, reply) => {
|
|
|
440
446
|
}
|
|
441
447
|
})
|
|
442
448
|
|
|
449
|
+
server.setReplySerializer((payload, statusCode) => {
|
|
450
|
+
if (statusCode === 201) {
|
|
451
|
+
return `Created ${payload}`
|
|
452
|
+
}
|
|
453
|
+
return JSON.stringify(payload)
|
|
454
|
+
})
|
|
455
|
+
|
|
443
456
|
server.listen(3000, err => {
|
|
444
457
|
if (err) throw err
|
|
445
458
|
const address = server.server.address()
|
|
@@ -604,3 +617,21 @@ const server2 = fastify()
|
|
|
604
617
|
server2.close().then(() => {})
|
|
605
618
|
const server3 = fastify()
|
|
606
619
|
server3.close(() => {})
|
|
620
|
+
|
|
621
|
+
{
|
|
622
|
+
// tests generics default values
|
|
623
|
+
const routeOptions: fastify.RouteOptions = {
|
|
624
|
+
method: 'GET',
|
|
625
|
+
url: '/',
|
|
626
|
+
handler: function (req, reply) { reply.send({}) }
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const genericHandler: fastify.RequestHandler = (req, reply) => { reply.send(reply) }
|
|
630
|
+
|
|
631
|
+
const middleware: fastify.FastifyMiddleware = function middleware (req, reply, done) {
|
|
632
|
+
this.addHook('onClose', function (instance, done) {
|
|
633
|
+
done()
|
|
634
|
+
})
|
|
635
|
+
done()
|
|
636
|
+
}
|
|
637
|
+
}
|
|
@@ -445,7 +445,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
|
|
|
445
445
|
method: 'GET',
|
|
446
446
|
url: '/',
|
|
447
447
|
headers: {
|
|
448
|
-
|
|
448
|
+
Accept: 'application/vnd.example.api+json;version=2'
|
|
449
449
|
}
|
|
450
450
|
}, (err, res) => {
|
|
451
451
|
t.error(err)
|
|
@@ -457,7 +457,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
|
|
|
457
457
|
method: 'GET',
|
|
458
458
|
url: '/',
|
|
459
459
|
headers: {
|
|
460
|
-
|
|
460
|
+
Accept: 'application/vnd.example.api+json;version=3'
|
|
461
461
|
}
|
|
462
462
|
}, (err, res) => {
|
|
463
463
|
t.error(err)
|
|
@@ -469,7 +469,7 @@ test('Should register a versioned route with custome versioning strategy', t =>
|
|
|
469
469
|
method: 'GET',
|
|
470
470
|
url: '/',
|
|
471
471
|
headers: {
|
|
472
|
-
|
|
472
|
+
Accept: 'application/vnd.example.api+json;version=4'
|
|
473
473
|
}
|
|
474
474
|
}, (err, res) => {
|
|
475
475
|
t.error(err)
|