LinearRouter matches a path with an empty segment and hands the handler an empty param. The other three routers return 404.
import { Hono } from 'hono'
import { LinearRouter } from 'hono/router/linear-router'
const app = new Hono({ router: new LinearRouter() })
app.get('/u/:id/posts', (c) => c.json({ id: c.req.param('id') }))
await app.request('http://localhost/u/42/posts') // 200 { id: "42" }
await app.request('http://localhost/u//posts') // 200 { id: "" } <- expected 404
| Router |
/u//posts |
| RegExpRouter |
404 |
| TrieRouter |
404 |
| PatternRouter |
404 |
| LinearRouter |
200, id is "" |
A handler that expects an id to be present gets an empty string instead.
Cause
In match(), the plain label branch already rejects an empty segment at the end of the path but not one in the middle:
let endValuePos = path.indexOf('/', pos + 1)
if (endValuePos === -1) {
if (pos + 1 === path.length) {
continue ROUTES_LOOP // trailing empty segment, rejected
}
endValuePos = path.length
}
value = path.slice(pos + 1, endValuePos)
For /u//posts the indexOf returns pos + 1, so value is '' and the match carries on. That is why /a/:p correctly 404s on /a/ but /u/:id/posts matches /u//posts.
Fix is to reject endValuePos === pos + 1 the same way. Full suite passes and I added the case to common.case.test.ts, where all four routers pass it. Happy to open the PR.
LinearRoutermatches a path with an empty segment and hands the handler an empty param. The other three routers return 404./u//postsidis""A handler that expects an id to be present gets an empty string instead.
Cause
In
match(), the plain label branch already rejects an empty segment at the end of the path but not one in the middle:For
/u//poststheindexOfreturnspos + 1, sovalueis''and the match carries on. That is why/a/:pcorrectly 404s on/a/but/u/:id/postsmatches/u//posts.Fix is to reject
endValuePos === pos + 1the same way. Full suite passes and I added the case tocommon.case.test.ts, where all four routers pass it. Happy to open the PR.