From d4548e1a17cce466edcf77a5add01bc58aec35fd Mon Sep 17 00:00:00 2001 From: tigerwill90 <26261762+tigerwill90@users.noreply.github.com> Date: Thu, 14 May 2026 02:00:55 +0200 Subject: [PATCH] feat(matcher): seal Context from matcher API via onlyRequestContext --- context.go | 17 +++++++++++++++++ route.go | 7 ++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index b934892..6ef0469 100644 --- a/context.go +++ b/context.go @@ -415,6 +415,23 @@ func (c *Context) getQueries() url.Values { return c.cachedQueries } +// onlyRequestContext wraps *Context so matchers can only reach the [RequestContext] interface and cannot +// misuse the full Context API. +type onlyRequestContext struct { + c *Context +} + +func (s onlyRequestContext) Request() *http.Request { return s.c.Request() } +func (s onlyRequestContext) RemoteIP() *net.IPAddr { return s.c.RemoteIP() } +func (s onlyRequestContext) ClientIP() (*net.IPAddr, error) { return s.c.ClientIP() } +func (s onlyRequestContext) Method() string { return s.c.Method() } +func (s onlyRequestContext) Path() string { return s.c.Path() } +func (s onlyRequestContext) EscapedPath() string { return s.c.EscapedPath() } +func (s onlyRequestContext) Host() string { return s.c.Host() } +func (s onlyRequestContext) QueryParams() url.Values { return s.c.QueryParams() } +func (s onlyRequestContext) QueryParam(name string) string { return s.c.QueryParam(name) } +func (s onlyRequestContext) Header(key string) string { return s.c.Header(key) } + // WrapF is an adapter for wrapping [http.HandlerFunc] and returns a [HandlerFunc] function. // The route parameters are being accessed by the wrapped handler through the context. func WrapF(f http.HandlerFunc) HandlerFunc { diff --git a/route.go b/route.go index 10e807f..70bf442 100644 --- a/route.go +++ b/route.go @@ -135,7 +135,7 @@ func (r *Route) String() string { // match reports whether the request satisfies this route's method constraint (if any) // and all attached matchers. -func (r *Route) match(method string, c RequestContext) bool { +func (r *Route) match(method string, c *Context) bool { // Fast path: routes with exactly one method and no matchers cache that // method in methodFast. if r.methodFast == method { @@ -149,7 +149,7 @@ func (r *Route) match(method string, c RequestContext) bool { // inlinable. // //go:noinline -func (r *Route) matchSlow(method string, c RequestContext) bool { +func (r *Route) matchSlow(method string, c *Context) bool { methods := r.methods switch len(methods) { case 0: @@ -165,8 +165,9 @@ func (r *Route) matchSlow(method string, c RequestContext) bool { return false } } + sealed := onlyRequestContext{c} for _, m := range r.matchers { - if !m.Match(c) { + if !m.Match(sealed) { return false } }