diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3467674..8e3251f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,14 @@ jobs: # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go matrix: version: + # versions of Go that this module can still be built with (and therefore are "supported" by this project) - "1.20" - "1.21" - "1.22" + - "1.23" + # actively supported versions of Go + - "1.24" + - "1.25" steps: - name: Check out source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 1552de8..797118f 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -9,9 +9,14 @@ jobs: # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go matrix: version: + # versions of Go that this module can still be built with (and therefore are "supported" by this project) - "1.20" - "1.21" - "1.22" + - "1.23" + # actively supported versions of Go + - "1.24" + - "1.25" steps: - name: Check out source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 33d68cf..bb3c1b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,9 +9,14 @@ jobs: # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go matrix: version: + # versions of Go that this module can still be built with (and therefore are "supported" by this project) - "1.20" - "1.21" - "1.22" + - "1.23" + # actively supported versions of Go + - "1.24" + - "1.25" steps: - name: Check out source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml index fff71a2..d114b86 100644 --- a/.github/workflows/tidy.yml +++ b/.github/workflows/tidy.yml @@ -9,9 +9,14 @@ jobs: # perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go matrix: version: + # versions of Go that this module can still be built with (and therefore are "supported" by this project) - "1.20" - "1.21" - "1.22" + - "1.23" + # actively supported versions of Go + - "1.24" + - "1.25" steps: - name: Check out source code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/README.md b/README.md index a2077ed..ed1e00e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,77 @@ -# Echo Middleware +# OpenAPI Validation Middleware for `labstack/echo` servers + +Middleware for the [Echo web server](https://github.com/labstack/echo) to perform validation of incoming requests via an OpenAPI specification. + +This project is a lightweight wrapper over the excellent [kin-openapi](https://github.com/getkin/kin-openapi) library's [`openapi3filter` package](https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3filter). + +This is _intended_ to be used with code that's generated through [`oapi-codegen`](https://github.com/oapi-codegen/oapi-codegen), but should work otherwise. ⚠️ This README may be for the latest development version, which may contain unreleased changes. Please ensure you're looking at the README for the latest release version. -Middleware for the [Echo web server](https://github.com/labstack/echo) for use with [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen). +## Usage + +You can add the middleware to your project with: + +```sh +go get github.com/oapi-codegen/echo-middleware +``` + +There is a full example of usage in [the Go doc for this project](https://pkg.go.dev/github.com/oapi-codegen/echo-middleware#pkg-examples). + +A simplified version of this code is as follows: + +```go +rawSpec := ` +openapi: "3.0.0" +# ... +` +spec, _ := openapi3.NewLoader().LoadFromData([]byte(rawSpec)) + +// NOTE that we need to make sure that the `Servers` aren't set, otherwise the OpenAPI validation middleware will validate that the `Host` header (of incoming requests) are targeting known `Servers` in the OpenAPI spec +// See also: Options#SilenceServersWarning +spec.Servers = nil + +e := echo.New() +e.POST("/resource", func(c echo.Context) error { + fmt.Printf("%s /resource was called\n", c.Request().Method) + + return c.NoContent(http.StatusNoContent) +}) + +// create middleware +mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: authenticationFunc, + }, +}) + +e.Use(mw) + +// now all HTTP routes will be handled by the middleware, and any requests that are invalid will be rejected +``` + +## FAQs + +### What versions of Echo does this support? + +| Version | Supported? | +| -- | -- | +| `github.com/labstack/echo/v4` | ✅ | +| `github.com/labstack/echo/v5` | ❌ [Issue](https://github.com/oapi-codegen/echo-middleware/issues/37) | + + +### "This doesn't support ..." / "I think it's a bug that ..." + +As this project is a lightweight wrapper over [kin-openapi](https://github.com/getkin/kin-openapi)'s [`openapi3filter` package](https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3filter), it's _likely_ that any bugs/features are better sent upstream. + +However, it's worth raising an issue here instead, as it'll allow us to triage it before it goes to the kin-openapi maintainers. + +Additionally, as `oapi-codegen` contains [a number of middleware modules](https://github.com/search?q=org%3Aoapi-codegen+middleware&type=repositories), we'll very likely want to implement the same functionality across all the middlewares, so it may take a bit more coordination to get the changes in across our middlewares. + +### I've just updated my version of `kin-openapi`, and now I can't build my code 😠 + +The [kin-openapi](https://github.com/getkin/kin-openapi) project - which we 💜 for providing a great library and set of tooling for interacting with OpenAPI - is a pre-v1 release, which means that they're within their rights to push breaking changes. + +This may lead to breakage in your consuming code, and if so, sorry that's happened! -Licensed under the Apache-2.0. +We'll be aware of the issue, and will work to update both the core `oapi-codegen` and the middlewares accordingly. diff --git a/oapi_validate.go b/oapi_validate.go index 11dd1eb..ef74bfa 100644 --- a/oapi_validate.go +++ b/oapi_validate.go @@ -1,17 +1,10 @@ -// Copyright 2019 DeepMap, Inc. +// Provide HTTP middleware functionality to validate that incoming requests conform to a given OpenAPI 3.x specification. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// This provides middleware for an echo/v4 HTTP server. // -// http://www.apache.org/licenses/LICENSE-2.0 +// This package is a lightweight wrapper over https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3filter from https://pkg.go.dev/github.com/getkin/kin-openapi. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// This is _intended_ to be used with code that's generated through https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen, but should work otherwise. package echomiddleware import ( @@ -46,44 +39,66 @@ func OapiValidatorFromYamlFile(path string) (echo.MiddlewareFunc, error) { return nil, fmt.Errorf("error reading %s: %w", path, err) } - swagger, err := openapi3.NewLoader().LoadFromData(data) + spec, err := openapi3.NewLoader().LoadFromData(data) if err != nil { - return nil, fmt.Errorf("error parsing %s as Swagger YAML: %w", path, err) + return nil, fmt.Errorf("error parsing %s as OpenAPI YAML: %w", path, err) } - return OapiRequestValidator(swagger), nil + return OapiRequestValidator(spec), nil } -// OapiRequestValidator creates a validator from a swagger object. -func OapiRequestValidator(swagger *openapi3.T) echo.MiddlewareFunc { - return OapiRequestValidatorWithOptions(swagger, nil) +// OapiRequestValidator Creates the middleware to validate that incoming requests match the given OpenAPI 3.x spec, with a default set of configuration. +func OapiRequestValidator(spec *openapi3.T) echo.MiddlewareFunc { + return OapiRequestValidatorWithOptions(spec, nil) } // ErrorHandler is called when there is an error in validation type ErrorHandler func(c echo.Context, err *echo.HTTPError) error -// MultiErrorHandler is called when oapi returns a MultiError type +// MultiErrorHandler is called when the OpenAPI filter returns an openapi3.MultiError (https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3#MultiError) type MultiErrorHandler func(openapi3.MultiError) *echo.HTTPError // Options to customize request validation. These are passed through to // openapi3filter. type Options struct { - ErrorHandler ErrorHandler - Options openapi3filter.Options - ParamDecoder openapi3filter.ContentParameterDecoder - UserData interface{} - Skipper echomiddleware.Skipper + // ErrorHandler is called when a validation error occurs. + // + // If not provided, `http.Error` will be called + ErrorHandler ErrorHandler + // Options contains any configuration for the underlying `openapi3filter` + Options openapi3filter.Options + // ParamDecoder is the openapi3filter.ContentParameterDecoder to be used for the decoding of the request body + // + // If unset, a default will be used + ParamDecoder openapi3filter.ContentParameterDecoder + // UserData is any user-specified data to inject into the context.Context, which is then passed in to the validation function. + // + // Set on the Context with the key `UserDataKey`. + UserData any + // Skipper an echo Skipper to allow skipping the middleware. + Skipper echomiddleware.Skipper + // MultiErrorHandler is called when there is an openapi3.MultiError (https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3#MultiError) returned by the `openapi3filter`. + // + // If not provided `defaultMultiErrorHandler` will be used. MultiErrorHandler MultiErrorHandler - // SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` + // SilenceServersWarning allows silencing a warning for https://github.com/oapi-codegen/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` SilenceServersWarning bool + // DoNotValidateServers ensures that there is no Host validation performed (see `SilenceServersWarning` and https://github.com/deepmap/oapi-codegen/issues/882 for more details) + DoNotValidateServers bool } -// OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options -func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) echo.MiddlewareFunc { - if swagger.Servers != nil && (options == nil || !options.SilenceServersWarning) { - log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.") +// OapiRequestValidatorWithOptions Creates the middleware to validate that incoming requests match the given OpenAPI 3.x spec, allowing explicit configuration. +// +// NOTE that this may panic if the OpenAPI spec isn't valid, or if it cannot be used to create the middleware +func OapiRequestValidatorWithOptions(spec *openapi3.T, options *Options) echo.MiddlewareFunc { + if options != nil && options.DoNotValidateServers { + spec.Servers = nil + } + + if spec.Servers != nil && (options == nil || !options.SilenceServersWarning) { + log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/oapi-codegen/oapi-codegen/issues/882 for more information.") } - router, err := gorillamux.NewRouter(swagger) + router, err := gorillamux.NewRouter(spec) if err != nil { panic(err) } @@ -115,6 +130,10 @@ func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options // We failed to find a matching route for the request. if err != nil { + if errors.Is(err, routers.ErrMethodNotAllowed) { + return echo.NewHTTPError(http.StatusMethodNotAllowed) + } + switch e := err.(type) { case *routers.RouteError: // We've got a bad request, the path requested doesn't match @@ -202,7 +221,7 @@ func GetEchoContext(c context.Context) echo.Context { return eCtx } -func GetUserData(c context.Context) interface{} { +func GetUserData(c context.Context) any { return c.Value(UserDataKey) } diff --git a/oapi_validate_example_test.go b/oapi_validate_example_test.go new file mode 100644 index 0000000..c226417 --- /dev/null +++ b/oapi_validate_example_test.go @@ -0,0 +1,310 @@ +package echomiddleware_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/labstack/echo/v4" + middleware "github.com/oapi-codegen/echo-middleware" +) + +func ExampleOapiRequestValidatorWithOptions() { + rawSpec := ` +openapi: "3.0.0" +info: + version: 1.0.0 + title: TestServer +servers: + - url: http://example.com/ +paths: + /resource: + post: + operationId: createResource + responses: + '204': + description: No content + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + additionalProperties: false + /protected_resource: + get: + operationId: getProtectedResource + security: + - BearerAuth: + - someScope + responses: + '204': + description: no content +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT +` + + must := func(err error) { + if err != nil { + panic(err) + } + } + + logResponseBody := func(rr *httptest.ResponseRecorder) { + if rr.Result().Body != nil { + data, _ := io.ReadAll(rr.Result().Body) + if len(data) > 0 { + fmt.Printf("Response body: %s", data) + } + } + } + + spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec)) + must(err) + + // NOTE that we need to make sure that the `Servers` aren't set, otherwise the OpenAPI validation middleware will validate that the `Host` header (of incoming requests) are targeting known `Servers` in the OpenAPI spec + // See also: Options#SilenceServersWarning + spec.Servers = nil + + e := echo.New() + e.POST("/resource", func(c echo.Context) error { + fmt.Printf("%s /resource was called\n", c.Request().Method) + + return c.NoContent(http.StatusNoContent) + }) + + e.GET("/protected_resource", func(c echo.Context) error { + // NOTE that we're setting up our `authenticationFunc` (below) to /never/ allow any requests in - so if we get a response from this endpoint, our `authenticationFunc` hasn't correctly worked + return c.NoContent(http.StatusNoContent) + }) + + authenticationFunc := func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error { + fmt.Printf("`AuthenticationFunc` was called for securitySchemeName=%s\n", ai.SecuritySchemeName) + return fmt.Errorf("this check always fails - don't let anyone in!") + } + + // create middleware + mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: authenticationFunc, + }, + }) + + e.Use(mw) + + // ================================================================================ + fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with no request body)") + + req, err := http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(nil)) + must(err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code) + logResponseBody(rr) + fmt.Println() + + // ================================================================================ + fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (because an invalid property is sent, and we have `additionalProperties: false`)") + body := map[string]string{ + "invalid": "not expected", + } + + data, err := json.Marshal(body) + must(err) + + req, err = http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(data)) + must(err) + req.Header.Set("Content-Type", "application/json") + + rr = httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code) + logResponseBody(rr) + fmt.Println() + + // ================================================================================ + fmt.Println("# A request that uses the wrong HTTP method is rejected with HTTP 405 Method Not Allowed") + req, err = http.NewRequest(http.MethodDelete, "/resource", bytes.NewReader(data)) + must(err) + req.Header.Set("Content-Type", "application/json") + + rr = httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 405\n", rr.Code) + logResponseBody(rr) + fmt.Println() + + // ================================================================================ + fmt.Println("# A request that is well-formed is passed through to the Handler") + body = map[string]string{ + "name": "Jamie", + } + + data, err = json.Marshal(body) + must(err) + + req, err = http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(data)) + must(err) + req.Header.Set("Content-Type", "application/json") + + rr = httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 204\n", rr.Code) + logResponseBody(rr) + fmt.Println() + + // ================================================================================ + fmt.Println("# A request to an authenticated endpoint must go through an `AuthenticationFunc`, and if it fails, an HTTP 403 is returned") + + req, err = http.NewRequest(http.MethodGet, "/protected_resource", nil) + must(err) + + rr = httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 403\n", rr.Code) + logResponseBody(rr) + fmt.Println() + + // Output: + // # A request that is malformed is rejected with HTTP 400 Bad Request (with no request body) + // Received an HTTP 400 response. Expected HTTP 400 + // Response body: {"message":"request body has an error: value is required but missing"} + // + // # A request that is malformed is rejected with HTTP 400 Bad Request (because an invalid property is sent, and we have `additionalProperties: false`) + // Received an HTTP 400 response. Expected HTTP 400 + // Response body: {"message":"request body has an error: doesn't match schema: property \"invalid\" is unsupported"} + // + // # A request that uses the wrong HTTP method is rejected with HTTP 405 Method Not Allowed + // Received an HTTP 405 response. Expected HTTP 405 + // Response body: {"message":"Method Not Allowed"} + // + // # A request that is well-formed is passed through to the Handler + // POST /resource was called + // Received an HTTP 204 response. Expected HTTP 204 + // + // # A request to an authenticated endpoint must go through an `AuthenticationFunc`, and if it fails, an HTTP 403 is returned + // `AuthenticationFunc` was called for securitySchemeName=BearerAuth + // Received an HTTP 403 response. Expected HTTP 403 + // Response body: {"message":"security requirements failed: this check always fails - don't let anyone in!"} +} + +func ExampleOapiRequestValidatorWithOptions_withErrorHandler() { + rawSpec := ` +openapi: "3.0.0" +info: + version: 1.0.0 + title: TestServer +servers: + - url: http://example.com/ +paths: + /resource: + post: + operationId: createResource + responses: + '204': + description: No content + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + additionalProperties: false +` + + must := func(err error) { + if err != nil { + panic(err) + } + } + + logResponseBody := func(rr *httptest.ResponseRecorder) { + if rr.Result().Body != nil { + data, _ := io.ReadAll(rr.Result().Body) + if len(data) > 0 { + fmt.Printf("Response body: %s", data) + } + } + } + + spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec)) + must(err) + + // NOTE that we need to make sure that the `Servers` aren't set, otherwise the OpenAPI validation middleware will validate that the `Host` header (of incoming requests) are targeting known `Servers` in the OpenAPI spec + // See also: Options#SilenceServersWarning + spec.Servers = nil + + e := echo.New() + e.POST("/resource", func(c echo.Context) error { + fmt.Printf("%s /resource was called\n", c.Request().Method) + + return c.NoContent(http.StatusNoContent) + }) + + authenticationFunc := func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error { + fmt.Printf("`AuthenticationFunc` was called for securitySchemeName=%s\n", ai.SecuritySchemeName) + return fmt.Errorf("this check always fails - don't let anyone in!") + } + + errorHandlerFunc := func(c echo.Context, err *echo.HTTPError) error { + fmt.Printf("ErrorHandler: An HTTP %d was returned by the middleware with error message: %s\n", err.Code, err.Message) + return c.String(err.Code, "This was rewritten by the ErrorHandler") + } + + // create middleware + mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: authenticationFunc, + }, + ErrorHandler: errorHandlerFunc, + }) + + // then wire it in + e.Use(mw) + + // ================================================================================ + fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandler") + + req, err := http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(nil)) + must(err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + + e.ServeHTTP(rr, req) + + fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code) + logResponseBody(rr) + + // Output: + // # A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandler + // ErrorHandler: An HTTP 400 was returned by the middleware with error message: request body has an error: value is required but missing + // Received an HTTP 400 response. Expected HTTP 400 + // Response body: This was rewritten by the ErrorHandler +} diff --git a/oapi_validate_test.go b/oapi_validate_test.go index 8bc6a11..c0895ab 100644 --- a/oapi_validate_test.go +++ b/oapi_validate_test.go @@ -1,17 +1,3 @@ -// Copyright 2019 DeepMap, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package echomiddleware import ( @@ -57,7 +43,7 @@ func doGet(t *testing.T, e *echo.Echo, rawURL string) *httptest.ResponseRecorder return tt } -func doPost(t *testing.T, e *echo.Echo, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { +func doPost(t *testing.T, e *echo.Echo, rawURL string, jsonBody any) *httptest.ResponseRecorder { u, err := url.Parse(rawURL) if err != nil { t.Fatalf("Invalid url: %s", rawURL) @@ -84,8 +70,8 @@ func doPost(t *testing.T, e *echo.Echo, rawURL string, jsonBody interface{}) *ht } func TestOapiRequestValidator(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") + spec, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing OpenAPI spec") // Create a new echo router e := echo.New() @@ -119,7 +105,7 @@ func TestOapiRequestValidator(t *testing.T) { } // Install our OpenApi based request validator - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) + e.Use(OapiRequestValidatorWithOptions(spec, &options)) called := false @@ -160,6 +146,14 @@ func TestOapiRequestValidator(t *testing.T) { called = false } + // Send a request with the wrong HTTP method + { + rec := doPost(t, e, "http://deepmap.ai/multiparamresource", nil) + assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + // Add a handler for the POST message e.POST("/resource", func(c echo.Context) error { called = true @@ -234,8 +228,8 @@ func TestOapiRequestValidator(t *testing.T) { } func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") + spec, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing OpenAPI spec") // Create a new echo router e := echo.New() @@ -252,7 +246,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { } // register middleware - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) + e.Use(OapiRequestValidatorWithOptions(spec, &options)) called := false @@ -335,8 +329,8 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { } func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") + spec, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing OpenAPI spec") // Create a new echo router e := echo.New() @@ -360,7 +354,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) } // register middleware - e.Use(OapiRequestValidatorWithOptions(swagger, &options)) + e.Use(OapiRequestValidatorWithOptions(spec, &options)) called := false