Skip to content

Commit 375d8ea

Browse files
committed
Implicitly registered group routes should be allowed overwritten in default routes. fix issue #3047
1 parent 5a43c9b commit 375d8ea

4 files changed

Lines changed: 90 additions & 5 deletions

File tree

echo.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,16 @@ 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+
186+
// GroupImplicitRouteName is a special route name for implicitly registered GROUP routes to execute group-level middleware
187+
// when no route matches. This route is functionally the same as the RouteNotFound method, and a name exists to
188+
// distinguish implicit group routes.
189+
GroupImplicitRouteName = "echo_implicitly_registered_group_route_name"
185190
)
186191

187192
// 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, Name: GroupImplicitRouteName, Path: ""}); 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, Name: GroupImplicitRouteName, Path: "/*"}); 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+
}

router.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,14 @@ 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
510+
509511
if route.Handler == nil {
510512
switch route.Method {
511513
case RouteNotFound:
514+
if route.Name == GroupImplicitRouteName {
515+
allowOverwritingRoute = true
516+
}
512517
route.Handler = r.notFoundHandler
513518
case http.MethodOptions:
514519
route.Handler = r.optionsMethodHandler
@@ -521,7 +526,7 @@ func (r *DefaultRouter) Add(route Route) (RouteInfo, error) {
521526
path := normalizePathSlash(route.Path)
522527

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

0 commit comments

Comments
 (0)