Skip to content

Commit 025d60f

Browse files
committed
improvement: raise exception when a route has duplicate params
1 parent e4fcc8d commit 025d60f

File tree

2 files changed

+126
-5
lines changed

2 files changed

+126
-5
lines changed

src/Router/Store.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,32 @@ export class Store {
143143
this.matchDomain = this.matchDomainReal
144144
}
145145

146+
/*
147+
* Generate tokens for the given route and push to the list
148+
* of tokens
149+
*/
150+
const tokens = matchit.parse(route.pattern, route.matchers)
151+
const collectedParams: Set<string> = new Set()
152+
153+
/**
154+
* Avoiding duplicate route params
155+
*/
156+
for (let token of tokens) {
157+
if ([1, 3].includes(token.type)) {
158+
if (collectedParams.has(token.val)) {
159+
throw new Exception(
160+
`Duplicate route param "${token.val}" in route ${route.pattern}`,
161+
500,
162+
'E_DUPLICATE_ROUTE',
163+
)
164+
} else {
165+
collectedParams.add(token.val)
166+
}
167+
}
168+
}
169+
170+
collectedParams.clear()
171+
146172
route.methods.forEach((method) => {
147173
const methodRoutes = this.getMethodRoutes(route.domain || 'root', method)
148174

@@ -159,11 +185,7 @@ export class Store {
159185
)
160186
}
161187

162-
/*
163-
* Generate tokens for the given route and push to the list
164-
* of tokens
165-
*/
166-
methodRoutes.tokens.push(matchit.parse(route.pattern, route.matchers))
188+
methodRoutes.tokens.push(tokens)
167189

168190
/*
169191
* Store reference to the route, so that we can return it to the user, when

test/store.spec.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,40 @@ test.group('Store | add', () => {
117117
assert.throw(fn, 'Duplicate route `GET:/`')
118118
})
119119

120+
test('raise error when two params have the same name', (assert) => {
121+
async function handler () {}
122+
const route = {
123+
pattern: '/:id/:id',
124+
methods: ['GET'],
125+
handler: handler,
126+
matchers: {},
127+
meta: {},
128+
domain: 'foo.com',
129+
middleware: [],
130+
}
131+
132+
const store = new Store()
133+
const fn = () => store.add(route)
134+
assert.throw(fn, 'E_DUPLICATE_ROUTE: Duplicate route param "id" in route /:id/:id')
135+
})
136+
137+
test('allow static path name same as the param name', (assert) => {
138+
async function handler () {}
139+
const route = {
140+
pattern: '/id/:id',
141+
methods: ['GET'],
142+
handler: handler,
143+
matchers: {},
144+
meta: {},
145+
domain: 'foo.com',
146+
middleware: [],
147+
}
148+
149+
const store = new Store()
150+
const fn = () => store.add(route)
151+
assert.doesNotThrow(fn)
152+
})
153+
120154
test('work fine when pattern is same but method is different', (assert) => {
121155
async function handler () {}
122156
const route = {
@@ -250,6 +284,71 @@ test.group('Store | add', () => {
250284
},
251285
})
252286
})
287+
288+
test('add route for multiple methods', (assert) => {
289+
async function handler () {}
290+
291+
const store = new Store()
292+
store.add({
293+
pattern: '/:id',
294+
methods: ['GET', 'POST'],
295+
handler: handler,
296+
matchers: {},
297+
meta: {},
298+
middleware: [],
299+
})
300+
301+
assert.deepEqual(store.tree, {
302+
tokens: [[{
303+
old: 'root',
304+
type: 0,
305+
val: 'root',
306+
end: '',
307+
}]],
308+
domains: {
309+
'root': {
310+
'GET': {
311+
tokens: [[
312+
{
313+
old: '/:id',
314+
type: 1,
315+
val: 'id',
316+
end: '',
317+
matcher: undefined,
318+
},
319+
]],
320+
routes: {
321+
'/:id': {
322+
pattern: '/:id',
323+
meta: {},
324+
handler,
325+
middleware: [],
326+
},
327+
},
328+
},
329+
'POST': {
330+
tokens: [[
331+
{
332+
old: '/:id',
333+
type: 1,
334+
val: 'id',
335+
end: '',
336+
matcher: undefined,
337+
},
338+
]],
339+
routes: {
340+
'/:id': {
341+
pattern: '/:id',
342+
meta: {},
343+
handler,
344+
middleware: [],
345+
},
346+
},
347+
},
348+
},
349+
},
350+
})
351+
})
253352
})
254353

255354
test.group('Store | match', () => {

0 commit comments

Comments
 (0)