Skip to content

Commit 7d54690

Browse files
mihardMihard
and
Mihard
authored
Proper colon support in reverse (#2416)
* Adds support of the escaped colon in echo.Reverse --------- Co-authored-by: Mihard <[email protected]>
1 parent de1c798 commit 7d54690

File tree

2 files changed

+96
-20
lines changed

2 files changed

+96
-20
lines changed

echo_test.go

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,26 +1517,97 @@ func TestEcho_OnAddRouteHandler(t *testing.T) {
15171517
}
15181518

15191519
func TestEchoReverse(t *testing.T) {
1520-
e := New()
1521-
dummyHandler := func(Context) error { return nil }
1520+
var testCases = []struct {
1521+
name string
1522+
whenRouteName string
1523+
whenParams []interface{}
1524+
expect string
1525+
}{
1526+
{
1527+
name: "ok,static with no params",
1528+
whenRouteName: "/static",
1529+
expect: "/static",
1530+
},
1531+
{
1532+
name: "ok,static with non existent param",
1533+
whenRouteName: "/static",
1534+
whenParams: []interface{}{"missing param"},
1535+
expect: "/static",
1536+
},
1537+
{
1538+
name: "ok, wildcard with no params",
1539+
whenRouteName: "/static/*",
1540+
expect: "/static/*",
1541+
},
1542+
{
1543+
name: "ok, wildcard with params",
1544+
whenRouteName: "/static/*",
1545+
whenParams: []interface{}{"foo.txt"},
1546+
expect: "/static/foo.txt",
1547+
},
1548+
{
1549+
name: "ok, single param without param",
1550+
whenRouteName: "/params/:foo",
1551+
expect: "/params/:foo",
1552+
},
1553+
{
1554+
name: "ok, single param with param",
1555+
whenRouteName: "/params/:foo",
1556+
whenParams: []interface{}{"one"},
1557+
expect: "/params/one",
1558+
},
1559+
{
1560+
name: "ok, multi param without params",
1561+
whenRouteName: "/params/:foo/bar/:qux",
1562+
expect: "/params/:foo/bar/:qux",
1563+
},
1564+
{
1565+
name: "ok, multi param with one param",
1566+
whenRouteName: "/params/:foo/bar/:qux",
1567+
whenParams: []interface{}{"one"},
1568+
expect: "/params/one/bar/:qux",
1569+
},
1570+
{
1571+
name: "ok, multi param with all params",
1572+
whenRouteName: "/params/:foo/bar/:qux",
1573+
whenParams: []interface{}{"one", "two"},
1574+
expect: "/params/one/bar/two",
1575+
},
1576+
{
1577+
name: "ok, multi param + wildcard with all params",
1578+
whenRouteName: "/params/:foo/bar/:qux/*",
1579+
whenParams: []interface{}{"one", "two", "three"},
1580+
expect: "/params/one/bar/two/three",
1581+
},
1582+
{
1583+
name: "ok, backslash is not escaped",
1584+
whenRouteName: "/backslash",
1585+
whenParams: []interface{}{"test"},
1586+
expect: `/a\b/test`,
1587+
},
1588+
{
1589+
name: "ok, escaped colon verbs",
1590+
whenRouteName: "/params:customVerb",
1591+
whenParams: []interface{}{"PATCH"},
1592+
expect: `/params:PATCH`,
1593+
},
1594+
}
1595+
for _, tc := range testCases {
1596+
t.Run(tc.name, func(t *testing.T) {
1597+
e := New()
1598+
dummyHandler := func(Context) error { return nil }
1599+
1600+
e.GET("/static", dummyHandler).Name = "/static"
1601+
e.GET("/static/*", dummyHandler).Name = "/static/*"
1602+
e.GET("/params/:foo", dummyHandler).Name = "/params/:foo"
1603+
e.GET("/params/:foo/bar/:qux", dummyHandler).Name = "/params/:foo/bar/:qux"
1604+
e.GET("/params/:foo/bar/:qux/*", dummyHandler).Name = "/params/:foo/bar/:qux/*"
1605+
e.GET("/a\\b/:x", dummyHandler).Name = "/backslash"
1606+
e.GET("/params\\::customVerb", dummyHandler).Name = "/params:customVerb"
15221607

1523-
e.GET("/static", dummyHandler).Name = "/static"
1524-
e.GET("/static/*", dummyHandler).Name = "/static/*"
1525-
e.GET("/params/:foo", dummyHandler).Name = "/params/:foo"
1526-
e.GET("/params/:foo/bar/:qux", dummyHandler).Name = "/params/:foo/bar/:qux"
1527-
e.GET("/params/:foo/bar/:qux/*", dummyHandler).Name = "/params/:foo/bar/:qux/*"
1528-
1529-
assert.Equal(t, "/static", e.Reverse("/static"))
1530-
assert.Equal(t, "/static", e.Reverse("/static", "missing param"))
1531-
assert.Equal(t, "/static/*", e.Reverse("/static/*"))
1532-
assert.Equal(t, "/static/foo.txt", e.Reverse("/static/*", "foo.txt"))
1533-
1534-
assert.Equal(t, "/params/:foo", e.Reverse("/params/:foo"))
1535-
assert.Equal(t, "/params/one", e.Reverse("/params/:foo", "one"))
1536-
assert.Equal(t, "/params/:foo/bar/:qux", e.Reverse("/params/:foo/bar/:qux"))
1537-
assert.Equal(t, "/params/one/bar/:qux", e.Reverse("/params/:foo/bar/:qux", "one"))
1538-
assert.Equal(t, "/params/one/bar/two", e.Reverse("/params/:foo/bar/:qux", "one", "two"))
1539-
assert.Equal(t, "/params/one/bar/two/three", e.Reverse("/params/:foo/bar/:qux/*", "one", "two", "three"))
1608+
assert.Equal(t, tc.expect, e.Reverse(tc.whenRouteName, tc.whenParams...))
1609+
})
1610+
}
15401611
}
15411612

15421613
func TestEchoReverseHandleHostProperly(t *testing.T) {

router.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,12 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
159159
for _, route := range r.routes {
160160
if route.Name == name {
161161
for i, l := 0, len(route.Path); i < l; i++ {
162-
if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln {
162+
hasBackslash := route.Path[i] == '\\'
163+
if hasBackslash && i+1 < l && route.Path[i+1] == ':' {
164+
i++ // backslash before colon escapes that colon. in that case skip backslash
165+
}
166+
if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) {
167+
// in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path
163168
for ; i < l && route.Path[i] != '/'; i++ {
164169
}
165170
uri.WriteString(fmt.Sprintf("%v", params[n]))

0 commit comments

Comments
 (0)