From 63782c88b44072564bfc437756a5e2c45994a389 Mon Sep 17 00:00:00 2001 From: tigerwill90 <26261762+tigerwill90@users.noreply.github.com> Date: Thu, 14 May 2026 01:14:59 +0200 Subject: [PATCH 1/3] fix(subrouter): propagate parent params in Sub default branch --- fox.go | 2 ++ fox_test.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fox.go b/fox.go index b5590d6..a494c24 100644 --- a/fox.go +++ b/fox.go @@ -1003,6 +1003,8 @@ func Sub(router *Router) HandlerFunc { // gracefully as a defensive measure: if the parent registers /api and the sub-router registers /, // we treat it similarly to /api*{any} (optional wildcard), matching /api with the pattern /api/. *subCtx.subPatterns = append(*subCtx.subPatterns, strings.TrimSuffix(c.pattern, "/")) + *subCtx.params = append(*subCtx.params, *c.params...) + *subCtx.paramsKeys = append((*subCtx.paramsKeys)[:0], keys...) router.serveSubRouter(subCtx, "/") return } diff --git a/fox_test.go b/fox_test.go index 321c36a..4945f04 100644 --- a/fox_test.go +++ b/fox_test.go @@ -1434,6 +1434,26 @@ func TestRouter_ServeHTTP_HandleSubRouter(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) }) + t.Run("sub-router with parent param and static suffix propagates params", func(t *testing.T) { + var paramSeen, patternSeen string + sub := MustRouter() + sub.MustAdd(MethodGet, "/", func(c *Context) { + paramSeen = c.Param("ver") + patternSeen = c.Pattern() + }) + + fx := MustRouter() + require.NoError(t, onlyError(fx.Add(MethodGet, "/api/{ver}/admin", Sub(sub)))) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin", nil) + w := httptest.NewRecorder() + fx.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "v1", paramSeen) + assert.Equal(t, "/api/{ver}/admin/", patternSeen) + }) + t.Run("sub-router registered in non route handler", func(t *testing.T) { sub := MustRouter() sub.MustAdd(MethodGet, "/users", patternHandler) From dbfa9f0b1ff12c703d20aaad4a30965cafc93c5a Mon Sep 17 00:00:00 2001 From: tigerwill90 <26261762+tigerwill90@users.noreply.github.com> Date: Thu, 14 May 2026 01:16:48 +0200 Subject: [PATCH 2/3] refactor!(matcher): drop Pattern from RequestContext interface --- context.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/context.go b/context.go index 46acb2a..b934892 100644 --- a/context.go +++ b/context.go @@ -62,8 +62,6 @@ type RequestContext interface { QueryParam(name string) string // Header retrieves the value of the request header for the given key. Header(key string) string - // Pattern returns the registered route pattern or an empty string if the handler is called in a scope other than [RouteHandler]. - Pattern() string } // Context represents the context of the current HTTP request. It provides methods to access request data and From ee8a146ee79ae0c60e4f0eac2bc7ac56358db032 Mon Sep 17 00:00:00 2001 From: tigerwill90 <26261762+tigerwill90@users.noreply.github.com> Date: Thu, 14 May 2026 01:16:57 +0200 Subject: [PATCH 3/3] refactor(context): use new(make) for context slice allocation --- tree.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tree.go b/tree.go index ba9dac3..691f35a 100644 --- a/tree.go +++ b/tree.go @@ -47,15 +47,11 @@ func (t *iTree) lookupByPath(method, path string, c *Context, lazy bool) (int, * } func (t *iTree) allocateContext() *Context { - patterns := make([]string, 0) - params := make([]string, 0, t.maxParams) - keys := make([]string, 0, t.maxParams) - stacks := make(skipStack, 0, t.maxDepth) return &Context{ - params: ¶ms, - skipStack: &stacks, - paramsKeys: &keys, - subPatterns: &patterns, + params: new(make([]string, 0, t.maxParams)), + skipStack: new(make(skipStack, 0, t.maxDepth)), + paramsKeys: new(make([]string, 0, t.maxParams)), + subPatterns: new(make([]string, 0)), // This is a read only value, no reset. It's always the // owner of the pool. tree: t,