fastify 6.0.0-alpha.3 → 6.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/SECURITY.md +6 -0
  2. package/SPONSORS.md +1 -0
  3. package/docs/Guides/Ecosystem.md +10 -53
  4. package/docs/Reference/Errors.md +0 -2
  5. package/docs/Reference/Reply.md +20 -2
  6. package/docs/Reference/Request.md +8 -0
  7. package/docs/Reference/Routes.md +6 -2
  8. package/docs/Reference/Server.md +2 -2
  9. package/docs/Reference/Validation-and-Serialization.md +33 -0
  10. package/docs/Reference/Warnings.md +2 -0
  11. package/fastify.js +1 -1
  12. package/lib/content-type-parser.js +28 -13
  13. package/lib/content-type.js +4 -4
  14. package/lib/errors.js +0 -5
  15. package/lib/handle-request.js +7 -20
  16. package/lib/reply.js +11 -4
  17. package/lib/request.js +9 -5
  18. package/lib/route.js +6 -2
  19. package/lib/validation.js +73 -14
  20. package/lib/warnings.js +10 -1
  21. package/package.json +1 -1
  22. package/test/child-logger-factory.test.js +25 -0
  23. package/test/content-parser.test.js +60 -0
  24. package/test/content-type-parser-async-context.test.js +76 -0
  25. package/test/custom-parser.4.test.js +40 -0
  26. package/test/http2/plain.test.js +45 -0
  27. package/test/internals/errors.test.js +1 -11
  28. package/test/internals/reply-serialize.test.js +20 -0
  29. package/test/internals/reply.test.js +30 -1
  30. package/test/internals/request.test.js +50 -0
  31. package/test/internals/validation.test.js +66 -0
  32. package/test/reply-media-type.test.js +57 -0
  33. package/test/rfc-10008.test.js +6 -9
  34. package/test/schema-feature.test.js +1 -1
  35. package/test/schema-validation.test.js +251 -1
  36. package/test/types/errors.tst.ts +0 -1
  37. package/test/types/fastify.tst.ts +5 -0
  38. package/test/types/route.tst.ts +21 -0
  39. package/types/errors.d.ts +0 -1
  40. package/types/instance.d.ts +4 -4
  41. package/types/route.d.ts +3 -2
@@ -402,6 +402,56 @@ test('Request with trust proxy - handles multiple entries in x-forwarded-host/pr
402
402
  t.assert.strictEqual(request.protocol, 'https')
403
403
  })
404
404
 
405
+ test('Request with trust proxy - host getter reads the merged headers once', t => {
406
+ t.plan(2)
407
+ const req = {
408
+ method: 'GET',
409
+ url: '/',
410
+ socket: { remoteAddress: 'ip' },
411
+ headers: { host: 'fastify.test' }
412
+ }
413
+
414
+ const TpRequest = Request.buildRequest(Request, true)
415
+ const request = new TpRequest('id', 'params', req, 'query', 'log')
416
+ // the headers getter may allocate a new object once request.headers was
417
+ // assigned in a hook, so the host getter must read it once per access
418
+ const additional = { 'user-assigned': 'yes' }
419
+ let reads = 0
420
+ Object.defineProperty(request, 'headers', {
421
+ get () {
422
+ reads++
423
+ return Object.assign({}, req.headers, additional)
424
+ }
425
+ })
426
+ t.assert.strictEqual(request.host, 'fastify.test')
427
+ t.assert.strictEqual(reads, 1)
428
+ })
429
+
430
+ test('Request with trust proxy - protocol getter reads the merged headers once', t => {
431
+ t.plan(2)
432
+ const req = {
433
+ method: 'GET',
434
+ url: '/',
435
+ socket: { remoteAddress: 'ip' },
436
+ headers: { 'x-forwarded-proto': 'https' }
437
+ }
438
+
439
+ const TpRequest = Request.buildRequest(Request, true)
440
+ const request = new TpRequest('id', 'params', req, 'query', 'log')
441
+ // the headers getter may allocate a new object once request.headers was
442
+ // assigned in a hook, so the protocol getter must read it once per access
443
+ const additional = { 'user-assigned': 'yes' }
444
+ let reads = 0
445
+ Object.defineProperty(request, 'headers', {
446
+ get () {
447
+ reads++
448
+ return Object.assign({}, req.headers, additional)
449
+ }
450
+ })
451
+ t.assert.strictEqual(request.protocol, 'https')
452
+ t.assert.strictEqual(reads, 1)
453
+ })
454
+
405
455
  test('Request with trust proxy - plain', t => {
406
456
  t.plan(1)
407
457
  const headers = {
@@ -10,6 +10,72 @@ const { normalizeSchema } = require('../../lib/schemas')
10
10
  const symbols = require('../../lib/validation').symbols
11
11
  const { kSchemaVisited } = require('../../lib/symbols')
12
12
 
13
+ for (const [part, symbol] of [
14
+ ['params', symbols.paramsSchema],
15
+ ['body', symbols.bodySchema],
16
+ ['query', symbols.querystringSchema],
17
+ ['headers', symbols.headersSchema]
18
+ ]) {
19
+ for (const async of [false, true]) {
20
+ test(`validate reads only the validated ${part} once (${async ? 'async' : 'sync'})`, async t => {
21
+ const request = {}
22
+ const value = { hello: 'world' }
23
+ let reads = 0
24
+ let calls = 0
25
+
26
+ for (const name of ['params', 'body', 'query', 'headers']) {
27
+ Object.defineProperty(request, name, {
28
+ get () {
29
+ t.assert.strictEqual(name, part, 'unvalidated request parts must not be read')
30
+ reads++
31
+ return value
32
+ }
33
+ })
34
+ }
35
+
36
+ const context = {
37
+ [symbol]: data => {
38
+ calls++
39
+ t.assert.strictEqual(data, value)
40
+ return async ? Promise.resolve(data) : true
41
+ }
42
+ }
43
+
44
+ t.assert.strictEqual(await validation.validate(context, request), false)
45
+ t.assert.strictEqual(reads, 1)
46
+ t.assert.strictEqual(calls, 1)
47
+ })
48
+ }
49
+ }
50
+
51
+ test('validate skips a body without a matching content-type schema', t => {
52
+ const context = { [symbols.bodySchema]: { 'application/json': () => true } }
53
+ const request = {
54
+ mediaType: 'text/plain',
55
+ get body () {
56
+ t.assert.fail('an unvalidated body must not be read')
57
+ }
58
+ }
59
+
60
+ t.assert.strictEqual(validation.validate(context, request), false)
61
+ })
62
+
63
+ test('validate passes null for an undefined request part and preserves other values', t => {
64
+ for (const value of [undefined, null, false, 0, '']) {
65
+ let calls = 0
66
+ const context = {
67
+ [symbols.bodySchema]: data => {
68
+ calls++
69
+ t.assert.strictEqual(data, value === undefined ? null : value)
70
+ return true
71
+ }
72
+ }
73
+
74
+ t.assert.strictEqual(validation.validate(context, { body: value }), false)
75
+ t.assert.strictEqual(calls, 1)
76
+ }
77
+ })
78
+
13
79
  test('Symbols', t => {
14
80
  t.plan(5)
15
81
  t.assert.strictEqual(typeof symbols.responseSchema, 'symbol')
@@ -129,3 +129,60 @@ test('reply.mediaType should reflect the last type set', async (t) => {
129
129
  const body = response.body
130
130
  t.assert.strictEqual(body, 'mediaType = text/plain')
131
131
  })
132
+
133
+ test('reply.mediaType should match the content-type set via raw.setHeader', async (t) => {
134
+ t.plan(2)
135
+
136
+ const fastify = Fastify()
137
+
138
+ fastify.get('/', (request, reply) => {
139
+ reply.raw.setHeader('content-type', 'application/json; charset=utf-8')
140
+ t.assert.strictEqual(reply.mediaType, 'application/json')
141
+ reply.send({ mediaType: reply.mediaType })
142
+ })
143
+
144
+ const response = await fastify.inject({
145
+ method: 'GET',
146
+ url: '/'
147
+ })
148
+ const body = await response.json()
149
+ t.assert.strictEqual(body.mediaType, 'application/json')
150
+ })
151
+
152
+ test('reply.mediaType should match case-insensitive content-type set via raw.setHeader', async (t) => {
153
+ t.plan(2)
154
+
155
+ const fastify = Fastify()
156
+
157
+ fastify.get('/', (request, reply) => {
158
+ reply.raw.setHeader('Content-Type', 'text/html; charset=utf-8')
159
+ t.assert.strictEqual(reply.mediaType, 'text/html')
160
+ reply.send(reply.mediaType)
161
+ })
162
+
163
+ const response = await fastify.inject({
164
+ method: 'GET',
165
+ url: '/'
166
+ })
167
+ t.assert.strictEqual(response.body, 'text/html')
168
+ })
169
+
170
+ test('reply.mediaType should prioritize reply.type over raw.setHeader', async (t) => {
171
+ t.plan(2)
172
+
173
+ const fastify = Fastify()
174
+
175
+ fastify.get('/', (request, reply) => {
176
+ reply.raw.setHeader('content-type', 'application/xml')
177
+ reply.type('application/json')
178
+ t.assert.strictEqual(reply.mediaType, 'application/json')
179
+ reply.send({ mediaType: reply.mediaType })
180
+ })
181
+
182
+ const response = await fastify.inject({
183
+ method: 'GET',
184
+ url: '/'
185
+ })
186
+ const body = await response.json()
187
+ t.assert.strictEqual(body.mediaType, 'application/json')
188
+ })
@@ -99,12 +99,9 @@ describe('RFC 10008', () => {
99
99
  t.assert.equal(res.json().code, 'FST_ERR_CTP_INVALID_JSON_BODY')
100
100
  })
101
101
 
102
- test('should return 400 if no content is provided for QUERY method', async (t) => {
103
- // If a media type is specified but is inconsistent
104
- // with the actual request content, a 400 (Bad Request)
105
- // can be returned. That is, a server is not allowed to
106
- // infer a media type from the request content and then
107
- // override a missing or "erroneous" value (i.e., "content sniffing").
102
+ test('should return 200 if body is empty but media type allows it for QUERY method', async (t) => {
103
+ // If a media type is set and allows the body to be empty,
104
+ // the server must allow it.
108
105
  t.plan(2)
109
106
 
110
107
  const fastify = Fastify()
@@ -116,11 +113,11 @@ describe('RFC 10008', () => {
116
113
  const res = await fastify.inject({
117
114
  method: 'QUERY',
118
115
  url: '/query',
119
- headers: { 'Content-Type': 'application/json' }
116
+ headers: { 'Content-Type': 'text/plain' }
120
117
  })
121
118
 
122
- t.assert.equal(res.statusCode, 400)
123
- t.assert.equal(res.json().code, 'FST_ERR_ROUTE_MISSING_CONTENT')
119
+ t.assert.equal(res.statusCode, 200)
120
+ t.assert.equal(res.body, '')
124
121
  })
125
122
 
126
123
  test('should return 415 if unsupported media type is provided for QUERY method', async (t) => {
@@ -459,7 +459,7 @@ test('Should emit a warning for every route with undefined schema', (t, testDone
459
459
  t.assert.ifError(error)
460
460
  t.assert.strictEqual(res.statusCode, 200)
461
461
  // fastify.inject run in series
462
- // last callback recieve all warnings at once
462
+ // last callback receive all warnings at once
463
463
  // GET /undefinedParams/123
464
464
  t.assert.deepStrictEqual(spyData.calls[0], { arguments: ['params', 'GET', '/undefinedParams/:id'], result: true })
465
465
  t.assert.deepStrictEqual(spyData.calls[1], { arguments: ['params', 'HEAD', '/undefinedParams/:id'], result: true })
@@ -8,7 +8,7 @@ const Schema = require('fluent-json-schema')
8
8
  const { waitForCb } = require('./helper')
9
9
  const { kRequestContentType } = require('../lib/symbols')
10
10
  const { spyWarning } = require('process-warning')
11
- const { FSTSEC002 } = require('../lib/warnings')
11
+ const { FSTSEC002, FSTSEC003 } = require('../lib/warnings')
12
12
 
13
13
  const customSchemaCompilers = {
14
14
  body: new AJV({
@@ -1921,6 +1921,52 @@ test('inline and local $ref header schemas do not emit FSTSEC002', async t => {
1921
1921
  t.assert.strictEqual(spyData.callCount(), 0)
1922
1922
  })
1923
1923
 
1924
+ test('header schema with a $ref inside propertyNames emits FSTSEC003 (case-normalization does not reach it)', async t => {
1925
+ const spyData = spyWarning(FSTSEC003)
1926
+ t.after(spyData.restore)
1927
+
1928
+ const fastify = Fastify()
1929
+ fastify.post('/', {
1930
+ schema: {
1931
+ headers: {
1932
+ type: 'object',
1933
+ propertyNames: { $ref: '#/definitions/denied' },
1934
+ definitions: {
1935
+ denied: { not: { enum: ['X-Debug-Override'] } }
1936
+ }
1937
+ }
1938
+ }
1939
+ }, async () => ({ ok: true }))
1940
+
1941
+ await fastify.ready()
1942
+
1943
+ t.assert.strictEqual(spyData.callCount(), 1)
1944
+ t.assert.deepStrictEqual(spyData.calls[0].arguments, ['POST', '/', '#/definitions/denied'])
1945
+ })
1946
+
1947
+ test('header schema detects a $ref inside propertyNames nested in an array', async t => {
1948
+ const spyData = spyWarning(FSTSEC003)
1949
+ t.after(spyData.restore)
1950
+
1951
+ const fastify = Fastify()
1952
+ fastify.post('/', {
1953
+ schema: {
1954
+ headers: {
1955
+ type: 'object',
1956
+ propertyNames: { not: { anyOf: [{ $ref: '#/definitions/denied' }] } },
1957
+ definitions: {
1958
+ denied: { enum: ['X-Debug-Override'] }
1959
+ }
1960
+ }
1961
+ }
1962
+ }, async () => ({ ok: true }))
1963
+
1964
+ await fastify.ready()
1965
+
1966
+ t.assert.strictEqual(spyData.callCount(), 1)
1967
+ t.assert.deepStrictEqual(spyData.calls[0].arguments, ['POST', '/', '#/definitions/denied'])
1968
+ })
1969
+
1924
1970
  test('header schema normalization traverses dependent schema keywords', async t => {
1925
1971
  const fastify = Fastify({
1926
1972
  ajv: { customOptions: { strictSchema: false } }
@@ -1993,3 +2039,207 @@ test('header schema detects an external $ref nested in an array', async t => {
1993
2039
  'http://example.com/nested-admin-headers#'
1994
2040
  ])
1995
2041
  })
2042
+
2043
+ test('header schema propertyNames: not-enum deny-list is lowercased', async t => {
2044
+ const fastify = Fastify()
2045
+
2046
+ // A `propertyNames` subschema validates each header name itself, so strings in
2047
+ // its `enum` are header names. They must be lowercased the same way as
2048
+ // `required` entries, otherwise a deny-list written in the usual header case
2049
+ // never matches the lowercased request object.
2050
+ fastify.get('/', {
2051
+ schema: {
2052
+ headers: {
2053
+ type: 'object',
2054
+ propertyNames: { not: { enum: ['X-Debug-Override'] } }
2055
+ }
2056
+ }
2057
+ }, async () => ({ ok: true }))
2058
+
2059
+ await fastify.ready()
2060
+
2061
+ const blocked = await fastify.inject({
2062
+ method: 'GET',
2063
+ url: '/',
2064
+ headers: { 'X-Debug-Override': '1' }
2065
+ })
2066
+ t.assert.strictEqual(blocked.statusCode, 400)
2067
+ t.assert.strictEqual(blocked.json().code, 'FST_ERR_VALIDATION')
2068
+
2069
+ const ok = await fastify.inject({
2070
+ method: 'GET',
2071
+ url: '/',
2072
+ headers: { 'X-Other': '1' }
2073
+ })
2074
+ t.assert.strictEqual(ok.statusCode, 200)
2075
+ })
2076
+
2077
+ test('header schema propertyNames: non-string enum items are left untouched', async t => {
2078
+ const fastify = Fastify({
2079
+ ajv: { customOptions: { strictSchema: false } }
2080
+ })
2081
+
2082
+ fastify.get('/', {
2083
+ schema: {
2084
+ headers: {
2085
+ type: 'object',
2086
+ propertyNames: { not: { enum: ['X-Debug-Override', 42, null] } }
2087
+ }
2088
+ }
2089
+ }, async () => ({ ok: true }))
2090
+
2091
+ await fastify.ready()
2092
+
2093
+ const blocked = await fastify.inject({
2094
+ method: 'GET',
2095
+ url: '/',
2096
+ headers: { 'X-Debug-Override': '1' }
2097
+ })
2098
+ t.assert.strictEqual(blocked.statusCode, 400)
2099
+
2100
+ const ok = await fastify.inject({
2101
+ method: 'GET',
2102
+ url: '/',
2103
+ headers: { 'X-Other': '1' }
2104
+ })
2105
+ t.assert.strictEqual(ok.statusCode, 200)
2106
+ })
2107
+
2108
+ test('header schema propertyNames: not-const deny-list is lowercased', async t => {
2109
+ const fastify = Fastify()
2110
+
2111
+ fastify.get('/', {
2112
+ schema: {
2113
+ headers: {
2114
+ type: 'object',
2115
+ propertyNames: { not: { const: 'X-Debug-Override' } }
2116
+ }
2117
+ }
2118
+ }, async () => ({ ok: true }))
2119
+
2120
+ await fastify.ready()
2121
+
2122
+ const blocked = await fastify.inject({
2123
+ method: 'GET',
2124
+ url: '/',
2125
+ headers: { 'X-Debug-Override': '1' }
2126
+ })
2127
+ t.assert.strictEqual(blocked.statusCode, 400)
2128
+ t.assert.strictEqual(blocked.json().code, 'FST_ERR_VALIDATION')
2129
+ })
2130
+
2131
+ test('header schema propertyNames: enum/const are lowercased inside combinators', async t => {
2132
+ const fastify = Fastify()
2133
+
2134
+ // The header-name `enum`/`const` can be nested under combinators inside
2135
+ // `propertyNames`; the lowercasing context must propagate through them.
2136
+ fastify.get('/anyOf', {
2137
+ schema: {
2138
+ headers: {
2139
+ type: 'object',
2140
+ propertyNames: { not: { anyOf: [{ enum: ['X-Debug-Override'] }] } }
2141
+ }
2142
+ }
2143
+ }, async () => ({ ok: true }))
2144
+
2145
+ fastify.get('/oneOf', {
2146
+ schema: {
2147
+ headers: {
2148
+ type: 'object',
2149
+ propertyNames: { not: { oneOf: [{ const: 'X-Debug-Override' }] } }
2150
+ }
2151
+ }
2152
+ }, async () => ({ ok: true }))
2153
+
2154
+ await fastify.ready()
2155
+
2156
+ for (const url of ['/anyOf', '/oneOf']) {
2157
+ const blocked = await fastify.inject({
2158
+ method: 'GET',
2159
+ url,
2160
+ headers: { 'X-Debug-Override': '1' }
2161
+ })
2162
+ t.assert.strictEqual(blocked.statusCode, 400, `${url} should block the header`)
2163
+ t.assert.strictEqual(blocked.json().code, 'FST_ERR_VALIDATION')
2164
+ }
2165
+ })
2166
+
2167
+ test('header schema propertyNames: lowercase-equivalent schema behaves identically', async t => {
2168
+ const fastify = Fastify()
2169
+
2170
+ fastify.get('/', {
2171
+ schema: {
2172
+ headers: {
2173
+ type: 'object',
2174
+ propertyNames: { not: { enum: ['x-debug-override'] } }
2175
+ }
2176
+ }
2177
+ }, async () => ({ ok: true }))
2178
+
2179
+ await fastify.ready()
2180
+
2181
+ const blocked = await fastify.inject({
2182
+ method: 'GET',
2183
+ url: '/',
2184
+ headers: { 'X-Debug-Override': '1' }
2185
+ })
2186
+ t.assert.strictEqual(blocked.statusCode, 400)
2187
+ t.assert.strictEqual(blocked.json().code, 'FST_ERR_VALIDATION')
2188
+ })
2189
+
2190
+ test('header schema propertyNames: enum/const values in header value schemas are not lowercased', async t => {
2191
+ const fastify = Fastify()
2192
+
2193
+ // `enum`/`const` inside a property value subschema hold header *values*, not
2194
+ // names, and must be preserved with their original case.
2195
+ fastify.get('/enum', {
2196
+ schema: {
2197
+ headers: {
2198
+ type: 'object',
2199
+ properties: { 'X-Mode': { type: 'string', enum: ['MIXED', 'lowercase'] } }
2200
+ }
2201
+ }
2202
+ }, async () => ({ ok: true }))
2203
+
2204
+ fastify.get('/const', {
2205
+ schema: {
2206
+ headers: {
2207
+ type: 'object',
2208
+ properties: { 'X-Mode': { type: 'string', const: 'MIXED' } }
2209
+ }
2210
+ }
2211
+ }, async () => ({ ok: true }))
2212
+
2213
+ await fastify.ready()
2214
+
2215
+ const enumMixed = await fastify.inject({
2216
+ method: 'GET',
2217
+ url: '/enum',
2218
+ headers: { 'X-Mode': 'MIXED' }
2219
+ })
2220
+ t.assert.strictEqual(enumMixed.statusCode, 200)
2221
+
2222
+ const constMixed = await fastify.inject({
2223
+ method: 'GET',
2224
+ url: '/const',
2225
+ headers: { 'X-Mode': 'MIXED' }
2226
+ })
2227
+ t.assert.strictEqual(constMixed.statusCode, 200)
2228
+ })
2229
+
2230
+ test('header schema propertyNames lowercasing does not mutate the input schema', async t => {
2231
+ const headers = {
2232
+ type: 'object',
2233
+ propertyNames: { not: { enum: ['X-Debug-Override'], const: 'X-Other' } }
2234
+ }
2235
+ const schema = { headers }
2236
+ const schemaBefore = JSON.stringify(schema)
2237
+ const headersBefore = JSON.stringify(headers)
2238
+
2239
+ const fastify = Fastify()
2240
+ fastify.get('/', { schema }, async () => ({ ok: true }))
2241
+ await fastify.ready()
2242
+
2243
+ t.assert.strictEqual(JSON.stringify(schema), schemaBefore)
2244
+ t.assert.strictEqual(JSON.stringify(headers), headersBefore)
2245
+ })
@@ -79,7 +79,6 @@ expect(errorCodes.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED).type.toBeA
79
79
  expect(errorCodes.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT).type.toBeAssignableTo<FastifyErrorConstructor>()
80
80
  expect(errorCodes.FST_ERR_ROUTE_REWRITE_NOT_STR).type.toBeAssignableTo<FastifyErrorConstructor>()
81
81
  expect(errorCodes.FST_ERR_ROUTE_MISSING_CONTENT_TYPE).type.toBeAssignableTo<FastifyErrorConstructor>()
82
- expect(errorCodes.FST_ERR_ROUTE_MISSING_CONTENT).type.toBeAssignableTo<FastifyErrorConstructor>()
83
82
  expect(errorCodes.FST_ERR_REOPENED_CLOSE_SERVER).type.toBeAssignableTo<FastifyErrorConstructor>()
84
83
  expect(errorCodes.FST_ERR_REOPENED_SERVER).type.toBeAssignableTo<FastifyErrorConstructor>()
85
84
  expect(errorCodes.FST_ERR_INSTANCE_ALREADY_STARTED).type.toBeAssignableTo<FastifyErrorConstructor>()
@@ -3,6 +3,7 @@ import * as http from 'node:http'
3
3
  import * as http2 from 'node:http2'
4
4
  import * as https from 'node:https'
5
5
  import { Socket } from 'node:net'
6
+ import pino from 'pino'
6
7
  import { expect } from 'tstyche'
7
8
  import fastify, {
8
9
  ConnectionError,
@@ -180,6 +181,10 @@ const customLogger = {
180
181
  debug: () => { },
181
182
  child: () => customLogger
182
183
  }
184
+ const pinoLogger = pino()
185
+ const pinoLoggerServer: FastifyInstance = fastify({ loggerInstance: pinoLogger })
186
+
187
+ expect(pinoLoggerServer).type.toBeAssignableTo<FastifyInstance>()
183
188
  expect(fastify({ logger: customLogger })).type.toBeAssignableTo<
184
189
  FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse, FastifyBaseLogger>
185
190
  >()
@@ -58,6 +58,15 @@ const asyncPreHandler = async (request: FastifyRequest) => {
58
58
 
59
59
  fastify().get('/', { preHandler: asyncPreHandler }, async () => 'this is an example')
60
60
 
61
+ fastify().route({
62
+ method: 'GET',
63
+ url: '/',
64
+ onRequest: async function () {
65
+ expect(this).type.toBe<FastifyInstance>()
66
+ },
67
+ handler: async () => 'ok'
68
+ })
69
+
61
70
  fastify().get(
62
71
  '/',
63
72
  { config: { foo: 'bar', bar: 100, includeMessage: true } },
@@ -495,6 +504,12 @@ expect(fastify().hasRoute({
495
504
  method: 'GET'
496
505
  })).type.toBe<boolean>()
497
506
 
507
+ fastify().hasRoute({
508
+ url: '/',
509
+ // @ts-expect-error Type 'string[]' is not assignable to type 'HTTPMethods'.
510
+ method: ['GET', 'POST']
511
+ })
512
+
498
513
  expect(fastify().hasRoute({
499
514
  url: '/',
500
515
  method: 'GET',
@@ -539,6 +554,12 @@ expect(
539
554
  })
540
555
  ).type.toBe<Omit<FindMyWayFindResult<RawServerDefault>, 'store'>>()
541
556
 
557
+ fastify().findRoute({
558
+ url: '/',
559
+ // @ts-expect-error Type 'string[]' is not assignable to type 'HTTPMethods'.
560
+ method: ['GET', 'POST']
561
+ })
562
+
542
563
  // we should not expose store
543
564
  expect(fastify().findRoute({
544
565
  url: '/',
package/types/errors.d.ts CHANGED
@@ -83,7 +83,6 @@ export type FastifyErrorCodes = Record<
83
83
  'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' |
84
84
  'FST_ERR_ROUTE_REWRITE_NOT_STR' |
85
85
  'FST_ERR_ROUTE_MISSING_CONTENT_TYPE' |
86
- 'FST_ERR_ROUTE_MISSING_CONTENT' |
87
86
  'FST_ERR_REOPENED_CLOSE_SERVER' |
88
87
  'FST_ERR_REOPENED_SERVER' |
89
88
  'FST_ERR_INSTANCE_ALREADY_STARTED' |
@@ -215,13 +215,13 @@ export interface FastifyInstance<
215
215
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
216
216
  ContextConfig = ContextConfigDefault,
217
217
  SchemaCompiler extends FastifySchema = FastifySchema
218
- >(opts: Pick<RouteOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>, 'method' | 'url' | 'constraints'>): boolean;
218
+ >(opts: Pick<RouteOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>, 'url' | 'constraints'> & { method: HTTPMethods }): boolean;
219
219
 
220
220
  findRoute<
221
221
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
222
222
  ContextConfig = ContextConfigDefault,
223
223
  SchemaCompiler extends FastifySchema = FastifySchema
224
- >(opts: Pick<RouteOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>, 'method' | 'url' | 'constraints'>): Omit<FindMyWayFindResult<RawServer>, 'store'>;
224
+ >(opts: Pick<RouteOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>, 'url' | 'constraints'> & { method: HTTPMethods }): Omit<FindMyWayFindResult<RawServer>, 'store'>;
225
225
 
226
226
  // addHook: overloads
227
227
 
@@ -579,7 +579,7 @@ export interface FastifyInstance<
579
579
  * which allows for modifying or adding child logger bindings and logger options, or
580
580
  * returning a completely custom child logger implementation.
581
581
  */
582
- childLoggerFactory: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
582
+ childLoggerFactory: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, FastifyBaseLogger, TypeProvider>;
583
583
 
584
584
  /**
585
585
  * Hook function that is called when creating a child logger instance for each request
@@ -598,7 +598,7 @@ export interface FastifyInstance<
598
598
  * }
599
599
  * ```
600
600
  */
601
- setChildLoggerFactory(factory: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, Logger,
601
+ setChildLoggerFactory(factory: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, FastifyBaseLogger,
602
602
  TypeProvider>): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
603
603
 
604
604
  /**
package/types/route.d.ts CHANGED
@@ -46,7 +46,8 @@ export interface RouteConstraint {
46
46
  /**
47
47
  * Route shorthand options for the various shorthand methods
48
48
  */
49
- type RouteShorthandHook<T extends (...args: any) => any> = (...args: Parameters<T>) => void | Promise<unknown>
49
+ type RouteShorthandHook<T extends (this: any, ...args: any) => any> =
50
+ (this: ThisParameterType<T>, ...args: Parameters<T>) => void | Promise<unknown>
50
51
 
51
52
  export interface RouteShorthandOptions<
52
53
  RawServer extends RawServerBase = RawServerDefault,
@@ -78,7 +79,7 @@ export interface RouteShorthandOptions<
78
79
  reply: FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, NoInfer<SchemaCompiler>,
79
80
  TypeProvider>
80
81
  ) => void;
81
- childLoggerFactory?: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
82
+ childLoggerFactory?: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, FastifyBaseLogger, TypeProvider>;
82
83
  schemaErrorFormatter?: SchemaErrorFormatter;
83
84
 
84
85
  // hooks