fastify 5.8.5 → 5.10.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/PROJECT_CHARTER.md +1 -1
- package/README.md +7 -10
- package/SECURITY.md +1 -1
- package/SPONSORS.md +6 -4
- package/build/build-validation.js +2 -2
- package/docs/Guides/Database.md +0 -28
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +23 -9
- package/docs/Guides/Getting-Started.md +2 -2
- package/docs/Guides/Migration-Guide-V4.md +2 -2
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Guides/Serverless.md +8 -15
- package/docs/Guides/Style-Guide.md +1 -1
- package/docs/Guides/Write-Plugin.md +1 -1
- package/docs/Reference/Encapsulation.md +27 -26
- package/docs/Reference/Errors.md +12 -4
- package/docs/Reference/HTTP2.md +10 -10
- package/docs/Reference/Hooks.md +5 -5
- package/docs/Reference/Index.md +14 -16
- package/docs/Reference/LTS.md +12 -13
- package/docs/Reference/Lifecycle.md +9 -8
- package/docs/Reference/Logging.md +47 -39
- package/docs/Reference/Middleware.md +21 -25
- package/docs/Reference/Principles.md +2 -2
- package/docs/Reference/Reply.md +6 -1
- package/docs/Reference/Request.md +29 -18
- package/docs/Reference/Routes.md +5 -2
- package/docs/Reference/Server.md +138 -11
- package/docs/Reference/Type-Providers.md +53 -9
- package/docs/Reference/TypeScript.md +3 -3
- package/docs/Reference/Validation-and-Serialization.md +15 -2
- package/docs/Reference/Warnings.md +11 -6
- package/eslint.config.js +7 -2
- package/fastify.d.ts +13 -3
- package/fastify.js +60 -31
- package/lib/content-type-parser.js +2 -0
- package/lib/content-type.js +34 -1
- package/lib/context.js +0 -3
- package/lib/decorate.js +11 -3
- package/lib/error-handler.js +10 -28
- package/lib/error-serializer.js +59 -59
- package/lib/errors.js +23 -1
- package/lib/four-oh-four.js +22 -19
- package/lib/handle-request.js +12 -5
- package/lib/log-controller.js +169 -0
- package/lib/logger-factory.js +25 -4
- package/lib/plugin-override.js +2 -1
- package/lib/plugin-utils.js +5 -5
- package/lib/reply.js +96 -50
- package/lib/req-id-gen-factory.js +4 -1
- package/lib/request.js +16 -6
- package/lib/route.js +47 -41
- package/lib/schema-controller.js +1 -1
- package/lib/schemas.js +37 -30
- package/lib/symbols.js +4 -2
- package/lib/validation.js +10 -13
- package/lib/warnings.js +22 -4
- package/package.json +15 -17
- package/scripts/validate-ecosystem-links.js +1 -0
- package/test/bundler/esbuild/package.json +1 -1
- package/test/close-pipelining.test.js +1 -2
- package/test/content-type.test.js +20 -0
- package/test/custom-http-server.test.js +38 -0
- package/test/decorator-instance-properties.test.js +63 -0
- package/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/test/genReqId.test.js +24 -0
- package/test/hooks.test.js +94 -0
- package/test/http-methods/get.test.js +1 -1
- package/test/http2/plain.test.js +135 -0
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/https/https.test.js +1 -2
- package/test/internals/errors.test.js +31 -1
- package/test/internals/logger.test.js +322 -0
- package/test/internals/plugin.test.js +3 -1
- package/test/internals/reply.test.js +35 -4
- package/test/internals/request.test.js +37 -10
- package/test/internals/schema-controller-perf.test.js +33 -0
- package/test/logger/logging.test.js +57 -1
- package/test/logger/options.test.js +38 -1
- package/test/reply-error.test.js +1 -1
- package/test/reply-trailers.test.js +70 -0
- package/test/request-media-type.test.js +105 -0
- package/test/route-prefix.test.js +34 -0
- package/test/router-options.test.js +222 -11
- package/test/schema-serialization.test.js +108 -0
- package/test/schema-validation.test.js +24 -0
- package/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/test/stream.4.test.js +2 -2
- package/test/throw.test.js +14 -0
- package/test/trust-proxy.test.js +70 -30
- package/test/types/content-type-parser.tst.ts +70 -0
- package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
- package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
- package/test/types/errors.tst.ts +91 -0
- package/test/types/fastify.tst.ts +351 -0
- package/test/types/hooks.tst.ts +578 -0
- package/test/types/instance.tst.ts +597 -0
- package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
- package/test/types/plugin.tst.ts +96 -0
- package/test/types/register.tst.ts +245 -0
- package/test/types/reply.tst.ts +297 -0
- package/test/types/request.tst.ts +199 -0
- package/test/types/route.tst.ts +576 -0
- package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
- package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
- package/test/types/tsconfig.json +9 -0
- package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
- package/test/types/using.tst.ts +14 -0
- package/types/errors.d.ts +4 -0
- package/types/instance.d.ts +2 -0
- package/types/logger.d.ts +22 -0
- package/types/request.d.ts +23 -2
- package/test/types/content-type-parser.test-d.ts +0 -72
- package/test/types/errors.test-d.ts +0 -90
- package/test/types/fastify.test-d.ts +0 -352
- package/test/types/hooks.test-d.ts +0 -550
- package/test/types/import.ts +0 -2
- package/test/types/instance.test-d.ts +0 -588
- package/test/types/plugin.test-d.ts +0 -97
- package/test/types/register.test-d.ts +0 -237
- package/test/types/reply.test-d.ts +0 -254
- package/test/types/request.test-d.ts +0 -188
- package/test/types/route.test-d.ts +0 -553
- package/test/types/using.test-d.ts +0 -17
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import { expect } from 'tstyche'
|
|
3
|
+
import fastify, {
|
|
4
|
+
FastifyContextConfig,
|
|
5
|
+
FastifyReply,
|
|
6
|
+
FastifyRequest,
|
|
7
|
+
FastifySchema,
|
|
8
|
+
FastifyTypeProviderDefault,
|
|
9
|
+
RawRequestDefaultExpression,
|
|
10
|
+
RouteHandler,
|
|
11
|
+
RouteHandlerMethod
|
|
12
|
+
} from '../../fastify.js'
|
|
13
|
+
import { FastifyInstance } from '../../types/instance.js'
|
|
14
|
+
import { FastifyBaseLogger } from '../../types/logger.js'
|
|
15
|
+
import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply.js'
|
|
16
|
+
import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route.js'
|
|
17
|
+
import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils.js'
|
|
18
|
+
|
|
19
|
+
type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string
|
|
20
|
+
type DefaultFastifyReplyWithCode<Code extends number> = FastifyReply<RouteGenericInterface, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, ResolveReplyTypeWithRouteGeneric<RouteGenericInterface['Reply'], Code, FastifySchema, FastifyTypeProviderDefault>>
|
|
21
|
+
|
|
22
|
+
const getHandler: RouteHandlerMethod = function (_request, reply) {
|
|
23
|
+
expect(reply.raw).type.toBe<RawReplyDefaultExpression>()
|
|
24
|
+
expect(reply.log).type.toBe<FastifyBaseLogger>()
|
|
25
|
+
expect(reply.request).type.toBe<
|
|
26
|
+
FastifyRequest<RouteGenericInterface, RawServerDefault, RawRequestDefaultExpression>
|
|
27
|
+
>()
|
|
28
|
+
expect(reply.code).type.toBe<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>()
|
|
29
|
+
expect(reply.status).type.toBe<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>()
|
|
30
|
+
expect(reply.code(100 as number).send).type.toBe<(...args: [payload?: unknown]) => FastifyReply>()
|
|
31
|
+
expect(reply.elapsedTime).type.toBe<number>()
|
|
32
|
+
expect(reply.statusCode).type.toBe<number>()
|
|
33
|
+
expect(reply.sent).type.toBe<boolean>()
|
|
34
|
+
expect(reply.writeEarlyHints).type.toBe<
|
|
35
|
+
(hints: Record<string, string | string[]>, callback?: (() => void) | undefined) => void
|
|
36
|
+
>()
|
|
37
|
+
expect(reply.send).type.toBe<((...args: [payload?: unknown]) => FastifyReply)>()
|
|
38
|
+
expect(reply.header).type.toBeAssignableTo<(key: string, value: any) => FastifyReply>()
|
|
39
|
+
expect(reply.headers).type.toBeAssignableTo<(values: { [key: string]: any }) => FastifyReply>()
|
|
40
|
+
expect(reply.getHeader).type.toBeAssignableTo<(key: string) => number | string | string[] | undefined>()
|
|
41
|
+
expect(reply.getHeaders).type.toBeAssignableTo<() => { [key: string]: number | string | string[] | undefined }>()
|
|
42
|
+
expect(reply.removeHeader).type.toBeAssignableTo<(key: string) => FastifyReply>()
|
|
43
|
+
expect(reply.hasHeader).type.toBeAssignableTo<(key: string) => boolean>()
|
|
44
|
+
expect(reply.redirect).type.toBe<(url: string, statusCode?: number) => FastifyReply>()
|
|
45
|
+
expect(reply.hijack).type.toBe<() => FastifyReply>()
|
|
46
|
+
expect(reply.callNotFound).type.toBe<() => void>()
|
|
47
|
+
expect(reply.type).type.toBe<(contentType: string) => FastifyReply>()
|
|
48
|
+
expect(reply.serializer).type.toBe<(fn: (payload: any) => string) => FastifyReply>()
|
|
49
|
+
expect(reply.serialize).type.toBe<(payload: any) => string | ArrayBuffer | Buffer>()
|
|
50
|
+
expect(reply.then).type.toBe<(fulfilled: () => void, rejected: (err: Error) => void) => void>()
|
|
51
|
+
expect(reply.trailer).type.toBe<
|
|
52
|
+
(
|
|
53
|
+
key: string,
|
|
54
|
+
fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise<string>) |
|
|
55
|
+
((reply: FastifyReply, payload: string | Buffer | null,
|
|
56
|
+
done: (err: Error | null, value?: string) => void) => void)
|
|
57
|
+
) => FastifyReply
|
|
58
|
+
>()
|
|
59
|
+
expect(reply.hasTrailer).type.toBe<(key: string) => boolean>()
|
|
60
|
+
expect(reply.removeTrailer).type.toBe<(key: string) => FastifyReply>()
|
|
61
|
+
expect(reply.server).type.toBe<FastifyInstance>()
|
|
62
|
+
expect(reply.getSerializationFunction).type.toBeAssignableTo<
|
|
63
|
+
((httpStatus: string) => DefaultSerializationFunction | undefined)
|
|
64
|
+
>()
|
|
65
|
+
expect(reply.getSerializationFunction).type.toBeAssignableTo<
|
|
66
|
+
((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined)
|
|
67
|
+
>()
|
|
68
|
+
expect(reply.compileSerializationSchema).type.toBeAssignableTo<
|
|
69
|
+
((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction)
|
|
70
|
+
>()
|
|
71
|
+
expect(reply.serializeInput).type.toBeAssignableTo<
|
|
72
|
+
((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)
|
|
73
|
+
>()
|
|
74
|
+
expect(reply.serializeInput).type.toBeAssignableTo<
|
|
75
|
+
((input: { [key: string]: unknown }, httpStatus: string) => unknown)
|
|
76
|
+
>()
|
|
77
|
+
expect(reply.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
|
|
78
|
+
expect(reply.getDecorator<string>('foo')).type.toBe<string>()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ReplyPayload {
|
|
82
|
+
Reply: {
|
|
83
|
+
test: boolean;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ReplyArrayPayload {
|
|
88
|
+
Reply: string[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ReplyUnion {
|
|
92
|
+
Reply: {
|
|
93
|
+
success: boolean;
|
|
94
|
+
} | {
|
|
95
|
+
error: string;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface ReplyHttpCodes {
|
|
100
|
+
Reply: {
|
|
101
|
+
'1xx': number,
|
|
102
|
+
200: 'abc',
|
|
103
|
+
201: boolean,
|
|
104
|
+
300: { foo: string },
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface InvalidReplyHttpCodes {
|
|
109
|
+
Reply: {
|
|
110
|
+
'1xx': number,
|
|
111
|
+
200: string,
|
|
112
|
+
999: boolean,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface ReplyVoid {
|
|
117
|
+
Reply: void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface ReplyUndefined {
|
|
121
|
+
Reply: undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Issue #5534 scenario: 204 No Content should allow empty send(), 201 Created should require payload
|
|
125
|
+
// Note: `204: undefined` gets converted to `unknown` via UndefinedToUnknown in type-provider.d.ts,
|
|
126
|
+
// meaning send() is optional but send({}) is also allowed. Use `void` instead of `undefined`
|
|
127
|
+
// if you want stricter "no payload allowed" semantics.
|
|
128
|
+
interface ReplyHttpCodesWithNoContent {
|
|
129
|
+
Reply: {
|
|
130
|
+
201: { id: string };
|
|
131
|
+
204: undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const typedHandler: RouteHandler<ReplyPayload> = async (request, reply) => {
|
|
136
|
+
// When Reply type is specified, send() requires a payload argument
|
|
137
|
+
expect(reply.send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
|
|
138
|
+
expect(reply.code(100).send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const server = fastify()
|
|
142
|
+
server.get('/get', getHandler)
|
|
143
|
+
server.get('/typed', typedHandler)
|
|
144
|
+
server.get<ReplyPayload>('/get-generic-send', async function handler (request, reply) {
|
|
145
|
+
reply.send({ test: true })
|
|
146
|
+
})
|
|
147
|
+
// When Reply type is specified, send() requires a payload - calling without arguments should error
|
|
148
|
+
server.get<ReplyPayload>('/get-generic-send-missing-payload', async function handler (request, reply) {
|
|
149
|
+
reply
|
|
150
|
+
// @ts-expect-error Expected 1 arguments, but got 0.
|
|
151
|
+
.send()
|
|
152
|
+
})
|
|
153
|
+
server.get<ReplyPayload>('/get-generic-return', async function handler (request, reply) {
|
|
154
|
+
return { test: false }
|
|
155
|
+
})
|
|
156
|
+
server.get<ReplyPayload>('/get-generic-send-error', async function handler (request, reply) {
|
|
157
|
+
reply.send({
|
|
158
|
+
// @ts-expect-error 'foo' does not exist in type '{ test: boolean; }'.
|
|
159
|
+
foo: 'bar'
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
// @ts-expect-error No overload matches this call.
|
|
163
|
+
server.get<ReplyPayload>('/get-generic-return-error', async function handler (request, reply) {
|
|
164
|
+
return { foo: 'bar' }
|
|
165
|
+
})
|
|
166
|
+
server.get<ReplyUnion>('/get-generic-union-send', async function handler (request, reply) {
|
|
167
|
+
if (0 as number === 0) {
|
|
168
|
+
reply.send({ success: true })
|
|
169
|
+
} else {
|
|
170
|
+
reply.send({ error: 'error' })
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
server.get<ReplyUnion>('/get-generic-union-return', async function handler (request, reply) {
|
|
174
|
+
if (0 as number === 0) {
|
|
175
|
+
return { success: true }
|
|
176
|
+
} else {
|
|
177
|
+
return { error: 'error' }
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
server.get<ReplyUnion>('/get-generic-union-send-error-1', async function handler (request, reply) {
|
|
181
|
+
reply.send({
|
|
182
|
+
// @ts-expect-error 'successes' does not exist in type '{ success: boolean; } | { error: string; }'.
|
|
183
|
+
successes: true
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
server.get<ReplyUnion>('/get-generic-union-send-error-2', async function handler (request, reply) {
|
|
187
|
+
reply.send({
|
|
188
|
+
// @ts-expect-error Type 'number' is not assignable to type 'string'.
|
|
189
|
+
error: 500
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
// @ts-expect-error No overload matches this call.
|
|
193
|
+
server.get<ReplyUnion>('/get-generic-union-return-error-1', async function handler (request, reply) {
|
|
194
|
+
return { successes: true }
|
|
195
|
+
})
|
|
196
|
+
// @ts-expect-error No overload matches this call.
|
|
197
|
+
server.get<ReplyUnion>('/get-generic-union-return-error-2', async function handler (request, reply) {
|
|
198
|
+
return { error: 500 }
|
|
199
|
+
})
|
|
200
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send', async function handler (request, reply) {
|
|
201
|
+
reply.code(200).send('abc')
|
|
202
|
+
reply.code(201).send(true)
|
|
203
|
+
reply.code(300).send({ foo: 'bar' })
|
|
204
|
+
reply.code(101).send(123)
|
|
205
|
+
})
|
|
206
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-1', async function handler (request, reply) {
|
|
207
|
+
reply.code(200)
|
|
208
|
+
// @ts-expect-error Argument of type '"def"' is not assignable to parameter of type '"abc"'.
|
|
209
|
+
.send('def')
|
|
210
|
+
})
|
|
211
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-2', async function handler (request, reply) {
|
|
212
|
+
reply.code(201)
|
|
213
|
+
// @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'boolean'.
|
|
214
|
+
.send(0)
|
|
215
|
+
})
|
|
216
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-3', async function handler (request, reply) {
|
|
217
|
+
reply.code(300).send({
|
|
218
|
+
// @ts-expect-error Type 'number' is not assignable to type 'string'.
|
|
219
|
+
foo: 123
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-4', async function handler (request, reply) {
|
|
223
|
+
reply.code(100)
|
|
224
|
+
// @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'.
|
|
225
|
+
.send('asdasd')
|
|
226
|
+
})
|
|
227
|
+
server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-5', async function handler (request, reply) {
|
|
228
|
+
reply
|
|
229
|
+
// @ts-expect-error Argument of type '401' is not assignable to parameter of type '100 | 101 | 102 | ... | 300'.
|
|
230
|
+
.code(401).send({
|
|
231
|
+
// @ts-expect-error Type 'number' is not assignable to type 'string'.
|
|
232
|
+
foo: 123
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
server.get<ReplyArrayPayload>('/get-generic-array-send', async function handler (request, reply) {
|
|
236
|
+
reply.code(200).send([''])
|
|
237
|
+
})
|
|
238
|
+
server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
|
|
239
|
+
reply.code(200)
|
|
240
|
+
// @ts-expect-error Argument of type 'string' is not assignable to parameter of type '{ '1xx': number; 200: string; 999: boolean; }'.
|
|
241
|
+
.send('')
|
|
242
|
+
})
|
|
243
|
+
server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
|
|
244
|
+
reply.code(200).send({
|
|
245
|
+
'1xx': 0,
|
|
246
|
+
200: '',
|
|
247
|
+
999: false
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) {
|
|
252
|
+
// accept is a header provided by @types/node
|
|
253
|
+
reply.getHeader('accept')
|
|
254
|
+
expect(reply.getHeaders()).type.toHaveProperty('accept')
|
|
255
|
+
reply.hasHeader('accept')
|
|
256
|
+
reply.header('accept', 'test')
|
|
257
|
+
reply.headers({ accept: 'test' })
|
|
258
|
+
reply.removeHeader('accept')
|
|
259
|
+
|
|
260
|
+
// x-fastify-test is not a header provided by @types/node
|
|
261
|
+
// and should not result in a typing error
|
|
262
|
+
reply.getHeader('x-fastify-test')
|
|
263
|
+
expect(reply.getHeaders()).type.toHaveProperty('x-fastify-test')
|
|
264
|
+
reply.hasHeader('x-fastify-test')
|
|
265
|
+
reply.header('x-fastify-test', 'test')
|
|
266
|
+
reply.headers({ 'x-fastify-test': 'test' })
|
|
267
|
+
reply.removeHeader('x-fastify-test')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Test: send() without arguments is valid when no Reply type is specified (default unknown)
|
|
271
|
+
server.get('/get-no-type-send-empty', async function handler (request, reply) {
|
|
272
|
+
reply.send()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// Test: send() without arguments is valid when Reply type is void
|
|
276
|
+
server.get<ReplyVoid>('/get-void-send-empty', async function handler (request, reply) {
|
|
277
|
+
reply.send()
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Test: send() without arguments is valid when Reply type is undefined
|
|
281
|
+
server.get<ReplyUndefined>('/get-undefined-send-empty', async function handler (request, reply) {
|
|
282
|
+
reply.send()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// Issue #5534 scenario: HTTP status codes with 204 No Content
|
|
286
|
+
server.get<ReplyHttpCodesWithNoContent>('/get-http-codes-no-content', async function handler (request, reply) {
|
|
287
|
+
// 204 No Content - send() without payload is valid because Reply is undefined
|
|
288
|
+
reply.code(204).send()
|
|
289
|
+
// 201 Created - send() requires payload
|
|
290
|
+
reply.code(201).send({ id: '123' })
|
|
291
|
+
})
|
|
292
|
+
// 201 Created without payload should error
|
|
293
|
+
server.get<ReplyHttpCodesWithNoContent>('/get-http-codes-201-missing-payload', async function handler (request, reply) {
|
|
294
|
+
reply.code(201)
|
|
295
|
+
// @ts-expect-error Expected 1 arguments, but got 0.
|
|
296
|
+
.send()
|
|
297
|
+
})
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { expect } from 'tstyche'
|
|
2
|
+
import fastify, {
|
|
3
|
+
ContextConfigDefault,
|
|
4
|
+
FastifyContextConfig,
|
|
5
|
+
FastifyLogFn,
|
|
6
|
+
FastifySchema,
|
|
7
|
+
FastifyTypeProviderDefault,
|
|
8
|
+
RawReplyDefaultExpression,
|
|
9
|
+
RawRequestDefaultExpression,
|
|
10
|
+
RawServerDefault,
|
|
11
|
+
RequestBodyDefault,
|
|
12
|
+
RequestGenericInterface,
|
|
13
|
+
RouteHandler,
|
|
14
|
+
RouteHandlerMethod,
|
|
15
|
+
SafePromiseLike
|
|
16
|
+
} from '../../fastify.js'
|
|
17
|
+
import { FastifyInstance } from '../../types/instance.js'
|
|
18
|
+
import { FastifyBaseLogger } from '../../types/logger.js'
|
|
19
|
+
import { FastifyReply } from '../../types/reply.js'
|
|
20
|
+
import { FastifyRequest, RequestRouteOptions, ValidationFunction } from '../../types/request.js'
|
|
21
|
+
import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route.js'
|
|
22
|
+
import { RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from '../../types/utils.js'
|
|
23
|
+
|
|
24
|
+
interface RequestBody {
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface RequestQuerystring {
|
|
29
|
+
from: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface RequestParams {
|
|
33
|
+
id: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RequestHeaders {
|
|
37
|
+
'x-foobar': string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface RequestData extends RequestGenericInterface {
|
|
41
|
+
Body: RequestBody;
|
|
42
|
+
Querystring: RequestQuerystring;
|
|
43
|
+
Params: RequestParams;
|
|
44
|
+
Headers: RequestHeaders;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Handler = RouteHandler<RequestData>
|
|
48
|
+
|
|
49
|
+
type CustomRequest = FastifyRequest<{
|
|
50
|
+
Body: RequestBody | undefined;
|
|
51
|
+
Querystring: RequestQuerystring;
|
|
52
|
+
Params: RequestParams;
|
|
53
|
+
Headers: RequestHeaders;
|
|
54
|
+
}>
|
|
55
|
+
|
|
56
|
+
type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers'
|
|
57
|
+
type ExpectedGetValidationFunction = (input: { [key: string]: unknown }) => boolean
|
|
58
|
+
|
|
59
|
+
interface CustomLoggerInterface extends FastifyBaseLogger {
|
|
60
|
+
foo: FastifyLogFn; // custom severity logger method
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const getHandler: RouteHandler = function (request, _reply) {
|
|
64
|
+
expect(request.url).type.toBe<string>()
|
|
65
|
+
expect(request.originalUrl).type.toBe<string>()
|
|
66
|
+
expect(request.method).type.toBe<string>()
|
|
67
|
+
expect(request.routeOptions).type.toBe<Readonly<RequestRouteOptions>>()
|
|
68
|
+
expect(request.is404).type.toBe<boolean>()
|
|
69
|
+
expect(request.hostname).type.toBe<string>()
|
|
70
|
+
expect(request.host).type.toBe<string>()
|
|
71
|
+
expect(request.port).type.toBe<number | null>()
|
|
72
|
+
expect(request.ip).type.toBe<string>()
|
|
73
|
+
expect(request.ips).type.toBe<string[] | undefined>()
|
|
74
|
+
expect(request.raw).type.toBe<RawRequestDefaultExpression>()
|
|
75
|
+
expect(request.body).type.toBe<RequestBodyDefault>()
|
|
76
|
+
expect(request.params).type.toBe<RequestParamsDefault>()
|
|
77
|
+
expect(request.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
|
|
78
|
+
expect(request.routeOptions.schema).type.toBe<FastifySchema | undefined>()
|
|
79
|
+
expect(request.routeOptions.handler).type.toBe<RouteHandlerMethod>()
|
|
80
|
+
expect(request.routeOptions.url).type.toBe<string | undefined>()
|
|
81
|
+
expect(request.routeOptions.version).type.toBe<string | undefined>()
|
|
82
|
+
|
|
83
|
+
expect(request.headers).type.toBe<RequestHeadersDefault & RawRequestDefaultExpression['headers']>()
|
|
84
|
+
request.headers = {}
|
|
85
|
+
|
|
86
|
+
expect(request.query).type.toBe<RequestQuerystringDefault>()
|
|
87
|
+
expect(request.id).type.toBe<string>()
|
|
88
|
+
expect(request.log).type.toBe<FastifyBaseLogger>()
|
|
89
|
+
expect(request.socket).type.toBe<RawRequestDefaultExpression['socket']>()
|
|
90
|
+
expect(request.signal).type.toBe<AbortSignal>()
|
|
91
|
+
expect(request.validationError).type.toBe<Error & { validation: any; validationContext: string } | undefined>()
|
|
92
|
+
expect(request.server).type.toBe<FastifyInstance>()
|
|
93
|
+
expect(request.getValidationFunction).type.toBeAssignableTo<
|
|
94
|
+
(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction | undefined
|
|
95
|
+
>()
|
|
96
|
+
expect(request.getValidationFunction).type.toBeAssignableTo<
|
|
97
|
+
(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction | undefined
|
|
98
|
+
>()
|
|
99
|
+
expect(request.getValidationFunction('body')).type.toBe<ValidationFunction | undefined>()
|
|
100
|
+
expect(request.getValidationFunction({ type: 'object' })).type.toBe<ValidationFunction | undefined>()
|
|
101
|
+
expect(request.validateInput).type.toBeAssignableTo<
|
|
102
|
+
(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean
|
|
103
|
+
>()
|
|
104
|
+
expect(request.validateInput).type.toBeAssignableTo<
|
|
105
|
+
(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean
|
|
106
|
+
>()
|
|
107
|
+
expect(request.getDecorator<string>('foo')).type.toBe<string>()
|
|
108
|
+
expect(request.setDecorator('foo', 'hello')).type.toBe<void>()
|
|
109
|
+
expect(request.setDecorator<string>('foo', 'hello')).type.toBe<void>()
|
|
110
|
+
expect(request.setDecorator<string>).type.not.toBeCallableWith('foo', true)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const getHandlerWithCustomLogger: RouteHandlerMethod<
|
|
114
|
+
RawServerDefault,
|
|
115
|
+
RawRequestDefaultExpression,
|
|
116
|
+
RawReplyDefaultExpression,
|
|
117
|
+
RouteGenericInterface,
|
|
118
|
+
ContextConfigDefault,
|
|
119
|
+
FastifySchema,
|
|
120
|
+
FastifyTypeProviderDefault,
|
|
121
|
+
CustomLoggerInterface
|
|
122
|
+
> = function (request, _reply) {
|
|
123
|
+
expect(request.log).type.toBe<CustomLoggerInterface>()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const postHandler: Handler = function (request) {
|
|
127
|
+
expect(request.body).type.toBe<RequestBody>()
|
|
128
|
+
expect(request.params).type.toBe<RequestParams>()
|
|
129
|
+
expect(
|
|
130
|
+
request.headers
|
|
131
|
+
).type.toBe<RequestHeaders & RawRequestDefaultExpression['headers']>()
|
|
132
|
+
expect(request.query).type.toBe<RequestQuerystring>()
|
|
133
|
+
expect(request.body.content).type.toBe<string>()
|
|
134
|
+
expect(request.query.from).type.toBe<string>()
|
|
135
|
+
expect(request.params.id).type.toBe<number>()
|
|
136
|
+
expect(request.headers['x-foobar']).type.toBe<string>()
|
|
137
|
+
expect(request.server).type.toBe<FastifyInstance>()
|
|
138
|
+
expect(request.routeOptions.config).type.toBe<FastifyContextConfig & FastifyRouteConfig>()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function putHandler (request: CustomRequest, reply: FastifyReply) {
|
|
142
|
+
expect(request.body).type.toBe<RequestBody | undefined>()
|
|
143
|
+
expect(request.params).type.toBe<RequestParams>()
|
|
144
|
+
expect(request.headers).type.toBe<RequestHeaders & RawRequestDefaultExpression['headers']>()
|
|
145
|
+
expect(request.query).type.toBe<RequestQuerystring>()
|
|
146
|
+
if (request.body === undefined) {
|
|
147
|
+
expect(request.body).type.toBe<undefined>()
|
|
148
|
+
} else {
|
|
149
|
+
expect(request.body.content).type.toBe<string>()
|
|
150
|
+
}
|
|
151
|
+
expect(request.query.from).type.toBe<string>()
|
|
152
|
+
expect(request.params.id).type.toBe<number>()
|
|
153
|
+
expect(request.headers['x-foobar']).type.toBe<string>()
|
|
154
|
+
expect(request.server).type.toBe<FastifyInstance>()
|
|
155
|
+
expect(request.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const server = fastify()
|
|
159
|
+
server.get('/get', getHandler)
|
|
160
|
+
server.post('/post', postHandler)
|
|
161
|
+
server.put('/put', putHandler)
|
|
162
|
+
|
|
163
|
+
const customLogger: CustomLoggerInterface = {
|
|
164
|
+
level: 'info',
|
|
165
|
+
silent: () => { },
|
|
166
|
+
info: () => { },
|
|
167
|
+
warn: () => { },
|
|
168
|
+
error: () => { },
|
|
169
|
+
fatal: () => { },
|
|
170
|
+
trace: () => { },
|
|
171
|
+
debug: () => { },
|
|
172
|
+
foo: () => { }, // custom severity logger method
|
|
173
|
+
child: () => customLogger
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const serverWithCustomLogger = fastify({ loggerInstance: customLogger })
|
|
177
|
+
|
|
178
|
+
expect(serverWithCustomLogger).type.not.toBeAssignableTo<
|
|
179
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
180
|
+
& Promise<
|
|
181
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
182
|
+
>
|
|
183
|
+
>()
|
|
184
|
+
|
|
185
|
+
expect(serverWithCustomLogger).type.toBeAssignableTo<
|
|
186
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
187
|
+
& PromiseLike<
|
|
188
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
189
|
+
>
|
|
190
|
+
>()
|
|
191
|
+
|
|
192
|
+
expect(serverWithCustomLogger).type.toBe<
|
|
193
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
194
|
+
& SafePromiseLike<
|
|
195
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
196
|
+
>
|
|
197
|
+
>()
|
|
198
|
+
|
|
199
|
+
serverWithCustomLogger.get('/get', getHandlerWithCustomLogger)
|