diff --git a/mux_test.go b/mux_test.go index cb3bbe0a..0845d7f7 100644 --- a/mux_test.go +++ b/mux_test.go @@ -2116,6 +2116,24 @@ func TestMultipleDefinitionOfSamePathWithDifferentMethods(t *testing.T) { } +func TestMultipleDefinitionOfSamePathWithDifferentQueries(t *testing.T) { + emptyHandler := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/api", emptyHandler).Queries("foo", "{foo:[0-9]+}").Methods(http.MethodGet) + r.HandleFunc("/api", emptyHandler).Queries("bar", "{bar:[0-9]+}").Methods(http.MethodGet) + + req := newRequest(http.MethodGet, "/api?bar=4") + match := new(RouteMatch) + matched := r.Match(req, match) + if !matched { + t.Error("Should have matched route for methods") + } + if match.MatchErr != nil { + t.Error("Should have no error. Found:", match.MatchErr) + } +} + func TestErrMatchNotFound(t *testing.T) { emptyHandler := func(w http.ResponseWriter, r *http.Request) {} @@ -2784,6 +2802,23 @@ func TestMethodNotAllowed(t *testing.T) { } } +func TestMethodNotAllowedSubrouterWithSeveralRoutes(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } + + router := NewRouter() + subrouter := router.PathPrefix("/v1").Subrouter() + subrouter.HandleFunc("/api", handler).Methods(http.MethodGet) + subrouter.HandleFunc("/api/{id}", handler).Methods(http.MethodGet) + + w := NewRecorder() + req := newRequest(http.MethodPut, "/v1/api") + router.ServeHTTP(w, req) + + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("Expected status code 405 (got %d)", w.Code) + } +} + type customMethodNotAllowedHandler struct { msg string } diff --git a/route.go b/route.go index 8a9e754a..b6582dae 100644 --- a/route.go +++ b/route.go @@ -53,6 +53,19 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { continue } + // Multiple routes may share the same path but use different HTTP methods. For instance: + // Route 1: POST "/users/{id}". + // Route 2: GET "/users/{id}", parameters: "id": "[0-9]+". + // + // The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2", + // The router should return a "Not Found" error as no route fully matches this request. + if rr, ok := m.(*routeRegexp); ok { + if rr.regexpType == regexpTypeQuery { + matchErr = ErrNotFound + break + } + } + // Ignore ErrNotFound errors. These errors arise from match call // to Subrouters. // @@ -66,16 +79,6 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { matchErr = nil // nolint:ineffassign return false - } else { - // Multiple routes may share the same path but use different HTTP methods. For instance: - // Route 1: POST "/users/{id}". - // Route 2: GET "/users/{id}", parameters: "id": "[0-9]+". - // - // The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2", - // The router should return a "Not Found" error as no route fully matches this request. - if match.MatchErr == ErrMethodMismatch { - match.MatchErr = nil - } } } @@ -84,7 +87,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { return false } - if match.MatchErr == ErrMethodMismatch && r.handler != nil { + if match.MatchErr != nil && r.handler != nil { // We found a route which matches request method, clear MatchErr match.MatchErr = nil // Then override the mis-matched handler