fastify 6.0.0-alpha.1 → 6.0.0-alpha.2

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/lib/route.js CHANGED
@@ -289,6 +289,7 @@ function buildRouting (options) {
289
289
  opts.attachValidation = false
290
290
  }
291
291
 
292
+ const routeConfigUrl = prefixing ? prefix : url
292
293
  if (prefixing === false) {
293
294
  // run 'onRoute' hooks
294
295
  for (const hook of this[kHooks].onRoute) {
@@ -327,7 +328,7 @@ function buildRouting (options) {
327
328
  const constraints = opts.constraints || {}
328
329
  const config = {
329
330
  ...opts.config,
330
- url,
331
+ url: routeConfigUrl,
331
332
  method: opts.method
332
333
  }
333
334
 
package/lib/validation.js CHANGED
@@ -120,7 +120,16 @@ function validateParam (validatorFunction, request, paramName) {
120
120
  let ret
121
121
 
122
122
  try {
123
- ret = validatorFunction?.(isUndefined ? null : request[paramName])
123
+ const data = isUndefined ? null : request[paramName]
124
+ // Ajv can only mutate a root value when its parent is provided.
125
+ if (validatorFunction?.schemaEnv) {
126
+ ret = validatorFunction(data, {
127
+ parentData: request,
128
+ parentDataProperty: paramName
129
+ })
130
+ } else {
131
+ ret = validatorFunction?.(data)
132
+ }
124
133
  } catch (err) {
125
134
  // If validator throws synchronously, ensure it propagates as an internal error
126
135
  err.statusCode = 500
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "6.0.0-alpha.1",
3
+ "version": "6.0.0-alpha.2",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -665,6 +665,40 @@ test('preClose async', async t => {
665
665
  await fastify.close()
666
666
  })
667
667
 
668
+ test('preClose runs exactly once with a child plugin', async t => {
669
+ t.plan(1)
670
+ const fastify = Fastify()
671
+ let count = 0
672
+
673
+ fastify.register(async (child) => {
674
+ child.get('/x', async () => 'ok')
675
+ })
676
+ fastify.addHook('preClose', async () => { count++ })
677
+
678
+ await fastify.ready()
679
+ await fastify.close()
680
+
681
+ t.assert.strictEqual(count, 1)
682
+ })
683
+
684
+ test('preClose runs exactly once with nested child plugins', async t => {
685
+ t.plan(1)
686
+ const fastify = Fastify()
687
+ let count = 0
688
+
689
+ fastify.register(async (child) => {
690
+ child.register(async (grandchild) => {
691
+ grandchild.get('/y', async () => 'ok')
692
+ })
693
+ })
694
+ fastify.addHook('preClose', async () => { count++ })
695
+
696
+ await fastify.ready()
697
+ await fastify.close()
698
+
699
+ t.assert.strictEqual(count, 1)
700
+ })
701
+
668
702
  test('preClose execution order', (t, done) => {
669
703
  t.plan(4)
670
704
  const fastify = Fastify()
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('root primitive values coerced by Ajv are assigned to the request', async (t) => {
7
+ const fastify = Fastify()
8
+
9
+ fastify.post('/', {
10
+ schema: {
11
+ body: {
12
+ type: 'integer',
13
+ minimum: 1,
14
+ maximum: 10
15
+ }
16
+ }
17
+ }, (request, reply) => {
18
+ reply.send({ body: request.body, type: typeof request.body })
19
+ })
20
+
21
+ const response = await fastify.inject({
22
+ method: 'POST',
23
+ url: '/',
24
+ payload: '"10"',
25
+ headers: { 'content-type': 'application/json' }
26
+ })
27
+
28
+ t.assert.strictEqual(response.statusCode, 200)
29
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
30
+ body: 10,
31
+ type: 'number'
32
+ })
33
+
34
+ await fastify.close()
35
+ })
@@ -49,7 +49,7 @@ test('Once called, Reply should return an object with methods', t => {
49
49
  t.assert.strictEqual(typeof reply[kReplyErrorHandlerCalled], 'boolean')
50
50
  t.assert.strictEqual(typeof reply.send, 'function')
51
51
  t.assert.strictEqual(typeof reply.code, 'function')
52
- t.assert.strictEqual(typeof reply.mediaType, 'string')
52
+ t.assert.strictEqual(typeof reply.mediaType, 'undefined')
53
53
  t.assert.strictEqual(typeof reply.status, 'function')
54
54
  t.assert.strictEqual(typeof reply.header, 'function')
55
55
  t.assert.strictEqual(typeof reply.serialize, 'function')
@@ -569,6 +569,38 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "
569
569
  completion.patience.then(testDone)
570
570
  })
571
571
 
572
+ test('reports the canonical route url for hidden prefix trailing slash route', async t => {
573
+ t.plan(4)
574
+
575
+ const fastify = Fastify({
576
+ ignoreTrailingSlash: false,
577
+ exposeHeadRoutes: false
578
+ })
579
+
580
+ const onRouteUrls = []
581
+
582
+ fastify.addHook('onRoute', routeOptions => {
583
+ onRouteUrls.push(routeOptions.url)
584
+ })
585
+
586
+ fastify.register(async instance => {
587
+ instance.get('/', async request => {
588
+ return request.routeOptions.url
589
+ })
590
+
591
+ instance.get('/bar/', async request => {
592
+ return request.routeOptions.url
593
+ })
594
+ }, { prefix: '/prefix' })
595
+
596
+ t.assert.strictEqual((await fastify.inject('/prefix')).payload, '/prefix')
597
+ t.assert.strictEqual((await fastify.inject('/prefix/')).payload, '/prefix')
598
+ t.assert.strictEqual((await fastify.inject('/prefix/bar/')).payload, '/prefix/bar/')
599
+ t.assert.deepStrictEqual(onRouteUrls, ['/prefix', '/prefix/bar/'])
600
+
601
+ await fastify.close()
602
+ })
603
+
572
604
  test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', (t, testDone) => {
573
605
  t.plan(4)
574
606
  const fastify = Fastify({
@@ -106,20 +106,33 @@ test('trust proxy function', async t => {
106
106
  await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined, 'fastify.test:1234')
107
107
  })
108
108
 
109
- test('trust proxy number', async t => {
110
- t.plan(8)
109
+ test('trust proxy number ignores forwarded headers', async t => {
110
+ t.plan(5)
111
111
  const app = fastify({
112
112
  trustProxy: 1
113
113
  })
114
114
  t.after(() => app.close())
115
115
 
116
116
  app.get('/trustproxynumber', function (req, reply) {
117
- testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
117
+ t.assert.strictEqual(req.ip, '203.0.113.7', 'ip falls back to socket remote address')
118
+ t.assert.deepStrictEqual(req.ips, ['203.0.113.7'], 'ips falls back to socket remote address')
119
+ t.assert.strictEqual(req.host, 'app.example.com', 'host ignores x-forwarded-host')
120
+ t.assert.strictEqual(req.hostname, 'app.example.com', 'hostname ignores x-forwarded-host')
121
+ t.assert.strictEqual(req.protocol, 'http', 'protocol ignores x-forwarded-proto')
118
122
  reply.code(200).send({ ip: req.ip, host: req.host })
119
123
  })
120
124
 
121
- const fastifyServer = await app.listen({ port: 0 })
122
- await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, 'fastify.test:1234')
125
+ await app.inject({
126
+ method: 'GET',
127
+ url: '/trustproxynumber',
128
+ remoteAddress: '203.0.113.7',
129
+ headers: {
130
+ host: 'app.example.com',
131
+ 'x-forwarded-for': '9.9.9.9, 8.8.8.8',
132
+ 'x-forwarded-host': 'evil.com',
133
+ 'x-forwarded-proto': 'https'
134
+ }
135
+ })
123
136
  })
124
137
 
125
138
  test('trust proxy IP addresses', async t => {
@@ -241,18 +254,15 @@ test('trust proxy reads forwarded headers from trusted connections', async t =>
241
254
  })
242
255
  })
243
256
 
244
- test('trust proxy with number and undefined socket remoteAddress', t => {
257
+ test('trust proxy with number and undefined socket remoteAddress ignores forwarded headers', t => {
245
258
  t.plan(3)
246
259
 
247
- // Test case for issue #6606: trustProxy: 1 with undefined/null socket.remoteAddress
248
- // This simulates IISNode on Windows where socket.remoteAddress may be undefined
249
260
  const headers = {
261
+ host: 'real.test',
250
262
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
251
263
  'x-forwarded-host': 'fastify.test',
252
264
  'x-forwarded-proto': 'https'
253
265
  }
254
- // socket must exist but remoteAddress can be undefined
255
- // This is what happens in IISNode with enableXFF="true"
256
266
  const req = {
257
267
  method: 'GET',
258
268
  url: '/',
@@ -262,20 +272,16 @@ test('trust proxy with number and undefined socket remoteAddress', t => {
262
272
 
263
273
  const TpRequest = buildRequest(Request, 1)
264
274
  const request = new TpRequest('id', 'params', req, 'query', 'log')
265
- // Even with undefined socket.remoteAddress, req.ip should be populated from X-Forwarded-For
266
- t.assert.ok(request.ip, 'ip is defined')
267
- // With trustProxy: 1, we trust 1 hop from socket. Since socket.remoteAddress is undefined,
268
- // the hop count check should skip it and we get 1.1.1.1 (the first trusted address from X-Forwarded-For)
269
- t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for')
270
- // The host should also work correctly
271
- t.assert.strictEqual(request.host, 'fastify.test', 'gets host from x-forwarded-host')
275
+ t.assert.strictEqual(request.ip, undefined, 'ip falls back to undefined socket remote address')
276
+ t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
277
+ t.assert.strictEqual(request.protocol, 'http', 'protocol ignores x-forwarded-proto')
272
278
  })
273
279
 
274
- test('trust proxy with number and null socket remoteAddress', t => {
280
+ test('trust proxy with number and null socket remoteAddress ignores forwarded headers', t => {
275
281
  t.plan(2)
276
282
 
277
- // Test case for trustProxy: 1 with null socket.remoteAddress
278
283
  const headers = {
284
+ host: 'real.test',
279
285
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
280
286
  'x-forwarded-host': 'fastify.test'
281
287
  }
@@ -288,8 +294,8 @@ test('trust proxy with number and null socket remoteAddress', t => {
288
294
 
289
295
  const TpRequest = buildRequest(Request, 1)
290
296
  const request = new TpRequest('id', 'params', req, 'query', 'log')
291
- t.assert.ok(request.ip, 'ip is defined')
292
- t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for')
297
+ t.assert.strictEqual(request.ip, null, 'ip falls back to null socket remote address')
298
+ t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
293
299
  })
294
300
 
295
301
  test('trust proxy does not trust x-forwarded-host/proto when socket is null', t => {
@@ -194,6 +194,8 @@ expect(fastify({
194
194
  }
195
195
  })).type.toBeAssignableTo<FastifyInstance>()
196
196
  expect(fastify({ trustProxy: true })).type.toBeAssignableTo<FastifyInstance>()
197
+ // @ts-expect-error No overload matches this call.
198
+ fastify({ trustProxy: 1 })
197
199
  expect(fastify({ routerOptions: { querystringParser: () => ({ foo: 'bar' }) } })).type.toBeAssignableTo<FastifyInstance>()
198
200
  expect(fastify({ routerOptions: { querystringParser: () => ({ foo: { bar: 'fuzz' } }) } })).type.toBeAssignableTo<FastifyInstance>()
199
201
  expect(fastify({ routerOptions: { querystringParser: () => ({ foo: ['bar', 'fuzz'] }) } })).type.toBeAssignableTo<FastifyInstance>()