Skip to content

Commit 4da962d

Browse files
committed
fix: prefer exact path match over parameterized match for method checking
Fixes #2547 Made-with: Cursor
1 parent 675712d commit 4da962d

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

router.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,16 @@ func (r *DefaultRouter) Route(c *Context) HandlerFunc {
911911
} else if currentNode.methods.notFoundHandler != nil {
912912
matchedRouteMethod = currentNode.methods.notFoundHandler
913913
break
914+
} else if currentNode.paramChild != nil && currentNode.anyChild == nil &&
915+
currentNode.parent != nil && currentNode.parent.paramChild != nil {
916+
// Path exactly matches this static node. Prefer this over backtracking to a param route
917+
// that would match the last segment (e.g. POST /VerifiedCallerId/Verification should not
918+
// match GET /VerifiedCallerId/:phone_number). Only when parent has paramChild (backtrack
919+
// would match) - otherwise we'd return 405 for paths that should be 404 (e.g. /a3 with route /a3/:id).
920+
if previousBestMatchNode == nil {
921+
previousBestMatchNode = currentNode.paramChild
922+
}
923+
break
914924
}
915925
}
916926

router_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,25 @@ func TestRouterMatchAnyPrefixIssue(t *testing.T) {
15141514
}
15151515
}
15161516

1517+
// TestRouterRoutePreferExactPathOverParam tests issue #2547: POST to path without param
1518+
// should return 405 with Allow: POST (from the more specific route), not 405 from
1519+
// the param route that would capture the last segment.
1520+
func TestRouterRoutePreferExactPathOverParam(t *testing.T) {
1521+
e := New()
1522+
e.GET("/VerifiedCallerId/:phone_number", handlerFunc)
1523+
e.POST("/VerifiedCallerId/Verification/:verification_uuid", handlerFunc)
1524+
1525+
req := httptest.NewRequest(http.MethodPost, "/VerifiedCallerId/Verification/", nil)
1526+
rec := httptest.NewRecorder()
1527+
c := e.NewContext(req, rec)
1528+
handler := e.router.Route(c)
1529+
1530+
err := handler(c)
1531+
assert.ErrorIs(t, err, ErrMethodNotAllowed)
1532+
assert.Contains(t, rec.Header().Get("Allow"), "POST")
1533+
assert.NotContains(t, rec.Header().Get("Allow"), "GET")
1534+
}
1535+
15171536
// TestRouterMatchAnySlash shall verify finding the best route
15181537
// for any routes with trailing slash requests
15191538
func TestRouterMatchAnySlash(t *testing.T) {

0 commit comments

Comments
 (0)