From 545191d6200d344f46f8d6f6cb1ef09b31624e40 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Thu, 16 Sep 2021 21:26:38 +0300 Subject: [PATCH] allow escaping of colon in route path so Google Cloud API "custom methods" https://cloud.google.com/apis/design/custom_methods can be implemented (resolves #1987) --- router.go | 3 +++ router_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/router.go b/router.go index 5b2474b32..a8277c8b8 100644 --- a/router.go +++ b/router.go @@ -98,6 +98,9 @@ func (r *Router) Add(method, path string, h HandlerFunc) { for i, lcpIndex := 0, len(path); i < lcpIndex; i++ { if path[i] == ':' { + if i > 0 && path[i-1] == '\\' { + continue + } j := i + 1 r.insert(method, path[:i], nil, staticKind, "", nil) diff --git a/router_test.go b/router_test.go index 71cedf8b6..1cb36b447 100644 --- a/router_test.go +++ b/router_test.go @@ -1118,6 +1118,58 @@ func TestRouterParamStaticConflict(t *testing.T) { } } +func TestRouterParam_escapeColon(t *testing.T) { + // to allow Google cloud API like route paths with colon in them + // i.e. https://service.name/v1/some/resource/name:customVerb <- that `:customVerb` is not path param. It is just a string + e := New() + + e.POST("/files/a/long/file\\:undelete", handlerFunc) + e.POST("/v1/some/resource/name:customVerb", handlerFunc) + + var testCases = []struct { + whenURL string + expectRoute interface{} + expectParam map[string]string + expectError string + }{ + { + whenURL: "/files/a/long/file\\:undelete", + expectRoute: "/files/a/long/file\\:undelete", + expectParam: map[string]string{}, + }, + { + whenURL: "/files/a/long/file\\:notMatching", + expectRoute: nil, + expectError: "code=404, message=Not Found", + expectParam: nil, + }, + { + whenURL: "/v1/some/resource/name:PATCH", + expectRoute: "/v1/some/resource/name:customVerb", + expectParam: map[string]string{"customVerb": ":PATCH"}, + }, + } + for _, tc := range testCases { + t.Run(tc.whenURL, func(t *testing.T) { + c := e.NewContext(nil, nil).(*context) + + e.router.Find(http.MethodPost, tc.whenURL, c) + err := c.handler(c) + + assert.Equal(t, tc.expectRoute, c.Get("path")) + if tc.expectError != "" { + assert.EqualError(t, err, tc.expectError) + } else { + assert.NoError(t, err) + } + for param, expectedValue := range tc.expectParam { + assert.Equal(t, expectedValue, c.Param(param)) + } + checkUnusedParamValues(t, c, tc.expectParam) + }) + } +} + func TestRouterMatchAny(t *testing.T) { e := New() r := e.router