Skip to content

Commit 7a68b87

Browse files
committed
feat: force array type raw param parsers
1 parent eadec55 commit 7a68b87

2 files changed

Lines changed: 112 additions & 5 deletions

File tree

packages/router/src/unplugin/codegen/generateRouteResolver.spec.ts

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { ImportsMap } from '../core/utils'
1010
import type { ParamParsersMap } from './generateParamParsers'
1111
import { generateAliasWarnings } from './generateAliasWarnings'
12+
import { mockWarn } from '../../tests/vitest-mock-warn'
1213

1314
const DEFAULT_OPTIONS = resolveOptions({})
1415
let DEFAULT_STATE: Parameters<typeof generateRouteRecord>[0]['state'] = {
@@ -231,6 +232,100 @@ describe('generateRouteRecordQuery', () => {
231232
],"
232233
`)
233234
})
235+
236+
describe('raw param parsers', () => {
237+
mockWarn()
238+
239+
function rawParsersMap(name: string): ParamParsersMap {
240+
return new Map([
241+
[
242+
name,
243+
{
244+
name,
245+
typeName: `Param_${name}`,
246+
relativePath: `parsers/${name}`,
247+
absolutePath: `/abs/parsers/${name}`,
248+
isRaw: true,
249+
},
250+
],
251+
])
252+
}
253+
254+
it('forces format=array for raw query parsers without user format', () => {
255+
const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
256+
node.value.setEditOverride('params', {
257+
query: { tags: { parser: 'set' } },
258+
})
259+
const result = generateRouteRecordQuery({
260+
importsMap,
261+
node,
262+
paramParsersMap: rawParsersMap('set'),
263+
})
264+
expect(result).toContain(
265+
`new MatcherPatternQueryParam('tags', 'tags', 'array', _normalized_PARAM_PARSER__set)`
266+
)
267+
})
268+
269+
it('forces format=array and warns when user specifies format=value', () => {
270+
const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
271+
node.value.setEditOverride('params', {
272+
query: { tags: { parser: 'set', format: 'value' } },
273+
})
274+
const result = generateRouteRecordQuery({
275+
importsMap,
276+
node,
277+
paramParsersMap: rawParsersMap('set'),
278+
})
279+
expect(result).toContain(
280+
`new MatcherPatternQueryParam('tags', 'tags', 'array', _normalized_PARAM_PARSER__set)`
281+
)
282+
expect(
283+
`Query param "tags" in route "/a" uses raw param parser "set"`
284+
).toHaveBeenWarned()
285+
})
286+
287+
it('keeps user format=array for raw parsers without warning', () => {
288+
const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
289+
node.value.setEditOverride('params', {
290+
query: { tags: { parser: 'set', format: 'array' } },
291+
})
292+
const result = generateRouteRecordQuery({
293+
importsMap,
294+
node,
295+
paramParsersMap: rawParsersMap('set'),
296+
})
297+
expect(result).toContain(
298+
`new MatcherPatternQueryParam('tags', 'tags', 'array', _normalized_PARAM_PARSER__set)`
299+
)
300+
})
301+
302+
it('does not force array for non-raw parsers', () => {
303+
const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
304+
node.value.setEditOverride('params', {
305+
query: { page: { parser: 'date', format: 'value' } },
306+
})
307+
const paramParsersMap: ParamParsersMap = new Map([
308+
[
309+
'date',
310+
{
311+
name: 'date',
312+
typeName: 'Param_date',
313+
relativePath: 'parsers/date',
314+
absolutePath: '/abs/parsers/date',
315+
isRaw: false,
316+
},
317+
],
318+
])
319+
const result = generateRouteRecordQuery({
320+
importsMap,
321+
node,
322+
paramParsersMap,
323+
})
324+
expect(result).toContain(
325+
`new MatcherPatternQueryParam('page', 'page', 'value', _normalized_PARAM_PARSER__date)`
326+
)
327+
})
328+
})
234329
})
235330

236331
describe('generateRouteRecord', () => {
@@ -1407,18 +1502,18 @@ describe('generateRouteResolver', () => {
14071502

14081503
describe('param parser filtering', () => {
14091504
type ParamParserEntry = NonNullable<ReturnType<ParamParsersMap['get']>>
1410-
const uuidEntry = {
1505+
const uuidEntry: ParamParserEntry = {
14111506
name: 'uuid',
14121507
typeName: 'Param_uuid',
14131508
relativePath: 'parsers/uuid',
14141509
absolutePath: '/abs/parsers/uuid',
1415-
} satisfies ParamParserEntry
1416-
const slugEntry = {
1510+
}
1511+
const slugEntry: ParamParserEntry = {
14171512
name: 'slug',
14181513
typeName: 'Param_slug',
14191514
relativePath: 'parsers/slug',
14201515
absolutePath: '/abs/parsers/slug',
1421-
} satisfies ParamParserEntry
1516+
}
14221517

14231518
it('omits imports and normalized declarations for unused parsers', () => {
14241519
const tree = new PrefixTree(DEFAULT_OPTIONS)

packages/router/src/unplugin/codegen/generateRouteResolver.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,11 +392,23 @@ ${queryParams
392392
paramParsersMap
393393
)
394394
395+
// raw parsers receive the URL value as-is, so force array format to
396+
// guarantee they see the full set of values for the key.
397+
const isRawParser = !!(
398+
param.parser && paramParsersMap.get(param.parser)?.isRaw
399+
)
400+
if (isRawParser && param.format === 'value') {
401+
console.warn(
402+
`Query param "${param.paramName}" in route "${node.fullPath}" uses raw param parser "${param.parser}" but specifies \`format: 'value'\`. The format is ignored because raw parsers always receive the array form.`
403+
)
404+
}
405+
const format = isRawParser ? 'array' : param.format
406+
395407
const args = [
396408
`'${param.paramName}'`,
397409
// TODO: allow param.queryKey
398410
`'${param.paramName}'`,
399-
`'${param.format}'`,
411+
`'${format}'`,
400412
]
401413
402414
if (parserOptions || param.defaultValue !== undefined || param.required) {

0 commit comments

Comments
 (0)