fix(nitro-server): read router params from the path h3 matched · nuxt/nuxt@010f6b4 · GitHub
Skip to content

Commit 010f6b4

Browse files
committed
fix(nitro-server): read router params from the path h3 matched
1 parent f6cfee5 commit 010f6b4

3 files changed

Lines changed: 108 additions & 17 deletions

File tree

‎packages/nitro-server/src/runtime/server.ts‎

Lines changed: 39 additions & 16 deletions

‎packages/nitro-server/test/server.test.ts‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { rollup } from 'rollup'
66
import { build } from 'tsdown'
77
import tsdownConfig from '../tsdown.config.ts'
88
import { resolveModuleExportNames } from '@nuxt/kit/internal'
9-
import { createEvent } from 'h3'
9+
import fc from 'fast-check'
10+
import { createApp, createEvent, createRouter, eventHandler, toPlainHandler } from 'h3'
1011
import type { H3Event } from 'h3'
1112
import * as shipped from '../../nuxt/src/server/index.ts'
1213
import type { RequestEvent } from '../../nuxt/src/server/index.ts'
@@ -133,6 +134,70 @@ describe('the shape of what it reads off an h3 v1 event', () => {
133134
expect(delegate.getRouterParams(e)).toEqual({ id: 'a b' })
134135
})
135136

137+
describe.each(['/', '/base/'])('router params routed by h3 under %s', (base) => {
138+
const router = createRouter({ preemptive: true })
139+
for (const route of ['/api/auth/**:slug', '/api/users/:id', '/api/users/:id/**:rest', '/api/items/*/x']) {
140+
router.use(route, eventHandler(event => ({
141+
matched: event.context.params,
142+
params: delegate.getRouterParams(event),
143+
decoded: delegate.getRouterParams(event, { decode: true }),
144+
})))
145+
}
146+
const app = createApp()
147+
app.use(base, router.handler)
148+
const handle = toPlainHandler(app)
149+
150+
async function request (path: string) {
151+
const response = await handle({ method: 'GET', path: base.slice(0, -1) + path, headers: {} })
152+
expect(response.status).toBe(200)
153+
return JSON.parse(response.body as string) as Record<'matched' | 'params' | 'decoded', Record<string, string>>
154+
}
155+
156+
it.for([
157+
['/api/auth/private%3Fignored/public', { slug: 'private' }],
158+
['/api/auth/private%3fignored/public', { slug: 'private' }],
159+
['/api/auth/a/b%3F/c', { slug: 'a/b' }],
160+
['/api/auth/a/%3Fb/c', { slug: 'a' }],
161+
['/api/auth/a%3Fb?c=%3F/d', { slug: 'a' }],
162+
['/api/auth/a%253Fb/c', { slug: 'a%253Fb/c' }],
163+
['/api/auth/a%23b/c', { slug: 'a%23b/c' }],
164+
['/api/auth/a%2Fb/c', { slug: 'a%2Fb/c' }],
165+
['/api/auth/a%2F%3Fb/c', { slug: 'a%2F' }],
166+
['/api/auth/caf%C3%A9/', { slug: 'caf%C3%A9' }],
167+
['/api/auth/a//', { slug: 'a/' }],
168+
['/api/auth/a%20b?x=1', { slug: 'a%20b' }],
169+
['/api/users/a%3Fb', { id: 'a' }],
170+
['/api/users/a%20b/', { id: 'a%20b' }],
171+
['/api/users/a%20b/c%3F/d', { id: 'a%20b', rest: 'c' }],
172+
['/api/users/a%3F/b/c', { id: 'a' }],
173+
['/api/items/a%2Fb/x', { _0: 'a%2Fb' }],
174+
['/api/items/a%23b/x?y', { _0: 'a%23b' }],
175+
] as const)('reads %s as h3 matched it', async ([path, params]) => {
176+
const { matched, params: encoded, decoded } = await request(path)
177+
expect(encoded).toEqual(params)
178+
for (const key in matched) {
179+
expect(decoded[key]).toBe(shipped.getRouterParam({ context: { params: matched } }, key, { decode: true }))
180+
}
181+
})
182+
183+
it('reads the same params h3 matched for any path', async () => {
184+
const segment = fc.array(fc.constantFrom('a', 'é', '%20', '%3F', '%3f', '%23', '%2F', '%25', '%253F', '%2525', '%C3%A9', '?', '#', ''), { maxLength: 4 }).map(parts => parts.join(''))
185+
const prefix = fc.constantFrom('/api/auth/', '/api/users/', '/api/items/')
186+
await fc.assert(fc.asyncProperty(prefix, fc.array(segment, { minLength: 1, maxLength: 4 }), async (prefix, segments) => {
187+
const path = prefix + segments.join('/')
188+
const response = await handle({ method: 'GET', path: base.slice(0, -1) + path, headers: {} })
189+
if (response.status !== 200) {
190+
return
191+
}
192+
const { matched, params, decoded } = JSON.parse(response.body as string)
193+
expect(decoded).toEqual(shipped.getRouterParams({ context: { params: matched } }, { decode: true }))
194+
for (const value of Object.values(params)) {
195+
expect(path).toContain(value)
196+
}
197+
}))
198+
})
199+
})
200+
136201
it('reads the client IP from the socket, and the first forwarded hop only when opted in', () => {
137202
const e = event('/', { headers: { 'x-forwarded-for': '203.0.113.1, 10.0.0.1' }, ip: '198.51.100.7' })
138203
expect(delegate.getRequestIP(e)).toBe('198.51.100.7')

‎test/basic.test.ts‎

Lines changed: 3 additions & 0 deletions

0 commit comments

Comments
 (0)