fastify 4.1.0 → 4.2.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/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +10 -0
- package/docs/Guides/Migration-Guide-V4.md +40 -1
- package/docs/Guides/Serverless.md +23 -10
- package/docs/Reference/Hooks.md +52 -0
- package/docs/Reference/Plugins.md +1 -1
- package/docs/Reference/TypeScript.md +38 -25
- package/docs/Reference/Validation-and-Serialization.md +11 -0
- package/docs/index.md +1 -1
- package/fastify.d.ts +2 -2
- package/fastify.js +1 -1
- package/integration/server.js +27 -0
- package/integration/test.sh +23 -0
- package/lib/context.js +5 -2
- package/lib/error-serializer.js +21 -24
- package/lib/handleRequest.js +1 -1
- package/lib/route.js +39 -29
- package/lib/symbols.js +2 -1
- package/lib/validation.js +2 -0
- package/package.json +4 -4
- package/test/hooks.test.js +21 -0
- package/test/pretty-print.test.js +3 -3
- package/test/reply-error.test.js +1 -1
- package/test/schema-feature.test.js +2 -2
- package/test/types/fastify.test-d.ts +12 -1
- package/test/types/request.test-d.ts +8 -4
- package/test/types/type-provider.test-d.ts +11 -2
- package/test/validation-error-handling.test.js +32 -0
- package/types/route.d.ts +10 -12
- package/types/type-provider.d.ts +12 -5
|
@@ -48,7 +48,7 @@ server (fewer resources allocated to a bound-to-fail task) and for the client
|
|
|
48
48
|
That will be achieved by wrapping into a custom plugin two main features:
|
|
49
49
|
|
|
50
50
|
1. the mechanism for authenticating with the provider
|
|
51
|
-
[decorating](../
|
|
51
|
+
[decorating](../Reference/Decorators.md) the `fastify` object with the
|
|
52
52
|
authentication key (`magicKey` from here onwards)
|
|
53
53
|
1. the mechanism for denying requests that would, otherwise, fail
|
|
54
54
|
|
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -132,9 +132,15 @@ section.
|
|
|
132
132
|
sampling process metrics.
|
|
133
133
|
- [`@dnlup/fastify-traps`](https://github.com/dnlup/fastify-traps) A plugin to
|
|
134
134
|
close the server gracefully on `SIGINT` and `SIGTERM` signals.
|
|
135
|
+
- [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides
|
|
136
|
+
easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes.
|
|
135
137
|
- [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server)
|
|
136
138
|
Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your
|
|
137
139
|
Fastify API graceful.
|
|
140
|
+
- [`@h4ad/serverless-adapter`](https://github.com/H4ad/serverless-adapter)
|
|
141
|
+
Run REST APIs and other web applications using your existing Node.js
|
|
142
|
+
application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda,
|
|
143
|
+
Huawei and many other clouds.
|
|
138
144
|
- [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics)
|
|
139
145
|
Minimalistic and opinionated plugin that collects usage/process metrics and
|
|
140
146
|
dispatches to [statsd](https://github.com/statsd/statsd).
|
|
@@ -327,6 +333,8 @@ section.
|
|
|
327
333
|
Kubernetes client plugin.
|
|
328
334
|
- [`fastify-language-parser`](https://github.com/lependu/fastify-language-parser)
|
|
329
335
|
Fastify plugin to parse request language.
|
|
336
|
+
- [`fastify-lcache`](https://github.com/denbon05/fastify-lcache)
|
|
337
|
+
Lightweight cache plugin
|
|
330
338
|
- [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from
|
|
331
339
|
a directory and inject the Fastify instance in each file.
|
|
332
340
|
- [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua
|
|
@@ -478,6 +486,8 @@ section.
|
|
|
478
486
|
Events with `reply.sse( … )` to Fastify.
|
|
479
487
|
- [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide
|
|
480
488
|
Server-Sent Events using Async Iterators (supports newer versions of Fastify).
|
|
489
|
+
- [`fastify-ssr-vite`](https://github.com/nineohnine/fastify-ssr-vite) A simple
|
|
490
|
+
plugin for setting up server side rendering with vite.
|
|
481
491
|
- [`fastify-stripe`](https://github.com/coopflow/fastify-stripe) Plugin to
|
|
482
492
|
initialize and encapsulate [Stripe
|
|
483
493
|
Node.js](https://github.com/stripe/stripe-node) instances in Fastify.
|
|
@@ -8,7 +8,39 @@ work after upgrading.
|
|
|
8
8
|
|
|
9
9
|
## Breaking Changes
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261))
|
|
12
|
+
|
|
13
|
+
When an error is thrown in a async error handler function,
|
|
14
|
+
the upper-level error handler is executed if set.
|
|
15
|
+
If there is not a upper-level error handler, the default will
|
|
16
|
+
be executed as it was previously.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
import Fastify from 'fastify'
|
|
20
|
+
|
|
21
|
+
const fastify = Fastify()
|
|
22
|
+
|
|
23
|
+
fastify.register(async fastify => {
|
|
24
|
+
fastify.setErrorHandler(async err => {
|
|
25
|
+
console.log(err.message) // 'kaboom'
|
|
26
|
+
throw new Error('caught')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
fastify.get('/encapsulated', async () => {
|
|
30
|
+
throw new Error('kaboom')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
fastify.setErrorHandler(async err => {
|
|
35
|
+
console.log(err.message) // 'caught'
|
|
36
|
+
throw new Error('wrapped')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const res = await fastify.inject('/encapsulated')
|
|
40
|
+
console.log(res.json().message) // 'wrapped'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Deprecation of `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506))
|
|
12
44
|
|
|
13
45
|
Starting this version of Fastify, we have deprecated the use of `app.use()`. We
|
|
14
46
|
have decided not to support the use of middlewares. Both
|
|
@@ -70,3 +102,10 @@ properties: {
|
|
|
70
102
|
}
|
|
71
103
|
}
|
|
72
104
|
```
|
|
105
|
+
|
|
106
|
+
### Add `reply.trailers` methods ([#3794](https://github.com/fastify/fastify/pull/3794))
|
|
107
|
+
|
|
108
|
+
Fastify now supports the [HTTP Trailer] response headers.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
[HTTP Trailer]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer
|
|
@@ -24,23 +24,31 @@ snippet of code.
|
|
|
24
24
|
|
|
25
25
|
### Contents
|
|
26
26
|
|
|
27
|
-
- [AWS
|
|
27
|
+
- [AWS](#aws)
|
|
28
28
|
- [Google Cloud Functions](#google-cloud-functions)
|
|
29
29
|
- [Google Cloud Run](#google-cloud-run)
|
|
30
30
|
- [Netlify Lambda](#netlify-lambda)
|
|
31
31
|
- [Vercel](#vercel)
|
|
32
32
|
|
|
33
|
-
## AWS
|
|
33
|
+
## AWS
|
|
34
|
+
|
|
35
|
+
To integrate with AWS, you have two choices of library:
|
|
36
|
+
|
|
37
|
+
- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify)
|
|
38
|
+
which only adds API Gateway support but has heavy optimizations for fastify.
|
|
39
|
+
- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter)
|
|
40
|
+
which is a little slower as it creates an HTTP request for each AWS event but
|
|
41
|
+
has support for more AWS services such as: AWS SQS, AWS SNS and others.
|
|
42
|
+
|
|
43
|
+
So you can decide which option is best for you, but you can test both libraries.
|
|
44
|
+
|
|
45
|
+
### Using @fastify/aws-lambda
|
|
34
46
|
|
|
35
47
|
The sample provided allows you to easily build serverless web
|
|
36
48
|
applications/services and RESTful APIs using Fastify on top of AWS Lambda and
|
|
37
49
|
Amazon API Gateway.
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify) is just one
|
|
41
|
-
possible way.*
|
|
42
|
-
|
|
43
|
-
### app.js
|
|
51
|
+
#### app.js
|
|
44
52
|
|
|
45
53
|
```js
|
|
46
54
|
const fastify = require('fastify');
|
|
@@ -71,7 +79,7 @@ When you execute your Fastify application like always, i.e. `node app.js` *(the
|
|
|
71
79
|
detection for this could be `require.main === module`)*, you can normally listen
|
|
72
80
|
to your port, so you can still run your Fastify function locally.
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
#### lambda.js
|
|
75
83
|
|
|
76
84
|
```js
|
|
77
85
|
const awsLambdaFastify = require('@fastify/aws-lambda')
|
|
@@ -99,14 +107,13 @@ signature to be used as a lambda `handler` function. This way all the incoming
|
|
|
99
107
|
events (API Gateway requests) are passed to the `proxy` function of
|
|
100
108
|
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify).
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
#### Example
|
|
103
111
|
|
|
104
112
|
An example deployable with
|
|
105
113
|
[claudia.js](https://claudiajs.com/tutorials/serverless-express.html) can be
|
|
106
114
|
found
|
|
107
115
|
[here](https://github.com/claudiajs/example-projects/tree/master/fastify-app-lambda).
|
|
108
116
|
|
|
109
|
-
|
|
110
117
|
### Considerations
|
|
111
118
|
|
|
112
119
|
- API Gateway does not support streams yet, so you are not able to handle
|
|
@@ -114,6 +121,12 @@ found
|
|
|
114
121
|
- API Gateway has a timeout of 29 seconds, so it is important to provide a reply
|
|
115
122
|
during this time.
|
|
116
123
|
|
|
124
|
+
#### Beyond API Gateway
|
|
125
|
+
|
|
126
|
+
If you need to integrate with more AWS services, take a look at
|
|
127
|
+
[@h4ad/serverless-adapter](https://viniciusl.com.br/serverless-adapter/docs/main/frameworks/fastify)
|
|
128
|
+
on Fastify to find out how to integrate.
|
|
129
|
+
|
|
117
130
|
## Google Cloud Functions
|
|
118
131
|
|
|
119
132
|
### Creation of Fastify instance
|
package/docs/Reference/Hooks.md
CHANGED
|
@@ -28,6 +28,7 @@ are Request/Reply hooks and application hooks:
|
|
|
28
28
|
- [onRegister](#onregister)
|
|
29
29
|
- [Scope](#scope)
|
|
30
30
|
- [Route level hooks](#route-level-hooks)
|
|
31
|
+
- [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties)
|
|
31
32
|
- [Diagnostics Channel Hooks](#diagnostics-channel-hooks)
|
|
32
33
|
|
|
33
34
|
**Notice:** the `done` callback is not available when using `async`/`await` or
|
|
@@ -649,6 +650,57 @@ fastify.route({
|
|
|
649
650
|
|
|
650
651
|
**Note**: both options also accept an array of functions.
|
|
651
652
|
|
|
653
|
+
## Using Hooks to Inject Custom Properties
|
|
654
|
+
<a id="using-hooks-to-inject-custom-properties"></a>
|
|
655
|
+
|
|
656
|
+
You can use a hook to inject custom properties into incoming requests.
|
|
657
|
+
This is useful for reusing processed data from hooks in controllers.
|
|
658
|
+
|
|
659
|
+
A very common use case is, for example, checking user authentication based
|
|
660
|
+
on their token and then storing their recovered data into
|
|
661
|
+
the [Request](./Request.md) instance. This way, your controllers can read it
|
|
662
|
+
easily with `request.authenticatedUser` or whatever you want to call it.
|
|
663
|
+
That's how it might look like:
|
|
664
|
+
|
|
665
|
+
```js
|
|
666
|
+
fastify.addHook('preParsing', async (request) => {
|
|
667
|
+
request.authenticatedUser = {
|
|
668
|
+
id: 42,
|
|
669
|
+
name: 'Jane Doe',
|
|
670
|
+
role: 'admin'
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
fastify.get('/me/is-admin', async function (req, reply) {
|
|
675
|
+
return { isAdmin: req.authenticatedUser?.role === 'admin' || false }
|
|
676
|
+
})
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Note that `.authenticatedUser` could actually be any property name
|
|
680
|
+
choosen by yourself. Using your own custom property prevents you
|
|
681
|
+
from mutating existing properties, which
|
|
682
|
+
would be a dangerous and destructive operation. So be careful and
|
|
683
|
+
make sure your property is entirely new, also using this approach
|
|
684
|
+
only for very specific and small cases like this example.
|
|
685
|
+
|
|
686
|
+
Regarding TypeScript in this example, you'd need to update the
|
|
687
|
+
`FastifyRequest` core interface to include your new property typing
|
|
688
|
+
(for more about it, see [TypeScript](./TypeScript.md) page), like:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
interface AuthenticatedUser { /* ... */ }
|
|
692
|
+
|
|
693
|
+
declare module 'fastify' {
|
|
694
|
+
export interface FastifyRequest {
|
|
695
|
+
authenticatedUser?: AuthenticatedUser;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Although this is a very pragmatic approach, if you're trying to do
|
|
701
|
+
something more complex that changes these core objects, then
|
|
702
|
+
consider creating a custom [Plugin](./Plugins.md) instead.
|
|
703
|
+
|
|
652
704
|
## Diagnostics Channel Hooks
|
|
653
705
|
|
|
654
706
|
> **Note:** The `diagnostics_channel` is currently experimental on Node.js, so
|
|
@@ -13,7 +13,7 @@ way we create a *directed acyclic graph* (DAG) and we will not have issues
|
|
|
13
13
|
caused by cross dependencies.
|
|
14
14
|
|
|
15
15
|
You may have already seen in the [Getting
|
|
16
|
-
Started](
|
|
16
|
+
Started](../Guides/Getting-Started.md#your-first-plugin) guide how easy it is
|
|
17
17
|
to use this API:
|
|
18
18
|
```
|
|
19
19
|
fastify.register(plugin, [options])
|
|
@@ -199,17 +199,18 @@ Below is how to setup schema validation using vanilla `typebox` and
|
|
|
199
199
|
#### typebox
|
|
200
200
|
|
|
201
201
|
A useful library for building types and a schema at once is
|
|
202
|
-
[typebox](https://www.npmjs.com/package/@sinclair/typebox)
|
|
203
|
-
|
|
204
|
-
you
|
|
202
|
+
[typebox](https://www.npmjs.com/package/@sinclair/typebox) along with
|
|
203
|
+
[fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox).
|
|
204
|
+
With typebox you define your schema within your code and use them
|
|
205
|
+
directly as types or schemas as you need them.
|
|
205
206
|
|
|
206
207
|
When you want to use it for validation of some payload in a fastify route you
|
|
207
208
|
can do it as follows:
|
|
208
209
|
|
|
209
|
-
1. Install `typebox` in your project.
|
|
210
|
+
1. Install `typebox` and `fastify-type-provider-typebox` in your project.
|
|
210
211
|
|
|
211
212
|
```bash
|
|
212
|
-
npm i @sinclair/typebox
|
|
213
|
+
npm i @sinclair/typebox @fastify/type-provider-typebox
|
|
213
214
|
```
|
|
214
215
|
|
|
215
216
|
2. Define the schema you need with `Type` and create the respective type with
|
|
@@ -218,40 +219,52 @@ can do it as follows:
|
|
|
218
219
|
```typescript
|
|
219
220
|
import { Static, Type } from '@sinclair/typebox'
|
|
220
221
|
|
|
221
|
-
const User = Type.Object({
|
|
222
|
+
export const User = Type.Object({
|
|
222
223
|
name: Type.String(),
|
|
223
|
-
mail: Type.Optional(Type.String({ format:
|
|
224
|
-
})
|
|
225
|
-
|
|
224
|
+
mail: Type.Optional(Type.String({ format: 'email' })),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
export type UserType = Static<typeof User>
|
|
226
228
|
```
|
|
227
229
|
|
|
228
230
|
3. Use the defined type and schema during the definition of your route
|
|
229
231
|
|
|
230
232
|
```typescript
|
|
231
|
-
|
|
233
|
+
import Fastify from 'fastify'
|
|
234
|
+
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
|
235
|
+
// ...
|
|
236
|
+
|
|
237
|
+
const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()
|
|
232
238
|
|
|
233
|
-
app.post<{ Body: UserType
|
|
234
|
-
|
|
239
|
+
app.post<{ Body: UserType, Reply: UserType }>(
|
|
240
|
+
'/',
|
|
235
241
|
{
|
|
236
242
|
schema: {
|
|
237
243
|
body: User,
|
|
238
244
|
response: {
|
|
239
|
-
200: User
|
|
245
|
+
200: User
|
|
240
246
|
},
|
|
241
247
|
},
|
|
242
248
|
},
|
|
243
249
|
(request, reply) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
// The `name` and `mail` types are automatically inferred
|
|
251
|
+
const { name, mail } = request.body;
|
|
252
|
+
reply.status(200).send({ name, mail });
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Note** For Ajv version 7 and above is required to use the `ajvTypeBoxPlugin`:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import Fastify from 'fastify'
|
|
261
|
+
import { ajvTypeBoxPlugin, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
|
262
|
+
|
|
263
|
+
const fastify = Fastify({
|
|
264
|
+
ajv: {
|
|
265
|
+
plugins: [ajvTypeBoxPlugin]
|
|
253
266
|
}
|
|
254
|
-
)
|
|
267
|
+
}).withTypeProvider<TypeBoxTypeProvider>()
|
|
255
268
|
```
|
|
256
269
|
|
|
257
270
|
#### Schemas in JSON Files
|
|
@@ -1359,7 +1372,7 @@ A method for checking the existence of a type parser of a certain content type
|
|
|
1359
1372
|
|
|
1360
1373
|
##### fastify.FastifyError
|
|
1361
1374
|
|
|
1362
|
-
[src](https://github.com/fastify/fastify/blob/main/
|
|
1375
|
+
[src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L179)
|
|
1363
1376
|
|
|
1364
1377
|
FastifyError is a custom error object that includes status code and validation
|
|
1365
1378
|
results.
|
|
@@ -1369,7 +1382,7 @@ properties: `statusCode: number` and `validation: ValiationResult[]`.
|
|
|
1369
1382
|
|
|
1370
1383
|
##### fastify.ValidationResult
|
|
1371
1384
|
|
|
1372
|
-
[src](https://github.com/fastify/fastify/blob/main/
|
|
1385
|
+
[src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L184)
|
|
1373
1386
|
|
|
1374
1387
|
The route validation internally relies upon Ajv, which is a high-performance
|
|
1375
1388
|
JSON schema validator.
|
|
@@ -484,6 +484,17 @@ fastify.post('/the/url', {
|
|
|
484
484
|
}, handler)
|
|
485
485
|
```
|
|
486
486
|
|
|
487
|
+
##### .statusCode property
|
|
488
|
+
|
|
489
|
+
All validation errors will be added a `.statusCode` property set to `400`. This guarantees
|
|
490
|
+
that the default error handler will set the status code of the response to `400`.
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
494
|
+
request.log.error(error, `This error has status code ${error.statusCode}`)
|
|
495
|
+
reply.status(error.statusCode).send(error)
|
|
496
|
+
})
|
|
497
|
+
```
|
|
487
498
|
|
|
488
499
|
##### Validation messages with other validation libraries
|
|
489
500
|
|
package/docs/index.md
CHANGED
|
@@ -16,7 +16,7 @@ Complete newcomers to Fastify should first read our [Getting
|
|
|
16
16
|
Started](./Guides/Getting-Started.md) guide.
|
|
17
17
|
|
|
18
18
|
Developers experienced with Fastify should consult the [reference
|
|
19
|
-
documentation](./Reference/
|
|
19
|
+
documentation](./Reference/Index.md) directly to find the topic they are seeking
|
|
20
20
|
more information about.
|
|
21
21
|
|
|
22
22
|
## Additional Documentation
|
package/fastify.d.ts
CHANGED
|
@@ -183,10 +183,10 @@ declare module '@fastify/error' {
|
|
|
183
183
|
|
|
184
184
|
export interface ValidationResult {
|
|
185
185
|
keyword: string;
|
|
186
|
-
|
|
186
|
+
instancePath: string;
|
|
187
187
|
schemaPath: string;
|
|
188
188
|
params: Record<string, string | string[]>;
|
|
189
|
-
message
|
|
189
|
+
message?: string;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/* Export all additional types */
|
package/fastify.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const Fastify = require('../fastify')
|
|
2
|
+
|
|
3
|
+
const fastify = Fastify()
|
|
4
|
+
|
|
5
|
+
fastify.listen({
|
|
6
|
+
host: '::',
|
|
7
|
+
port: 3000
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
fastify.get('/', async function (request, reply) {
|
|
11
|
+
reply.code(200).send({ data: 'home page' })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
fastify.post('/post/:id', async function (request, reply) {
|
|
15
|
+
const { id } = request.params
|
|
16
|
+
reply.code(201).send({ data: `${id}` })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
fastify.put('/put/:id', async function (request, reply) {
|
|
20
|
+
const { id } = request.params
|
|
21
|
+
reply.code(200).send({ data: `${id}` })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
fastify.delete('/delete/:id', async function (request, reply) {
|
|
25
|
+
const { id } = request.params
|
|
26
|
+
reply.code(204).send({ data: `${id}` })
|
|
27
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
NUMBER=$RANDOM
|
|
6
|
+
curl -i -X GET -H 'Content-Type: application/json' localhost:3000/ > GET
|
|
7
|
+
if [[ ! $(cat GET | head -1| cut -f2 -d" ") == "200" || ! $(cat GET | tail -1| cut -f4 -d"\"") == "home page" ]] ; then
|
|
8
|
+
exit 1
|
|
9
|
+
fi;
|
|
10
|
+
curl -i -X POST -H 'Content-Type: application/json' localhost:3000/post/$NUMBER --data {} > POST
|
|
11
|
+
if [[ ! $(cat POST | head -1| cut -f2 -d" ") == "201" || ! $(cat POST | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
|
12
|
+
exit 1
|
|
13
|
+
fi;
|
|
14
|
+
curl -i -X PUT -H 'Content-Type: application/json' localhost:3000/put/$NUMBER --data {} > PUT
|
|
15
|
+
if [[ ! $(cat PUT | head -1| cut -f2 -d" ") == "200" || ! $(cat PUT | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
|
16
|
+
exit 1
|
|
17
|
+
fi;
|
|
18
|
+
curl -i -X DELETE -H 'Content-Type: application/json' localhost:3000/delete/$NUMBER --data {} > DELETE
|
|
19
|
+
if [[ ! $(cat DELETE | head -1| cut -f2 -d" ") == "204" ]]; then
|
|
20
|
+
exit 1
|
|
21
|
+
fi;
|
|
22
|
+
|
|
23
|
+
rm -f GET POST PUT DELETE
|
package/lib/context.js
CHANGED
|
@@ -9,7 +9,8 @@ const {
|
|
|
9
9
|
kRequest,
|
|
10
10
|
kBodyLimit,
|
|
11
11
|
kLogLevel,
|
|
12
|
-
kContentTypeParser
|
|
12
|
+
kContentTypeParser,
|
|
13
|
+
kRouteByFastify
|
|
13
14
|
} = require('./symbols.js')
|
|
14
15
|
|
|
15
16
|
// Objects that holds the context of every request
|
|
@@ -25,7 +26,8 @@ function Context ({
|
|
|
25
26
|
attachValidation,
|
|
26
27
|
replySerializer,
|
|
27
28
|
schemaErrorFormatter,
|
|
28
|
-
server
|
|
29
|
+
server,
|
|
30
|
+
isFastify
|
|
29
31
|
}) {
|
|
30
32
|
this.schema = schema
|
|
31
33
|
this.handler = handler
|
|
@@ -50,6 +52,7 @@ function Context ({
|
|
|
50
52
|
this.attachValidation = attachValidation
|
|
51
53
|
this[kReplySerializerDefault] = replySerializer
|
|
52
54
|
this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
|
|
55
|
+
this[kRouteByFastify] = isFastify
|
|
53
56
|
|
|
54
57
|
this.server = server
|
|
55
58
|
}
|
package/lib/error-serializer.js
CHANGED
|
@@ -39,7 +39,7 @@ class Serializer {
|
|
|
39
39
|
} else {
|
|
40
40
|
/* eslint no-undef: "off" */
|
|
41
41
|
const integer = this.parseInteger(i)
|
|
42
|
-
if (Number.isNaN(integer)) {
|
|
42
|
+
if (Number.isNaN(integer) || !Number.isFinite(integer)) {
|
|
43
43
|
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
44
44
|
} else {
|
|
45
45
|
return '' + integer
|
|
@@ -55,6 +55,8 @@ class Serializer {
|
|
|
55
55
|
const num = Number(i)
|
|
56
56
|
if (Number.isNaN(num)) {
|
|
57
57
|
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
58
|
+
} else if (!Number.isFinite(num)) {
|
|
59
|
+
return null
|
|
58
60
|
} else {
|
|
59
61
|
return '' + num
|
|
60
62
|
}
|
|
@@ -72,44 +74,44 @@ class Serializer {
|
|
|
72
74
|
return bool === null ? 'null' : this.asBoolean(bool)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
asDatetime (date
|
|
76
|
-
const quotes =
|
|
77
|
+
asDatetime (date) {
|
|
78
|
+
const quotes = '"'
|
|
77
79
|
if (date instanceof Date) {
|
|
78
80
|
return quotes + date.toISOString() + quotes
|
|
79
81
|
}
|
|
80
|
-
return this.asString(date
|
|
82
|
+
return this.asString(date)
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
asDatetimeNullable (date
|
|
84
|
-
return date === null ? 'null' : this.asDatetime(date
|
|
85
|
+
asDatetimeNullable (date) {
|
|
86
|
+
return date === null ? 'null' : this.asDatetime(date)
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
asDate (date
|
|
88
|
-
const quotes =
|
|
89
|
+
asDate (date) {
|
|
90
|
+
const quotes = '"'
|
|
89
91
|
if (date instanceof Date) {
|
|
90
92
|
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
|
|
91
93
|
}
|
|
92
|
-
return this.asString(date
|
|
94
|
+
return this.asString(date)
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
asDateNullable (date
|
|
96
|
-
return date === null ? 'null' : this.asDate(date
|
|
97
|
+
asDateNullable (date) {
|
|
98
|
+
return date === null ? 'null' : this.asDate(date)
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
asTime (date
|
|
100
|
-
const quotes =
|
|
101
|
+
asTime (date) {
|
|
102
|
+
const quotes = '"'
|
|
101
103
|
if (date instanceof Date) {
|
|
102
104
|
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
103
105
|
}
|
|
104
|
-
return this.asString(date
|
|
106
|
+
return this.asString(date)
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
asTimeNullable (date
|
|
108
|
-
return date === null ? 'null' : this.asTime(date
|
|
109
|
+
asTimeNullable (date) {
|
|
110
|
+
return date === null ? 'null' : this.asTime(date)
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
asString (str
|
|
112
|
-
const quotes =
|
|
113
|
+
asString (str) {
|
|
114
|
+
const quotes = '"'
|
|
113
115
|
if (str instanceof Date) {
|
|
114
116
|
return quotes + str.toISOString() + quotes
|
|
115
117
|
} else if (str === null) {
|
|
@@ -119,11 +121,6 @@ class Serializer {
|
|
|
119
121
|
} else if (typeof str !== 'string') {
|
|
120
122
|
str = str.toString()
|
|
121
123
|
}
|
|
122
|
-
// If we skipQuotes it means that we are using it as test
|
|
123
|
-
// no need to test the string length for the render
|
|
124
|
-
if (skipQuotes) {
|
|
125
|
-
return str
|
|
126
|
-
}
|
|
127
124
|
|
|
128
125
|
if (str.length < 42) {
|
|
129
126
|
return this.asStringSmall(str)
|
|
@@ -185,7 +182,7 @@ class Serializer {
|
|
|
185
182
|
}
|
|
186
183
|
|
|
187
184
|
function anonymous0 (input) {
|
|
188
|
-
//
|
|
185
|
+
// #
|
|
189
186
|
|
|
190
187
|
var obj = (input && typeof input.toJSON === 'function')
|
|
191
188
|
? input.toJSON()
|
package/lib/handleRequest.js
CHANGED
package/lib/route.js
CHANGED
|
@@ -8,7 +8,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
|
|
|
8
8
|
const { normalizeSchema } = require('./schemas')
|
|
9
9
|
const { parseHeadOnSendHandlers } = require('./headRoute')
|
|
10
10
|
const warning = require('./warnings')
|
|
11
|
-
const { kRequestAcceptVersion } = require('./symbols')
|
|
11
|
+
const { kRequestAcceptVersion, kRouteByFastify } = require('./symbols')
|
|
12
12
|
|
|
13
13
|
const {
|
|
14
14
|
compileSchemasForValidation,
|
|
@@ -113,7 +113,7 @@ function buildRouting (options) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Convert shorthand to extended route declaration
|
|
116
|
-
function prepareRoute ({ method, url, options, handler }) {
|
|
116
|
+
function prepareRoute ({ method, url, options, handler, isFastify }) {
|
|
117
117
|
if (typeof url !== 'string') {
|
|
118
118
|
throw new FST_ERR_INVALID_URL(typeof url)
|
|
119
119
|
}
|
|
@@ -140,11 +140,11 @@ function buildRouting (options) {
|
|
|
140
140
|
handler: handler || (options && options.handler)
|
|
141
141
|
})
|
|
142
142
|
|
|
143
|
-
return route.call(this, { options })
|
|
143
|
+
return route.call(this, { options, isFastify })
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Route management
|
|
147
|
-
function route ({ options }) {
|
|
147
|
+
function route ({ options, isFastify }) {
|
|
148
148
|
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
|
|
149
149
|
const opts = { ...options }
|
|
150
150
|
|
|
@@ -176,30 +176,30 @@ function buildRouting (options) {
|
|
|
176
176
|
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
|
|
177
177
|
switch (opts.prefixTrailingSlash) {
|
|
178
178
|
case 'slash':
|
|
179
|
-
addNewRoute.call(this, { path })
|
|
179
|
+
addNewRoute.call(this, { path, isFastify })
|
|
180
180
|
break
|
|
181
181
|
case 'no-slash':
|
|
182
|
-
addNewRoute.call(this, { path: '' })
|
|
182
|
+
addNewRoute.call(this, { path: '', isFastify })
|
|
183
183
|
break
|
|
184
184
|
case 'both':
|
|
185
185
|
default:
|
|
186
|
-
addNewRoute.call(this, { path: '' })
|
|
186
|
+
addNewRoute.call(this, { path: '', isFastify })
|
|
187
187
|
// If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
|
|
188
188
|
if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
|
|
189
|
-
addNewRoute.call(this, { path, prefixing: true })
|
|
189
|
+
addNewRoute.call(this, { path, prefixing: true, isFastify })
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
} else if (path[0] === '/' && prefix.endsWith('/')) {
|
|
193
193
|
// Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
|
|
194
|
-
addNewRoute.call(this, { path: path.slice(1) })
|
|
194
|
+
addNewRoute.call(this, { path: path.slice(1), isFastify })
|
|
195
195
|
} else {
|
|
196
|
-
addNewRoute.call(this, { path })
|
|
196
|
+
addNewRoute.call(this, { path, isFastify })
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
// chainable api
|
|
200
200
|
return this
|
|
201
201
|
|
|
202
|
-
function addNewRoute ({ path, prefixing = false }) {
|
|
202
|
+
function addNewRoute ({ path, prefixing = false, isFastify = false }) {
|
|
203
203
|
const url = prefix + path
|
|
204
204
|
|
|
205
205
|
opts.url = url
|
|
@@ -241,7 +241,8 @@ function buildRouting (options) {
|
|
|
241
241
|
attachValidation: opts.attachValidation,
|
|
242
242
|
schemaErrorFormatter: opts.schemaErrorFormatter,
|
|
243
243
|
replySerializer: this[kReplySerializerDefault],
|
|
244
|
-
server: this
|
|
244
|
+
server: this,
|
|
245
|
+
isFastify
|
|
245
246
|
})
|
|
246
247
|
|
|
247
248
|
if (opts.version) {
|
|
@@ -249,13 +250,20 @@ function buildRouting (options) {
|
|
|
249
250
|
constraints.version = opts.version
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
const
|
|
253
|
+
const headHandler = router.find('HEAD', opts.url, constraints)
|
|
254
|
+
const hasHEADHandler = headHandler != null
|
|
253
255
|
|
|
254
|
-
//
|
|
255
|
-
if (!
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
// remove the head route created by fastify
|
|
257
|
+
if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
|
|
258
|
+
router.off(opts.method, opts.url, { constraints })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// any route insertion error created by fastify can be safely ignore
|
|
265
|
+
// because it only duplicate route for head
|
|
266
|
+
if (!context[kRouteByFastify]) {
|
|
259
267
|
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`)
|
|
260
268
|
if (isDuplicatedRoute) {
|
|
261
269
|
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
|
|
@@ -320,19 +328,21 @@ function buildRouting (options) {
|
|
|
320
328
|
}
|
|
321
329
|
})
|
|
322
330
|
|
|
323
|
-
const { exposeHeadRoute } = opts
|
|
324
|
-
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
|
|
325
|
-
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
|
|
326
|
-
|
|
327
|
-
if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
|
|
328
|
-
const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
|
|
329
|
-
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers } })
|
|
330
|
-
} else if (headRouteExists && exposeHeadRoute) {
|
|
331
|
-
warning.emit('FSTDEP007')
|
|
332
|
-
}
|
|
333
|
-
|
|
334
331
|
done(notHandledErr)
|
|
335
332
|
})
|
|
333
|
+
|
|
334
|
+
// register head route in sync
|
|
335
|
+
// we must place it after the `this.after`
|
|
336
|
+
const { exposeHeadRoute } = opts
|
|
337
|
+
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
|
|
338
|
+
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
|
|
339
|
+
|
|
340
|
+
if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) {
|
|
341
|
+
const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
|
|
342
|
+
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers }, isFastify: true })
|
|
343
|
+
} else if (hasHEADHandler && exposeHeadRoute) {
|
|
344
|
+
warning.emit('FSTDEP007')
|
|
345
|
+
}
|
|
336
346
|
}
|
|
337
347
|
}
|
|
338
348
|
|
package/lib/symbols.js
CHANGED
|
@@ -47,7 +47,8 @@ const keys = {
|
|
|
47
47
|
kTestInternals: Symbol('fastify.testInternals'),
|
|
48
48
|
kErrorHandler: Symbol('fastify.errorHandler'),
|
|
49
49
|
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
|
50
|
-
kKeepAliveConnections: Symbol('fastify.keepAliveConnections')
|
|
50
|
+
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
|
51
|
+
kRouteByFastify: Symbol('fastify.routeByFastify')
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
module.exports = keys
|
package/lib/validation.js
CHANGED
|
@@ -106,11 +106,13 @@ function validate (context, request) {
|
|
|
106
106
|
|
|
107
107
|
function wrapValidationError (result, dataVar, schemaErrorFormatter) {
|
|
108
108
|
if (result instanceof Error) {
|
|
109
|
+
result.statusCode = result.statusCode || 400
|
|
109
110
|
result.validationContext = result.validationContext || dataVar
|
|
110
111
|
return result
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const error = schemaErrorFormatter(result, dataVar)
|
|
115
|
+
error.statusCode = error.statusCode || 400
|
|
114
116
|
error.validation = result
|
|
115
117
|
error.validationContext = dataVar
|
|
116
118
|
return error
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"eslint-plugin-n": "^15.2.0",
|
|
147
147
|
"eslint-plugin-promise": "^6.0.0",
|
|
148
148
|
"fast-json-body": "^1.1.0",
|
|
149
|
-
"fast-json-stringify": "^
|
|
149
|
+
"fast-json-stringify": "^5.0.0",
|
|
150
150
|
"fastify-plugin": "^3.0.1",
|
|
151
151
|
"fluent-json-schema": "^3.1.0",
|
|
152
152
|
"form-data": "^4.0.0",
|
|
@@ -179,10 +179,10 @@
|
|
|
179
179
|
"dependencies": {
|
|
180
180
|
"@fastify/ajv-compiler": "^3.1.0",
|
|
181
181
|
"@fastify/error": "^3.0.0",
|
|
182
|
-
"@fastify/fast-json-stringify-compiler": "^
|
|
182
|
+
"@fastify/fast-json-stringify-compiler": "^4.0.0",
|
|
183
183
|
"abstract-logging": "^2.0.1",
|
|
184
184
|
"avvio": "^8.1.3",
|
|
185
|
-
"find-my-way": "^
|
|
185
|
+
"find-my-way": "^7.0.0",
|
|
186
186
|
"light-my-request": "^5.0.0",
|
|
187
187
|
"pino": "^8.0.0",
|
|
188
188
|
"process-warning": "^2.0.0",
|
package/test/hooks.test.js
CHANGED
|
@@ -799,6 +799,27 @@ test('onRoute hook with many prefix', t => {
|
|
|
799
799
|
fastify.ready(err => { t.error(err) })
|
|
800
800
|
})
|
|
801
801
|
|
|
802
|
+
test('onRoute hook should not be called when it registered after route', t => {
|
|
803
|
+
t.plan(3)
|
|
804
|
+
const fastify = Fastify()
|
|
805
|
+
|
|
806
|
+
fastify.addHook('onRoute', () => {
|
|
807
|
+
t.pass()
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
fastify.get('/', function (req, reply) {
|
|
811
|
+
reply.send()
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
fastify.addHook('onRoute', () => {
|
|
815
|
+
t.fail('should not be called')
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
fastify.ready(err => {
|
|
819
|
+
t.error(err)
|
|
820
|
+
})
|
|
821
|
+
})
|
|
822
|
+
|
|
802
823
|
test('onResponse hook should log request error', t => {
|
|
803
824
|
t.plan(4)
|
|
804
825
|
|
|
@@ -144,7 +144,7 @@ test('pretty print - commonPrefix', t => {
|
|
|
144
144
|
`
|
|
145
145
|
const flatExpected = `└── / (-)
|
|
146
146
|
├── helicopter (GET, HEAD)
|
|
147
|
-
└── hello (GET,
|
|
147
|
+
└── hello (GET, HEAD, PUT)
|
|
148
148
|
`
|
|
149
149
|
t.equal(typeof radixTree, 'string')
|
|
150
150
|
t.equal(typeof flatTree, 'string')
|
|
@@ -200,7 +200,7 @@ test('pretty print - includeMeta, includeHooks', t => {
|
|
|
200
200
|
│ • (onTimeout) ["onTimeout()"]
|
|
201
201
|
│ • (onRequest) ["anonymous()"]
|
|
202
202
|
│ • (errorHandler) "defaultErrorHandler()"
|
|
203
|
-
└── hello (GET,
|
|
203
|
+
└── hello (GET, HEAD, PUT)
|
|
204
204
|
• (onTimeout) ["onTimeout()"]
|
|
205
205
|
• (onRequest) ["anonymous()"]
|
|
206
206
|
• (errorHandler) "defaultErrorHandler()"
|
|
@@ -210,7 +210,7 @@ test('pretty print - includeMeta, includeHooks', t => {
|
|
|
210
210
|
├── helicopter (GET, HEAD)
|
|
211
211
|
│ • (onTimeout) ["onTimeout()"]
|
|
212
212
|
│ • (onRequest) ["anonymous()"]
|
|
213
|
-
└── hello (GET,
|
|
213
|
+
└── hello (GET, HEAD, PUT)
|
|
214
214
|
• (onTimeout) ["onTimeout()"]
|
|
215
215
|
• (onRequest) ["anonymous()"]
|
|
216
216
|
`
|
package/test/reply-error.test.js
CHANGED
|
@@ -206,7 +206,7 @@ test('Should throw of the schema does not exists in output', t => {
|
|
|
206
206
|
|
|
207
207
|
fastify.ready(err => {
|
|
208
208
|
t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
|
|
209
|
-
t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot
|
|
209
|
+
t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-strinfigy
|
|
210
210
|
})
|
|
211
211
|
})
|
|
212
212
|
|
|
@@ -826,7 +826,7 @@ test('Validation context in validation result', t => {
|
|
|
826
826
|
t.equal(err instanceof Error, true)
|
|
827
827
|
t.ok(err.validation, 'detailed errors')
|
|
828
828
|
t.equal(err.validationContext, 'body')
|
|
829
|
-
reply.send()
|
|
829
|
+
reply.code(400).send()
|
|
830
830
|
})
|
|
831
831
|
fastify.post('/', {
|
|
832
832
|
handler: echoParams,
|
|
@@ -7,8 +7,10 @@ import fastify, {
|
|
|
7
7
|
LightMyRequestChain,
|
|
8
8
|
LightMyRequestResponse,
|
|
9
9
|
LightMyRequestCallback,
|
|
10
|
-
InjectOptions, FastifyBaseLogger
|
|
10
|
+
InjectOptions, FastifyBaseLogger,
|
|
11
|
+
ValidationResult
|
|
11
12
|
} from '../../fastify'
|
|
13
|
+
import { ErrorObject as AjvErrorObject } from 'ajv'
|
|
12
14
|
import * as http from 'http'
|
|
13
15
|
import * as https from 'https'
|
|
14
16
|
import * as http2 from 'http2'
|
|
@@ -208,3 +210,12 @@ fastify().then(fastifyInstance => expectAssignable<FastifyInstance>(fastifyInsta
|
|
|
208
210
|
expectAssignable<FastifyPluginAsync>(async () => {})
|
|
209
211
|
expectAssignable<FastifyPluginCallback>(() => {})
|
|
210
212
|
expectAssignable<FastifyPlugin>(() => {})
|
|
213
|
+
|
|
214
|
+
const ajvErrorObject: AjvErrorObject = {
|
|
215
|
+
keyword: '',
|
|
216
|
+
instancePath: '',
|
|
217
|
+
schemaPath: '',
|
|
218
|
+
params: {},
|
|
219
|
+
message: ''
|
|
220
|
+
}
|
|
221
|
+
expectAssignable<ValidationResult>(ajvErrorObject)
|
|
@@ -49,7 +49,7 @@ interface RequestData extends RequestGenericInterface {
|
|
|
49
49
|
type Handler = RouteHandler<RequestData>
|
|
50
50
|
|
|
51
51
|
type CustomRequest = FastifyRequest<{
|
|
52
|
-
Body: RequestBody;
|
|
52
|
+
Body: RequestBody | undefined;
|
|
53
53
|
Querystring: RequestQuerystring;
|
|
54
54
|
Params: RequestParams;
|
|
55
55
|
Headers: RequestHeaders;
|
|
@@ -85,7 +85,7 @@ const getHandler: RouteHandler = function (request, _reply) {
|
|
|
85
85
|
expectType<FastifyInstance>(request.server)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const getHandlerWithCustomLogger: RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault,
|
|
88
|
+
const getHandlerWithCustomLogger: RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, ResolveFastifyRequestType<FastifyTypeProviderDefault, FastifySchema, RouteGenericInterface>, CustomLoggerInterface> = function (request, _reply) {
|
|
89
89
|
expectType<CustomLoggerInterface>(request.log)
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -104,11 +104,15 @@ const postHandler: Handler = function (request) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
function putHandler (request: CustomRequest, reply: FastifyReply) {
|
|
107
|
-
expectType<RequestBody>(request.body)
|
|
107
|
+
expectType<RequestBody | undefined>(request.body)
|
|
108
108
|
expectType<RequestParams>(request.params)
|
|
109
109
|
expectType<RequestHeaders & RawRequestDefaultExpression['headers']>(request.headers)
|
|
110
110
|
expectType<RequestQuerystring>(request.query)
|
|
111
|
-
|
|
111
|
+
if (typeof request.body === 'undefined') {
|
|
112
|
+
expectType<undefined>(request.body)
|
|
113
|
+
} else {
|
|
114
|
+
expectType<string>(request.body.content)
|
|
115
|
+
}
|
|
112
116
|
expectType<string>(request.query.from)
|
|
113
117
|
expectType<number>(request.params.id)
|
|
114
118
|
expectType<string>(request.headers['x-foobar'])
|
|
@@ -298,7 +298,7 @@ expectError(server.withTypeProvider<TypeBoxProvider>().get(
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
},
|
|
301
|
-
async (_, res)
|
|
301
|
+
async (_, res) => {
|
|
302
302
|
return false
|
|
303
303
|
}
|
|
304
304
|
))
|
|
@@ -384,11 +384,20 @@ expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
|
384
384
|
} as const
|
|
385
385
|
}
|
|
386
386
|
},
|
|
387
|
-
async (_, res)
|
|
387
|
+
async (_, res) => {
|
|
388
388
|
return false
|
|
389
389
|
}
|
|
390
390
|
))
|
|
391
391
|
|
|
392
|
+
// https://github.com/fastify/fastify/issues/4088
|
|
393
|
+
expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get('/', {
|
|
394
|
+
schema: {
|
|
395
|
+
response: { type: 'string' }
|
|
396
|
+
} as const
|
|
397
|
+
}, (_, res) => {
|
|
398
|
+
return { foo: 555 }
|
|
399
|
+
}))
|
|
400
|
+
|
|
392
401
|
// -------------------------------------------------------------------
|
|
393
402
|
// Reply Type Override
|
|
394
403
|
// -------------------------------------------------------------------
|
|
@@ -97,6 +97,38 @@ test('should be able to use setErrorHandler specify custom validation error', t
|
|
|
97
97
|
})
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
test('validation error has 400 statusCode set', t => {
|
|
101
|
+
t.plan(3)
|
|
102
|
+
|
|
103
|
+
const fastify = Fastify()
|
|
104
|
+
|
|
105
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
106
|
+
const errorResponse = {
|
|
107
|
+
message: error.message,
|
|
108
|
+
statusCode: error.statusCode || 500
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
reply.code(errorResponse.statusCode).send(errorResponse)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
fastify.post('/', { schema }, echoBody)
|
|
115
|
+
|
|
116
|
+
fastify.inject({
|
|
117
|
+
method: 'POST',
|
|
118
|
+
payload: {
|
|
119
|
+
hello: 'michelangelo'
|
|
120
|
+
},
|
|
121
|
+
url: '/'
|
|
122
|
+
}, (err, res) => {
|
|
123
|
+
t.error(err)
|
|
124
|
+
t.same(res.json(), {
|
|
125
|
+
statusCode: 400,
|
|
126
|
+
message: "body must have required property 'name'"
|
|
127
|
+
})
|
|
128
|
+
t.equal(res.statusCode, 400)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
100
132
|
test('error inside custom error handler should have validationContext', t => {
|
|
101
133
|
t.plan(1)
|
|
102
134
|
|
package/types/route.d.ts
CHANGED
|
@@ -69,14 +69,14 @@ export type RouteHandlerMethod<
|
|
|
69
69
|
ContextConfig = ContextConfigDefault,
|
|
70
70
|
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
71
71
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
72
|
-
ReturnType = ResolveFastifyReplyReturnType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
73
72
|
RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
74
73
|
Logger extends FastifyLoggerInstance = FastifyLoggerInstance
|
|
75
74
|
> = (
|
|
76
75
|
this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>,
|
|
77
76
|
request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, RequestType, Logger>,
|
|
78
77
|
reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>
|
|
79
|
-
|
|
78
|
+
// This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked.
|
|
79
|
+
) => ResolveFastifyReplyReturnType<TypeProvider, SchemaCompiler, RouteGeneric>
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Shorthand options including the handler function property
|
|
@@ -89,11 +89,10 @@ export interface RouteShorthandOptionsWithHandler<
|
|
|
89
89
|
ContextConfig = ContextConfigDefault,
|
|
90
90
|
SchemaCompiler = FastifySchema,
|
|
91
91
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
92
|
-
ReturnType = ResolveFastifyReplyReturnType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
93
92
|
RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
94
93
|
Logger extends FastifyLoggerInstance = FastifyLoggerInstance
|
|
95
94
|
> extends RouteShorthandOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger> {
|
|
96
|
-
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider,
|
|
95
|
+
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
/**
|
|
@@ -105,18 +104,18 @@ export interface RouteShorthandMethod<
|
|
|
105
104
|
RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
|
|
106
105
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
107
106
|
> {
|
|
108
|
-
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema,
|
|
107
|
+
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>, Logger extends FastifyLoggerInstance = FastifyLoggerInstance>(
|
|
109
108
|
path: string,
|
|
110
109
|
opts: RouteShorthandOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>,
|
|
111
|
-
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider,
|
|
110
|
+
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>
|
|
112
111
|
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
|
|
113
|
-
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema,
|
|
112
|
+
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>, Logger extends FastifyLoggerInstance = FastifyLoggerInstance>(
|
|
114
113
|
path: string,
|
|
115
|
-
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider,
|
|
114
|
+
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>
|
|
116
115
|
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
|
|
117
|
-
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema,
|
|
116
|
+
<RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler = FastifySchema, RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>, Logger extends FastifyLoggerInstance = FastifyLoggerInstance>(
|
|
118
117
|
path: string,
|
|
119
|
-
opts: RouteShorthandOptionsWithHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider,
|
|
118
|
+
opts: RouteShorthandOptionsWithHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>
|
|
120
119
|
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -131,13 +130,12 @@ export interface RouteOptions<
|
|
|
131
130
|
ContextConfig = ContextConfigDefault,
|
|
132
131
|
SchemaCompiler = FastifySchema,
|
|
133
132
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
134
|
-
ReturnType = ResolveFastifyReplyReturnType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
135
133
|
RequestType extends FastifyRequestType = ResolveFastifyRequestType<TypeProvider, SchemaCompiler, RouteGeneric>,
|
|
136
134
|
Logger extends FastifyLoggerInstance = FastifyLoggerInstance
|
|
137
135
|
> extends RouteShorthandOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger> {
|
|
138
136
|
method: HTTPMethods | HTTPMethods[];
|
|
139
137
|
url: string;
|
|
140
|
-
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider,
|
|
138
|
+
handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, RequestType, Logger>;
|
|
141
139
|
}
|
|
142
140
|
|
|
143
141
|
export type RouteHandler<
|
package/types/type-provider.d.ts
CHANGED
|
@@ -21,17 +21,24 @@ export type CallTypeProvider<F extends FastifyTypeProvider, I> = (F & { input: I
|
|
|
21
21
|
// -----------------------------------------------------------------------------------------------
|
|
22
22
|
|
|
23
23
|
// Used to map undefined SchemaCompiler properties to unknown
|
|
24
|
-
|
|
24
|
+
// Without brackets, UndefinedToUnknown<undefined | null> => unknown
|
|
25
|
+
type UndefinedToUnknown<T> = [T] extends [undefined] ? unknown : T
|
|
26
|
+
|
|
27
|
+
// union-aware keyof operator
|
|
28
|
+
// keyof ({ a: number} | { b: number}) => never
|
|
29
|
+
// KeysOf<{a: number} | {b: number}> => "a" | "b"
|
|
30
|
+
// this exists to allow users to override faulty type-provider logic.
|
|
31
|
+
type KeysOf<T> = T extends any ? keyof T : never
|
|
25
32
|
|
|
26
33
|
// Resolves Request types either from generic argument or Type Provider.
|
|
27
34
|
type ResolveRequestParams<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
|
|
28
|
-
UndefinedToUnknown<
|
|
35
|
+
UndefinedToUnknown<KeysOf<RouteGeneric['Params']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['params']> : RouteGeneric['Params']>
|
|
29
36
|
type ResolveRequestQuerystring<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
|
|
30
|
-
UndefinedToUnknown<
|
|
37
|
+
UndefinedToUnknown<KeysOf<RouteGeneric['Querystring']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['querystring']> : RouteGeneric['Querystring']>
|
|
31
38
|
type ResolveRequestHeaders<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
|
|
32
|
-
UndefinedToUnknown<
|
|
39
|
+
UndefinedToUnknown<KeysOf<RouteGeneric['Headers']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['headers']> : RouteGeneric['Headers']>
|
|
33
40
|
type ResolveRequestBody<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
|
|
34
|
-
UndefinedToUnknown<
|
|
41
|
+
UndefinedToUnknown<KeysOf<RouteGeneric['Body']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['body']> : RouteGeneric['Body']>
|
|
35
42
|
|
|
36
43
|
// The target request type. This type is inferenced on fastify 'requests' via generic argument assignment
|
|
37
44
|
export interface FastifyRequestType<Params = unknown, Querystring = unknown, Headers = unknown, Body = unknown> {
|