Skip to content

Commit 43248e8

Browse files
committed
fix(subrouter): strip regex source from pattern prefix in Sub
1 parent c50f167 commit 43248e8

4 files changed

Lines changed: 50 additions & 11 deletions

File tree

fox.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net"
1313
"net/http"
1414
"path"
15+
"regexp"
1516
"slices"
1617
"strings"
1718
"sync"
@@ -975,15 +976,24 @@ func Sub(router *Router) HandlerFunc {
975976

976977
*subCtx.subPatterns = append(*subCtx.subPatterns, *c.subPatterns...)
977978

978-
lastTkType := route.pattern.tokens[len(route.pattern.tokens)-1].typ
979+
lastTk := route.pattern.tokens[len(route.pattern.tokens)-1]
979980
var p string
980-
switch lastTkType {
981+
switch lastTk.typ {
981982
case nodeWildcard:
982983
key := (*c.paramsKeys)[len(*c.paramsKeys)-1]
983-
p = strings.TrimSuffix(c.pattern[:len(c.pattern)-(len(key)+wildcardExtraChar)], "/")
984+
extra := len(key) + wildcardExtraChar
985+
if lastTk.regexp != nil {
986+
// +1 for the ':' separator between name and regex source.
987+
extra += 1 + len(rawExpr(lastTk.regexp))
988+
}
989+
p = strings.TrimSuffix(c.pattern[:len(c.pattern)-extra], "/")
984990
case nodeParam:
985991
key := (*c.paramsKeys)[len(*c.paramsKeys)-1]
986-
p = strings.TrimSuffix(c.pattern[:len(c.pattern)-(len(key)+paramExtraChar)], "/")
992+
extra := len(key) + paramExtraChar
993+
if lastTk.regexp != nil {
994+
extra += 1 + len(rawExpr(lastTk.regexp))
995+
}
996+
p = strings.TrimSuffix(c.pattern[:len(c.pattern)-extra], "/")
987997
default:
988998
// Reaching this case means the parent route does not end with a catch-all parameter (e.g., /api/
989999
// instead of /api/+{rest}). This is technically a misuse of the sub-router API, but we handle it
@@ -1124,3 +1134,8 @@ func firstHeader(headers http.Header, k string) (string, bool) {
11241134
}
11251135
return v[0], true
11261136
}
1137+
1138+
func rawExpr(re *regexp.Regexp) string {
1139+
expr := re.String()
1140+
return expr[4 : len(expr)-2]
1141+
}

fox_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,34 @@ func TestRouter_ServeHTTP_HandleSubRouter(t *testing.T) {
14781478
assert.Equal(t, http.StatusNotFound, w.Code)
14791479
})
14801480

1481+
t.Run("sub-router registered with regex wildcard", func(t *testing.T) {
1482+
sub := MustRouter()
1483+
sub.MustAdd(MethodGet, "/users", patternHandler)
1484+
1485+
fx := MustRouter(AllowRegexpParam(true))
1486+
require.NoError(t, onlyError(fx.Add(MethodGet, "/api/+{rest:[a-z]+}", Sub(sub))))
1487+
1488+
req := httptest.NewRequest(http.MethodGet, "/api/users", nil)
1489+
w := httptest.NewRecorder()
1490+
fx.ServeHTTP(w, req)
1491+
assert.Equal(t, http.StatusOK, w.Code)
1492+
assert.Equal(t, "/api/users", w.Body.String())
1493+
})
1494+
1495+
t.Run("sub-router registered with regex param", func(t *testing.T) {
1496+
sub := MustRouter()
1497+
sub.MustAdd(MethodGet, "/users", patternHandler)
1498+
1499+
fx := MustRouter(AllowRegexpParam(true))
1500+
require.NoError(t, onlyError(fx.Add(MethodGet, "/api/{name:[a-z]+}", Sub(sub))))
1501+
1502+
req := httptest.NewRequest(http.MethodGet, "/api/users", nil)
1503+
w := httptest.NewRecorder()
1504+
fx.ServeHTTP(w, req)
1505+
assert.Equal(t, http.StatusOK, w.Code)
1506+
assert.Equal(t, "/api/users", w.Body.String())
1507+
})
1508+
14811509
t.Run("sub-router no route handler sees clean context", func(t *testing.T) {
14821510
var pat, tenant string
14831511
var route *Route

matcher.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,7 @@ func (m QueryRegexpMatcher) Key() string {
110110

111111
// Value returns the regular expression matching the query parameter.
112112
func (m QueryRegexpMatcher) Value() string {
113-
expr := m.regex.String()
114-
return expr[4 : len(expr)-2]
113+
return rawExpr(m.regex)
115114
}
116115

117116
// String returns a textual representation of the matcher in the form "qx:key=expr".
@@ -224,8 +223,7 @@ func (m HeaderRegexpMatcher) Key() string {
224223

225224
// Value returns the regular expression matching the header.
226225
func (m HeaderRegexpMatcher) Value() string {
227-
expr := m.regex.String()
228-
return expr[4 : len(expr)-2]
226+
return rawExpr(m.regex)
229227
}
230228

231229
// Match reports whether the request contains the configured header with any value matching the configured regular

tree.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,9 +953,7 @@ func concat(a, b string) string {
953953
// placeholder ("?" for params, "*" for catch-alls).
954954
func canonicalKey(tk token) string {
955955
if tk.regexp != nil {
956-
// Strip the surrounding ^(?: and )$ added by compileParamRegexp.
957-
expr := tk.regexp.String()
958-
return expr[4 : len(expr)-2]
956+
return rawExpr(tk.regexp)
959957
}
960958
switch tk.typ {
961959
case nodeParam:

0 commit comments

Comments
 (0)