Skip to content

Commit 8148ea9

Browse files
committed
Implicitly registered group routes should be allowed overwritten in default routes. fix issue #3047
1 parent bba3d12 commit 8148ea9

5 files changed

Lines changed: 87 additions & 5 deletions

File tree

echo.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ const (
177177
// QUERY Method is a safe, idempotent request method carrying request content (a query) in its body, see rfc 10008.
178178
// It is not (yet) part of the `net/http` standard library, so Echo defines it here.
179179
QUERY = "QUERY"
180-
// RouteNotFound is special method type for routes handling "route not found" (404) cases
180+
// RouteNotFound is a special method type for routes handling "route not found" (404) cases
181181
RouteNotFound = "echo_route_not_found"
182-
// RouteAny is special method type that matches any HTTP method in request. Any has lower
182+
// RouteAny is a special method type that matches any HTTP method in request. Any has lower
183183
// priority that other methods that have been registered with Router to that path.
184184
RouteAny = "echo_route_any"
185+
185186
)
186187

187188
// Headers

group.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
4141
// So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the
4242
// Router would find route to match our request path and therefore guarantee the middleware(s) will get executed.
4343
// Note: we use nil handler so Router would choose the default 404 handler. This may not work with custom routers.
44-
g.RouteNotFound("", nil)
45-
g.RouteNotFound("/*", nil)
44+
if _, err := g.AddRoute(Route{Method: RouteNotFound, Path: "", allowOverwrite: true}); err != nil {
45+
panic(err) // this is how `v4` handles errors. `v5` has methods to have panic-free usage
46+
}
47+
if _, err := g.AddRoute(Route{Method: RouteNotFound, Path: "/*", allowOverwrite: true}); err != nil {
48+
panic(err) // this is how `v4` handles errors. `v5` has methods to have panic-free usage
49+
}
4650
}
4751

4852
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. Panics on error.

group_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,3 +866,74 @@ func TestGroup_RouteNotFoundWithMiddleware(t *testing.T) {
866866
})
867867
}
868868
}
869+
870+
func TestGroup_UseMultipleTimes(t *testing.T) {
871+
t.Run("Group created without middleware can call Use multiple times", func(t *testing.T) {
872+
e := NewWithConfig(Config{
873+
Router: NewRouter(RouterConfig{AllowOverwritingRoute: false}),
874+
})
875+
876+
g1 := e.Group("/api")
877+
mw1Called := false
878+
g1.Use(func(next HandlerFunc) HandlerFunc {
879+
mw1Called = true
880+
return func(c *Context) error { return next(c) }
881+
})
882+
883+
mw2Called := false
884+
g1.Use(func(next HandlerFunc) HandlerFunc {
885+
mw2Called = true
886+
return func(c *Context) error { return next(c) }
887+
})
888+
889+
g1.GET("/test", func(c *Context) error {
890+
return c.String(http.StatusTeapot, "OK")
891+
})
892+
893+
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
894+
rec := httptest.NewRecorder()
895+
e.ServeHTTP(rec, req)
896+
897+
assert.True(t, mw1Called)
898+
assert.True(t, mw2Called)
899+
assert.Equal(t, http.StatusTeapot, rec.Code)
900+
})
901+
902+
t.Run("Group created with middleware can call Use multiple times", func(t *testing.T) {
903+
e := NewWithConfig(Config{
904+
Router: NewRouter(RouterConfig{AllowOverwritingRoute: false}),
905+
})
906+
907+
mw0Called := true
908+
g1 := e.Group("/api", func(next HandlerFunc) HandlerFunc {
909+
mw0Called = true
910+
return func(c *Context) error { return next(c) }
911+
})
912+
913+
mw1Called := false
914+
g1.Use(func(next HandlerFunc) HandlerFunc {
915+
mw1Called = true
916+
return func(c *Context) error { return next(c) }
917+
})
918+
919+
mw2Called := false
920+
g1.Use(func(next HandlerFunc) HandlerFunc {
921+
mw2Called = true
922+
return func(c *Context) error { return next(c) }
923+
})
924+
925+
g1.GET("/test", func(c *Context) error {
926+
return c.String(http.StatusTeapot, "OK")
927+
})
928+
929+
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
930+
rec := httptest.NewRecorder()
931+
e.ServeHTTP(rec, req)
932+
933+
assert.True(t, mw0Called)
934+
assert.True(t, mw1Called)
935+
assert.True(t, mw2Called)
936+
assert.Equal(t, http.StatusTeapot, rec.Code)
937+
})
938+
939+
}

route.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ type Route struct {
2222
// fallback to default/global handlers in certain situations.
2323
Handler HandlerFunc
2424
Middlewares []MiddlewareFunc
25+
26+
// allowOverwrite permits this route to replace an existing route with the same method+path,
27+
// overriding the router's AllowOverwritingRoute config for this specific registration.
28+
allowOverwrite bool
2529
}
2630

2731
// ToRouteInfo converts Route to RouteInfo

router.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ func newAddRouteError(route Route, err error) *AddRouteError {
506506

507507
// Add registers a new route for method and path with matching handler.
508508
func (r *DefaultRouter) Add(route Route) (RouteInfo, error) {
509+
allowOverwritingRoute := r.allowOverwritingRoute || route.allowOverwrite
510+
509511
if route.Handler == nil {
510512
switch route.Method {
511513
case RouteNotFound:
@@ -521,7 +523,7 @@ func (r *DefaultRouter) Add(route Route) (RouteInfo, error) {
521523
path := normalizePathSlash(route.Path)
522524

523525
h := applyMiddleware(route.Handler, route.Middlewares...)
524-
if !r.allowOverwritingRoute {
526+
if !allowOverwritingRoute {
525527
for _, rr := range r.routes {
526528
if route.Method == rr.Method && route.Path == rr.Path {
527529
return RouteInfo{}, newAddRouteError(route, errors.New("adding duplicate route (same method+path) is not allowed"))

0 commit comments

Comments
 (0)