From 65c0bf9bc7d9306f6f0d08176ff77b49171c2fda Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 5 Oct 2026 16:09:57 -0700 Subject: [PATCH 1/3] fix(router): removing a route with a custom method panics Router.Remove clears the handler by calling setHandler(method, nil). For a method without its own field, routeMethods.set then read r.handler on the nil route and panicked. Treat a nil route like a nil handler and delete the entry. --- router.go | 2 +- router_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index 599c561a5..4ffe51445 100644 --- a/router.go +++ b/router.go @@ -255,7 +255,7 @@ func (m *routeMethods) set(method string, r *routeMethod) { if m.anyOther == nil { m.anyOther = make(map[string]*routeMethod) } - if r.handler == nil { + if r == nil || r.handler == nil { // Router.Remove passes nil delete(m.anyOther, method) } else { m.anyOther[method] = r diff --git a/router_test.go b/router_test.go index fd7786f95..5acc66a06 100644 --- a/router_test.go +++ b/router_test.go @@ -3146,6 +3146,31 @@ func TestDefaultRouter_Remove(t *testing.T) { } } +func TestDefaultRouter_RemoveCustomMethod(t *testing.T) { + e := New() + e.Add("PURGE", "/cache", handlerFunc) + e.GET("/cache", handlerFunc) + e.Add("PURGE", "/purge-only", handlerFunc) + + assert.NoError(t, e.Router().Remove("PURGE", "/cache")) + assert.NoError(t, e.Router().Remove("PURGE", "/purge-only")) + + _, err := e.Router().Routes().FindByMethodPath("PURGE", "/cache") + assert.Error(t, err) + _, err = e.Router().Routes().FindByMethodPath(http.MethodGet, "/cache") + assert.NoError(t, err) + + // the path keeps its GET route, so the removed custom method gets 405 + rec := httptest.NewRecorder() + e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/cache", nil)) + assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) + assert.NotContains(t, rec.Header().Get(HeaderAllow), "PURGE") + + rec = httptest.NewRecorder() + e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/purge-only", nil)) + assert.Equal(t, http.StatusNotFound, rec.Code) +} + func TestDefaultRouter_AddWithoutHandler(t *testing.T) { router := NewRouter(RouterConfig{}) From 44b516b3f3756a56b38239e3abb36366fc65f74c Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 5 Oct 2026 16:14:14 -0700 Subject: [PATCH 2/3] test(router): assert the exact Allow header after removing a custom method --- router_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router_test.go b/router_test.go index 5acc66a06..a4a7edf18 100644 --- a/router_test.go +++ b/router_test.go @@ -3164,7 +3164,7 @@ func TestDefaultRouter_RemoveCustomMethod(t *testing.T) { rec := httptest.NewRecorder() e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/cache", nil)) assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) - assert.NotContains(t, rec.Header().Get(HeaderAllow), "PURGE") + assert.Equal(t, "GET, OPTIONS", rec.Header().Get(HeaderAllow)) rec = httptest.NewRecorder() e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/purge-only", nil)) From 6ede7239d0a7126a51e8f28a5a34d4c10f5aae6e Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Mon, 5 Oct 2026 16:14:30 -0700 Subject: [PATCH 3/3] test(router): match the Allow header order after removing a custom method --- router_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router_test.go b/router_test.go index a4a7edf18..3439d2bf8 100644 --- a/router_test.go +++ b/router_test.go @@ -3164,7 +3164,7 @@ func TestDefaultRouter_RemoveCustomMethod(t *testing.T) { rec := httptest.NewRecorder() e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/cache", nil)) assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) - assert.Equal(t, "GET, OPTIONS", rec.Header().Get(HeaderAllow)) + assert.Equal(t, "OPTIONS, GET", rec.Header().Get(HeaderAllow)) rec = httptest.NewRecorder() e.ServeHTTP(rec, httptest.NewRequest("PURGE", "/purge-only", nil))