v0.3.3 - Critical Routing Bug Fix
Fixed
Critical Routing Bug
Fixed panic in routes with :param followed by multiple static segments.
Problem: Routes like /users/:id/activate + /users/:id/deactivate caused panic at line 147 in tree.go:
panic: internal error: no common prefix between "/deactivate" and ":id"
Root Cause: Common prefix check compared /deactivate with :id and found no match.
Solution: Implemented httprouter's production-quality pattern:
- Create empty placeholder child nodes after param nodes
- Bypass common prefix check for param node children
- Use direct children assignment (
n.children = []*node{child}) to allow empty path placeholders
Pattern studied from: httprouter's insertChild (lines 217-270) and addRoute (lines 180-184)
Changes
internal/radix/tree.go(lines 118-138): Added special case handling for param node childreninternal/radix/tree_param_static_test.go(new file): 6 comprehensive test cases covering all param+static patternsCHANGELOG.md: Documented v0.3.3 release with full details
Testing
✅ All 650+ tests passing with race detector (via WSL2 Gentoo)
✅ Coverage: 88.4% internal/radix, 91.7% overall (maintained high quality)
✅ Linter: 0 issues (golangci-lint clean)
✅ Performance: No regressions (256 ns/op static, 326 ns/op parametric)
✅ CI: GREEN
New Test Cases
TestTree_ParamWithMultipleStaticChildren- Original bug case (/users/:id/activate+/users/:id/deactivate)TestTree_NestedParams- Nested params (/users/:user_id/posts/:post_id/comments)TestTree_AlternatingParamStatic- Alternating pattern (/a/:b/c/:d/e)TestTree_ParamWithLongStaticTail- Long static tails (/:a/b/c/d/e/f/g)TestTree_ConsecutiveParams- Consecutive params (/:a/:b/:c/d)TestTree_StaticVsParam- Static priority over params
Performance
- No regressions - routing performance maintained:
- Static routes: 256 ns/op, 1 alloc/op
- Parametric routes: 326 ns/op, 1 alloc/op
Technical Details
- httprouter Pattern: When param node has static children, create empty placeholder child (path="", priority=1) and bypass common prefix check
- Direct Assignment: Use
n.children = []*node{child}instead ofaddChild()to allow empty path placeholder - Zero-allocation Routing: Placeholder pattern maintains zero-allocation guarantee for route lookup
Full Changelog: https://github.com/coregx/fursy/blob/main/CHANGELOG.md#033---2025-01-19