From 85625bf50ea386bd07c8016407266228f2fca7a9 Mon Sep 17 00:00:00 2001 From: ProgrammingMuffin Date: Sun, 17 Dec 2023 12:38:48 +0530 Subject: [PATCH 1/4] feat: add :from-:to range route formats --- router.go | 6 +++--- router_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/router.go b/router.go index ee6f3fa48..8f17571d2 100644 --- a/router.go +++ b/router.go @@ -165,7 +165,7 @@ func (r *Router) Reverse(name string, params ...interface{}) string { } if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) { // in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path - for ; i < l && route.Path[i] != '/'; i++ { + for ; i < l && route.Path[i] != '/' && route.Path[i] != '-'; i++ { } uri.WriteString(fmt.Sprintf("%v", params[n])) n++ @@ -220,7 +220,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) { j := i + 1 r.insert(method, path[:i], staticKind, routeMethod{}) - for ; i < lcpIndex && path[i] != '/'; i++ { + for ; i < lcpIndex && (path[i] != '/' && path[i] != '-'); i++ { } pnames = append(pnames, path[j:i]) @@ -660,7 +660,7 @@ func (r *Router) Find(method, path string, c Context) { // act similarly to any node - consider all remaining search as match i = l } else { - for ; i < l && search[i] != '/'; i++ { + for ; i < l && search[i] != '/' && search[i] != '-'; i++ { } } diff --git a/router_test.go b/router_test.go index 619cce092..6fb66992c 100644 --- a/router_test.go +++ b/router_test.go @@ -728,6 +728,42 @@ func TestRouterParam(t *testing.T) { } } +func TestRouterRangeParam(t *testing.T) { + e := New() + r := e.router + + r.Add(http.MethodGet, "/flights/:from-:to", handlerFunc) + + var testCases = []struct { + name string + whenURL string + expectRoute interface{} + expectParam map[string]string + }{ + { + name: "route /flights/LAX-DEN to /flights/:from-:to", + whenURL: "/flights/LAX-DEN", + expectRoute: "/flights/:from-:to", + expectParam: map[string]string{"from": "LAX", "to": "DEN"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + c := e.NewContext(nil, nil).(*context) + r.Find(http.MethodGet, tc.whenURL, c) + + c.handler(c) + assert.Equal(t, tc.expectRoute, c.Get("path")) + for param, expectedValue := range tc.expectParam { + assert.Equal(t, expectedValue, c.Param(param)) + } + checkUnusedParamValues(t, c, tc.expectParam) + }) + } +} + func TestRouter_addAndMatchAllSupportedMethods(t *testing.T) { var testCases = []struct { name string From 9118246789561d93979cf39b1154394d17fb31f2 Mon Sep 17 00:00:00 2001 From: ProgrammingMuffin Date: Mon, 18 Dec 2023 12:52:38 +0530 Subject: [PATCH 2/4] fix: add brackets to distinguish params --- router.go | 17 ++++++++++++----- router_test.go | 13 ++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/router.go b/router.go index 8f17571d2..cf47cd8a9 100644 --- a/router.go +++ b/router.go @@ -210,7 +210,11 @@ func (r *Router) Add(method, path string, h HandlerFunc) { } for i, lcpIndex := 0, len(path); i < lcpIndex; i++ { - if path[i] == ':' { + if path[i] == ':' || path[i] == '{' { + bracketsOpen := false + if path[i] == '{' { + bracketsOpen = true + } if i > 0 && path[i-1] == '\\' { path = path[:i-1] + path[i:] i-- @@ -220,11 +224,15 @@ func (r *Router) Add(method, path string, h HandlerFunc) { j := i + 1 r.insert(method, path[:i], staticKind, routeMethod{}) - for ; i < lcpIndex && (path[i] != '/' && path[i] != '-'); i++ { + for ; i < lcpIndex && (path[i] != '/' && !(path[j-1] == '{' && path[i] == '}')); i++ { } pnames = append(pnames, path[j:i]) - path = path[:j] + path[i:] + if bracketsOpen { + path = path[:j-1] + ":" + path[i+1:] + } else { + path = path[:j] + path[i:] + } i, lcpIndex = j, len(path) if i == lcpIndex { @@ -660,7 +668,7 @@ func (r *Router) Find(method, path string, c Context) { // act similarly to any node - consider all remaining search as match i = l } else { - for ; i < l && search[i] != '/' && search[i] != '-'; i++ { + for ; i < l && search[i] != '/' && !(search[i] == '-' || search[i] == '.'); i++ { } } @@ -714,7 +722,6 @@ func (r *Router) Find(method, path string, c Context) { if currentNode == nil && previousBestMatchNode == nil { return // nothing matched at all } - // matchedHandler could be method+path handler that we matched or notFoundHandler from node with matching path // user provided not found (404) handler has priority over generic method not found (405) handler or global 404 handler var rPath string diff --git a/router_test.go b/router_test.go index 6fb66992c..79c292407 100644 --- a/router_test.go +++ b/router_test.go @@ -732,7 +732,8 @@ func TestRouterRangeParam(t *testing.T) { e := New() r := e.router - r.Add(http.MethodGet, "/flights/:from-:to", handlerFunc) + r.Add(http.MethodGet, "/flights/{from}.{to}", handlerFunc) + r.Add(http.MethodGet, "/flights/{from}-{to}", handlerFunc) var testCases = []struct { name string @@ -741,9 +742,15 @@ func TestRouterRangeParam(t *testing.T) { expectParam map[string]string }{ { - name: "route /flights/LAX-DEN to /flights/:from-:to", + name: "route /flights/LAX.DEN to /flights/{from}.{to}", + whenURL: "/flights/LAX.DEN", + expectRoute: "/flights/{from}.{to}", + expectParam: map[string]string{"from": "LAX", "to": "DEN"}, + }, + { + name: "route /flights/LAX-DEN to /flights/{from}-{to}", whenURL: "/flights/LAX-DEN", - expectRoute: "/flights/:from-:to", + expectRoute: "/flights/{from}-{to}", expectParam: map[string]string{"from": "LAX", "to": "DEN"}, }, } From 852450e3cb57323d12f9d2b9b25651b48921aabd Mon Sep 17 00:00:00 2001 From: ProgrammingMuffin Date: Mon, 18 Dec 2023 14:25:31 +0530 Subject: [PATCH 3/4] fix: make dynamic parameter segment distinction --- router.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index cf47cd8a9..a0b8f0f65 100644 --- a/router.go +++ b/router.go @@ -668,8 +668,14 @@ func (r *Router) Find(method, path string, c Context) { // act similarly to any node - consider all remaining search as match i = l } else { - for ; i < l && search[i] != '/' && !(search[i] == '-' || search[i] == '.'); i++ { + for ; i < l && search[i] != '/'; i++ { + for _, static := range currentNode.staticChildren { + if search[i] == static.label { + goto Done + } + } } + Done: } paramValues[paramIndex] = search[:i] From d2b378c7930020de26e745f8528ab33c635446f0 Mon Sep 17 00:00:00 2001 From: ProgrammingMuffin Date: Mon, 18 Dec 2023 14:44:40 +0530 Subject: [PATCH 4/4] fix: test scenario update --- router_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/router_test.go b/router_test.go index 79c292407..275447f4a 100644 --- a/router_test.go +++ b/router_test.go @@ -733,7 +733,7 @@ func TestRouterRangeParam(t *testing.T) { r := e.router r.Add(http.MethodGet, "/flights/{from}.{to}", handlerFunc) - r.Add(http.MethodGet, "/flights/{from}-{to}", handlerFunc) + r.Add(http.MethodGet, "/flights/{from}to{to}", handlerFunc) var testCases = []struct { name string @@ -748,9 +748,9 @@ func TestRouterRangeParam(t *testing.T) { expectParam: map[string]string{"from": "LAX", "to": "DEN"}, }, { - name: "route /flights/LAX-DEN to /flights/{from}-{to}", - whenURL: "/flights/LAX-DEN", - expectRoute: "/flights/{from}-{to}", + name: "route /flights/LAXtoDEN to /flights/{from}to{to}", + whenURL: "/flights/LAXtoDEN", + expectRoute: "/flights/{from}to{to}", expectParam: map[string]string{"from": "LAX", "to": "DEN"}, }, }