From 58055c37297d780f65a8722b1c121f3a12c84960 Mon Sep 17 00:00:00 2001 From: Florent CHAUVEAU Date: Sun, 7 Apr 2024 13:20:51 +0200 Subject: [PATCH 1/3] Added tests for issue #17 --- oapi_validate_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++ test_spec.yaml | 38 +++++++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/oapi_validate_test.go b/oapi_validate_test.go index 8bc6a11..2d7e29f 100644 --- a/oapi_validate_test.go +++ b/oapi_validate_test.go @@ -129,6 +129,18 @@ func TestOapiRequestValidator(t *testing.T) { called = true return nil }) + // add a Handler for an encoded path parameter + // this needs to be installed before calling the first doGet + // because of echo internals (maxParam) + e.GET("/resource/maxlength/:encoded", func(c echo.Context) error { + called = true + return c.NoContent(http.StatusNoContent) + }) + e.GET("/resource/pattern/:encoded", func(c echo.Context) error { + called = true + return c.NoContent(http.StatusNoContent) + }) + // Let's send the request to the wrong server, this should return 404 { rec := doGet(t, e, "http://not.deepmap.ai/resource") @@ -231,6 +243,43 @@ func TestOapiRequestValidator(t *testing.T) { assert.False(t, called, "Handler should not have been called") called = false } + + // Let's send a request with an encoded parameter + // It should pass validation even though the parameter is encoded + // to 3 chars and the parameter is limited to maxLength: 1 + { + rec := doGet(t, e, "http://deepmap.ai/resource/maxlength/%2B") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Let's send a request with an unencoded parameter + // It should pass as well + { + rec := doGet(t, e, "http://deepmap.ai/resource/maxlength/+") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Let's send a request with an encoded parameter + // It should pass validation + { + rec := doGet(t, e, "http://deepmap.ai/resource/pattern/%2B1234") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Let's send a request with an unencoded parameter + // It should pass as well + { + rec := doGet(t, e, "http://deepmap.ai/resource/pattern/+1234") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } } func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { diff --git a/test_spec.yaml b/test_spec.yaml index 1f847d7..56af6a4 100644 --- a/test_spec.yaml +++ b/test_spec.yaml @@ -16,7 +16,7 @@ paths: minimum: 10 maximum: 100 responses: - '200': + "200": description: success content: application/json: @@ -29,7 +29,7 @@ paths: post: operationId: createResource responses: - '204': + "204": description: No content requestBody: required: true @@ -39,6 +39,32 @@ paths: properties: name: type: string + /resource/maxlength/{param}: + get: + operationId: getMaxLengthResourceParameter + parameters: + - name: param + in: path + required: true + schema: + type: string + maxLength: 1 + responses: + "204": + description: success + /resource/pattern/{param}: + get: + operationId: getPatternResourceParameter + parameters: + - name: param + in: path + required: true + schema: + type: string + pattern: '^\+[1-9]+$' + responses: + "204": + description: success /protected_resource: get: operationId: getProtectedResource @@ -46,7 +72,7 @@ paths: - BearerAuth: - someScope responses: - '204': + "204": description: no content /protected_resource2: get: @@ -55,7 +81,7 @@ paths: - BearerAuth: - otherScope responses: - '204': + "204": description: no content /protected_resource_401: get: @@ -64,7 +90,7 @@ paths: - BearerAuth: - unauthorized responses: - '401': + "401": description: no content /multiparamresource: get: @@ -85,7 +111,7 @@ paths: minimum: 10 maximum: 100 responses: - '200': + "200": description: success content: application/json: From 9f0f8589ad77ef7aaf6df4bc5fed7b3eee6bb35b Mon Sep 17 00:00:00 2001 From: Florent CHAUVEAU Date: Sun, 7 Apr 2024 13:21:35 +0200 Subject: [PATCH 2/3] Fixes #17 Parameters should be unscaped before being sent for validation. gorillamux returns escaped parameters, but openapi3filter expects unescaped parameters. --- oapi_validate.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/oapi_validate.go b/oapi_validate.go index 11dd1eb..907855b 100644 --- a/oapi_validate.go +++ b/oapi_validate.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "net/http" + "net/url" "os" "strings" @@ -128,6 +129,14 @@ func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options } } + // because gorillamux.NewRouter() uses mux.NewRouter().UseEncodedPath() + // we need to unescape the path parameters for openapi3filter + for k, v := range pathParams { + if v, err := url.PathUnescape(v); err == nil { + pathParams[k] = v + } + } + validationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, From e592981d9d8b47963248f3be5fb2858ef4682982 Mon Sep 17 00:00:00 2001 From: Florent CHAUVEAU Date: Sun, 5 Jan 2025 14:46:24 +0100 Subject: [PATCH 3/3] Fixes #12 return http.StatusMethodNotAllowed when appropriate --- oapi_validate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oapi_validate.go b/oapi_validate.go index 907855b..f937ae1 100644 --- a/oapi_validate.go +++ b/oapi_validate.go @@ -118,6 +118,10 @@ func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options if err != nil { switch e := err.(type) { case *routers.RouteError: + // The path may exist on the server, but the method is not allowed. + if errors.Is(e, routers.ErrMethodNotAllowed) { + return echo.NewHTTPError(http.StatusMethodNotAllowed, e.Reason) + } // We've got a bad request, the path requested doesn't match // either server, or path, or something. return echo.NewHTTPError(http.StatusNotFound, e.Reason)