Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,16 @@ func (r *DefaultRouter) Route(c *Context) HandlerFunc {
} else if currentNode.methods.notFoundHandler != nil {
matchedRouteMethod = currentNode.methods.notFoundHandler
break
} else if currentNode.paramChild != nil && currentNode.anyChild == nil &&
currentNode.parent != nil && currentNode.parent.paramChild != nil {
// Path exactly matches this static node. Prefer this over backtracking to a param route
// that would match the last segment (e.g. POST /VerifiedCallerId/Verification should not
// match GET /VerifiedCallerId/:phone_number). Only when parent has paramChild (backtrack
// would match) - otherwise we'd return 405 for paths that should be 404 (e.g. /a3 with route /a3/:id).
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode.paramChild
}
break
}
}

Expand Down
19 changes: 19 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,25 @@ func TestRouterMatchAnyPrefixIssue(t *testing.T) {
}
}

// TestRouterRoutePreferExactPathOverParam tests issue #2547: POST to path without param
// should return 405 with Allow: POST (from the more specific route), not 405 from
// the param route that would capture the last segment.
func TestRouterRoutePreferExactPathOverParam(t *testing.T) {
e := New()
e.GET("/VerifiedCallerId/:phone_number", handlerFunc)
e.POST("/VerifiedCallerId/Verification/:verification_uuid", handlerFunc)

req := httptest.NewRequest(http.MethodPost, "/VerifiedCallerId/Verification/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
handler := e.router.Route(c)

err := handler(c)
assert.ErrorIs(t, err, ErrMethodNotAllowed)
assert.Contains(t, rec.Header().Get("Allow"), "POST")
assert.NotContains(t, rec.Header().Get("Allow"), "GET")
}

// TestRouterMatchAnySlash shall verify finding the best route
// for any routes with trailing slash requests
func TestRouterMatchAnySlash(t *testing.T) {
Expand Down
Loading