Skip to content

Commit c74e132

Browse files
authored
fix: return HTTP 405 when request method mismatch (#25)
When a route is correctly matched, but the HTTP method used does not, we should return an HTTP 405 Method Not Allowed. However, in our previous implementation, we would always return an HTTP 404 Not Found, which is incorrect. Instead, we can match on whether we have received an `ErrMethodNotAllowed` and if so, return the correct HTTP 405.
1 parent e1bbe8a commit c74e132

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

internal/test/chi/oapi_validate_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,19 @@ func testRequestValidatorBasicFunctions(t *testing.T, r *chi.Mux) {
391391
called = false
392392
}
393393

394+
// Send a request with wrong HTTP method
395+
{
396+
body := struct {
397+
Name string `json:"name"`
398+
}{
399+
Name: "Marcin",
400+
}
401+
rec := doPost(t, r, "http://deepmap.ai/resource", body)
402+
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
403+
assert.False(t, called, "Handler should not have been called")
404+
called = false
405+
}
406+
394407
// Add a handler for the POST message
395408
r.Post("/resource", func(w http.ResponseWriter, r *http.Request) {
396409
called = true

internal/test/gorilla/oapi_validate_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,19 @@ func testRequestValidatorBasicFunctions(t *testing.T, r *mux.Router) {
391391
called = false
392392
}
393393

394+
// Send a request with wrong HTTP method
395+
{
396+
body := struct {
397+
Name string `json:"name"`
398+
}{
399+
Name: "Marcin",
400+
}
401+
rec := doPost(t, r, "http://deepmap.ai/resource", body)
402+
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
403+
assert.False(t, called, "Handler should not have been called")
404+
called = false
405+
}
406+
394407
// Add a handler for the POST message
395408
r.HandleFunc("/resource", func(w http.ResponseWriter, r *http.Request) {
396409
called = true

internal/test/nethttp/oapi_validate_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ func doPost(t *testing.T, mux http.Handler, rawURL string, jsonBody interface{})
6262
return rr
6363
}
6464

65+
func doPatch(t *testing.T, mux http.Handler, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder {
66+
u, err := url.Parse(rawURL)
67+
if err != nil {
68+
t.Fatalf("Invalid url: %s", rawURL)
69+
}
70+
71+
data, err := json.Marshal(jsonBody)
72+
require.NoError(t, err)
73+
74+
req, err := http.NewRequest(http.MethodPatch, u.String(), bytes.NewReader(data))
75+
require.NoError(t, err)
76+
77+
req.Header.Set("content-type", "application/json")
78+
79+
rr := httptest.NewRecorder()
80+
81+
mux.ServeHTTP(rr, req)
82+
83+
return rr
84+
}
85+
6586
// use wraps a given http.ServeMux with middleware for execution
6687
func use(r *http.ServeMux, mw func(next http.Handler) http.Handler) http.Handler {
6788
return mw(r)
@@ -434,6 +455,19 @@ func testRequestValidatorBasicFunctions(t *testing.T, r *http.ServeMux, mw func(
434455
called = false
435456
}
436457

458+
// Send a request with wrong method
459+
{
460+
body := struct {
461+
Name string `json:"name"`
462+
}{
463+
Name: "Marcin",
464+
}
465+
rec := doPatch(t, server, "http://deepmap.ai/resource", body)
466+
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
467+
assert.False(t, called, "Handler should not have been called")
468+
called = false
469+
}
470+
437471
called = false
438472
// Send a good request body
439473
{

oapi_validate.go

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ func validateRequest(r *http.Request, router routers.Router, options *Options) (
8787
// Find route
8888
route, pathParams, err := router.FindRoute(r)
8989
if err != nil {
90+
if errors.Is(err, routers.ErrMethodNotAllowed) {
91+
return http.StatusMethodNotAllowed, err
92+
}
93+
9094
return http.StatusNotFound, err // We failed to find a matching route for the request.
9195
}
9296

oapi_validate_example_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,27 @@ components:
163163
logResponseBody(rr)
164164
fmt.Println()
165165

166+
// ================================================================================
167+
fmt.Println("# A request with an invalid HTTP method, to a valid path, is rejected with an HTTP 405 Method Not Allowed")
168+
body = map[string]string{
169+
"invalid": "not expected",
170+
}
171+
172+
data, err = json.Marshal(body)
173+
must(err)
174+
175+
req, err = http.NewRequest(http.MethodPatch, "/resource", bytes.NewReader(data))
176+
must(err)
177+
req.Header.Set("Content-Type", "application/json")
178+
179+
rr = httptest.NewRecorder()
180+
181+
server.ServeHTTP(rr, req)
182+
183+
fmt.Printf("Received an HTTP %d response. Expected HTTP 405\n", rr.Code)
184+
logResponseBody(rr)
185+
fmt.Println()
186+
166187
// ================================================================================
167188
fmt.Println("# A request that is well-formed is passed through to the Handler")
168189
body = map[string]string{
@@ -207,6 +228,10 @@ components:
207228
// Received an HTTP 400 response. Expected HTTP 400
208229
// Response body: request body has an error: doesn't match schema: property "invalid" is unsupported
209230
//
231+
// # A request with an invalid HTTP method, to a valid path, is rejected with an HTTP 405 Method Not Allowed
232+
// Received an HTTP 405 response. Expected HTTP 405
233+
// Response body: method not allowed
234+
//
210235
// # A request that is well-formed is passed through to the Handler
211236
// POST /resource was called
212237
// Received an HTTP 204 response. Expected HTTP 204

0 commit comments

Comments
 (0)