Skip to content

v0.3.3 - Critical Routing Bug Fix

Choose a tag to compare

@kolkov kolkov released this 24 Nov 00:29
· 10 commits to main since this release

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 children
  • internal/radix/tree_param_static_test.go (new file): 6 comprehensive test cases covering all param+static patterns
  • CHANGELOG.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

  1. TestTree_ParamWithMultipleStaticChildren - Original bug case (/users/:id/activate + /users/:id/deactivate)
  2. TestTree_NestedParams - Nested params (/users/:user_id/posts/:post_id/comments)
  3. TestTree_AlternatingParamStatic - Alternating pattern (/a/:b/c/:d/e)
  4. TestTree_ParamWithLongStaticTail - Long static tails (/:a/b/c/d/e/f/g)
  5. TestTree_ConsecutiveParams - Consecutive params (/:a/:b/:c/d)
  6. 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 of addChild() 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