Repository: deepmap/oapi-codegen
Pull request #699 (2022-07-29 08:32:42)

Correct error message for new flag

jamietanna: Happy to ignore this in lieu of #700 

Patch:
diff --git a/README.md b/README.md
index 3eadd00..0e19e73 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
 OpenAPI Client and Server Code Generator
 ----------------------------------------
 
+⚠️ 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.
+
 This package contains a set of utilities for generating Go boilerplate code for
 services based on
 [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
@@ -645,7 +649,9 @@ you can specify any combination of those.
  that produced by the `types` target.
 - `client`: generate the client boilerplate. It, too, requires the types to be
  present in its package.
-- `spec`: embed the OpenAPI spec into the generated code as a gzipped blob. This
+- `spec`: embed the OpenAPI spec into the generated code as a gzipped blob.
+  This is then usable with the `OapiRequestValidator`, or to be used by other
+  methods that need access to the parsed OpenAPI specification
 - `skip-fmt`: skip running `goimports` on the generated code. This is useful for debugging
  the generated file in case the spec contains weird strings.
 - `skip-prune`: skip pruning unused components from the spec prior to generating
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index 5ad3d32..da9f504 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -258,7 +258,7 @@ func updateConfigFromFlags(cfg configuration) (configuration, error) {
 
 	if len(unsupportedFlags) > 0 {
 		return configuration{}, fmt.Errorf("flags %s aren't supported in "+
-			"new config style, please use  -old-config-style or update your configuration ",
+			"new config style, please use --old-config-style or update your configuration ",
 			strings.Join(unsupportedFlags, ", "))
 	}
 
diff --git a/pkg/chi-middleware/oapi_validate.go b/pkg/chi-middleware/oapi_validate.go
index dfce720..e230239 100644
--- a/pkg/chi-middleware/oapi_validate.go
+++ b/pkg/chi-middleware/oapi_validate.go
@@ -5,6 +5,7 @@ package middleware
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -18,10 +19,14 @@ import (
 // ErrorHandler is called when there is an error in validation
 type ErrorHandler func(w http.ResponseWriter, message string, statusCode int)
 
+// MultiErrorHandler is called when oapi returns a MultiError type
+type MultiErrorHandler func(openapi3.MultiError) (int, error)
+
 // Options to customize request validation, openapi3filter specified options will be passed through.
 type Options struct {
-	Options      openapi3filter.Options
-	ErrorHandler ErrorHandler
+	Options           openapi3filter.Options
+	ErrorHandler      ErrorHandler
+	MultiErrorHandler MultiErrorHandler
 }
 
 // OapiRequestValidator Creates middleware to validate request by swagger spec.
@@ -80,6 +85,12 @@ func validateRequest(r *http.Request, router routers.Router, options *Options) (
 	}
 
 	if err := openapi3filter.ValidateRequest(context.Background(), requestValidationInput); err != nil {
+		me := openapi3.MultiError{}
+		if errors.As(err, &me) {
+			errFunc := getMultiErrorHandlerFromOptions(options)
+			return errFunc(me)
+		}
+
 		switch e := err.(type) {
 		case *openapi3filter.RequestError:
 			// We've got a bad request
@@ -98,3 +109,24 @@ func validateRequest(r *http.Request, router routers.Router, options *Options) (
 
 	return http.StatusOK, nil
 }
+
+// attempt to get the MultiErrorHandler from the options. If it is not set,
+// return a default handler
+func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler {
+	if options == nil {
+		return defaultMultiErrorHandler
+	}
+
+	if options.MultiErrorHandler == nil {
+		return defaultMultiErrorHandler
+	}
+
+	return options.MultiErrorHandler
+}
+
+// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list
+// of all of the errors. This method is called if there are no other
+// methods defined on the options.
+func defaultMultiErrorHandler(me openapi3.MultiError) (int, error) {
+	return http.StatusBadRequest, me
+}
diff --git a/pkg/gin-middleware/oapi_validate.go b/pkg/gin-middleware/oapi_validate.go
index 1f1687d..b165fe0 100644
--- a/pkg/gin-middleware/oapi_validate.go
+++ b/pkg/gin-middleware/oapi_validate.go
@@ -59,13 +59,17 @@ func OapiRequestValidator(swagger *openapi3.T) gin.HandlerFunc {
 // ErrorHandler is called when there is an error in validation
 type ErrorHandler func(c *gin.Context, message string, statusCode int)
 
+// MultiErrorHandler is called when oapi returns a MultiError type
+type MultiErrorHandler func(openapi3.MultiError) error
+
 // Options to customize request validation. These are passed through to
 // openapi3filter.
 type Options struct {
-	ErrorHandler ErrorHandler
-	Options      openapi3filter.Options
-	ParamDecoder openapi3filter.ContentParameterDecoder
-	UserData     interface{}
+	ErrorHandler      ErrorHandler
+	Options           openapi3filter.Options
+	ParamDecoder      openapi3filter.ContentParameterDecoder
+	UserData          interface{}
+	MultiErrorHandler MultiErrorHandler
 }
 
 // Create a validator from a swagger object, with validation options
@@ -128,6 +132,12 @@ func ValidateRequestFromContext(c *gin.Context, router routers.Router, options *
 
 	err = openapi3filter.ValidateRequest(requestContext, validationInput)
 	if err != nil {
+		me := openapi3.MultiError{}
+		if errors.As(err, &me) {
+			errFunc := getMultiErrorHandlerFromOptions(options)
+			return errFunc(me)
+		}
+
 		switch e := err.(type) {
 		case *openapi3filter.RequestError:
 			// We've got a bad request
@@ -163,3 +173,24 @@ func GetGinContext(c context.Context) *gin.Context {
 func GetUserData(c context.Context) interface{} {
 	return c.Value(UserDataKey)
 }
+
+// attempt to get the MultiErrorHandler from the options. If it is not set,
+// return a default handler
+func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler {
+	if options == nil {
+		return defaultMultiErrorHandler
+	}
+
+	if options.MultiErrorHandler == nil {
+		return defaultMultiErrorHandler
+	}
+
+	return options.MultiErrorHandler
+}
+
+// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list
+// of all of the errors. This method is called if there are no other
+// methods defined on the options.
+func defaultMultiErrorHandler(me openapi3.MultiError) error {
+	return fmt.Errorf("multiple errors encountered: %s", me)
+}
diff --git a/pkg/middleware/oapi_validate.go b/pkg/middleware/oapi_validate.go
index b0bde5d..00ce1da 100644
--- a/pkg/middleware/oapi_validate.go
+++ b/pkg/middleware/oapi_validate.go
@@ -16,6 +16,7 @@ package middleware
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -61,14 +62,18 @@ func OapiRequestValidator(swagger *openapi3.T) echo.MiddlewareFunc {
 // 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
+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      ErrorHandler
+	Options           openapi3filter.Options
+	ParamDecoder      openapi3filter.ContentParameterDecoder
+	UserData          interface{}
+	Skipper           echomiddleware.Skipper
+	MultiErrorHandler MultiErrorHandler
 }
 
 // OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options
@@ -136,6 +141,12 @@ func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options
 
 	err = openapi3filter.ValidateRequest(requestContext, validationInput)
 	if err != nil {
+		me := openapi3.MultiError{}
+		if errors.As(err, &me) {
+			errFunc := getMultiErrorHandlerFromOptions(options)
+			return errFunc(me)
+		}
+
 		switch e := err.(type) {
 		case *openapi3filter.RequestError:
 			// We've got a bad request
@@ -202,3 +213,28 @@ func getSkipperFromOptions(options *Options) echomiddleware.Skipper {
 
 	return options.Skipper
 }
+
+// attempt to get the MultiErrorHandler from the options. If it is not set,
+// return a default handler
+func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler {
+	if options == nil {
+		return defaultMultiErrorHandler
+	}
+
+	if options.MultiErrorHandler == nil {
+		return defaultMultiErrorHandler
+	}
+
+	return options.MultiErrorHandler
+}
+
+// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list
+// of all of the errors. This method is called if there are no other
+// methods defined on the options.
+func defaultMultiErrorHandler(me openapi3.MultiError) *echo.HTTPError {
+	return &echo.HTTPError{
+		Code:     http.StatusBadRequest,
+		Message:  me.Error(),
+		Internal: me,
+	}
+}


Test patch:
diff --git a/pkg/chi-middleware/oapi_validate_test.go b/pkg/chi-middleware/oapi_validate_test.go
index 8df1a2e..4d3c925 100644
--- a/pkg/chi-middleware/oapi_validate_test.go
+++ b/pkg/chi-middleware/oapi_validate_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	_ "embed"
 	"errors"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -54,6 +55,207 @@ func TestOapiRequestValidator(t *testing.T) {
 	testRequestValidatorBasicFunctions(t, r)
 }
 
+func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	r := chi.NewRouter()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+	}
+
+	// register middleware
+	r.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	r.Get("/multiparamresource", func(w http.ResponseWriter, r *http.Request) {
+		called = true
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "parsing \"abc\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
+
+func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	r := chi.NewRouter()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+		MultiErrorHandler: func(me openapi3.MultiError) (int, error) {
+			return http.StatusTeapot, me
+		},
+	}
+
+	// register middleware
+	r.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	r.Get("/multiparamresource", func(w http.ResponseWriter, r *http.Request) {
+		called = true
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \"id\"")
+			assert.Contains(t, string(body), "parsing \"abc\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \"id2\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
+
 func TestOapiRequestValidatorWithOptions(t *testing.T) {
 	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
 	require.NoError(t, err, "Error initializing swagger")
diff --git a/pkg/chi-middleware/test_spec.yaml b/pkg/chi-middleware/test_spec.yaml
index f2ce175..3a7b169 100644
--- a/pkg/chi-middleware/test_spec.yaml
+++ b/pkg/chi-middleware/test_spec.yaml
@@ -66,6 +66,35 @@ paths:
       responses:
         '401':
           description: no content
+  /multiparamresource:
+    get:
+      operationId: getResource
+      parameters:
+        - name: id
+          in: query
+          required: true
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+        - name: id2
+          required: true
+          in: query
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+      responses:
+        '200':
+            description: success
+            content:
+              application/json:
+                schema:
+                  properties:
+                    name:
+                      type: string
+                    id:
+                      type: integer
 components:
   securitySchemes:
     BearerAuth:
diff --git a/pkg/gin-middleware/oapi_validate_test.go b/pkg/gin-middleware/oapi_validate_test.go
index 7373071..2d0f1d3 100644
--- a/pkg/gin-middleware/oapi_validate_test.go
+++ b/pkg/gin-middleware/oapi_validate_test.go
@@ -18,6 +18,8 @@ import (
 	"context"
 	_ "embed"
 	"errors"
+	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -202,3 +204,212 @@ func TestOapiRequestValidator(t *testing.T) {
 		called = false
 	}
 }
+
+func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	g := gin.New()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+	}
+
+	// register middleware
+	g.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	g.GET("/multiparamresource", func(c *gin.Context) {
+		called = true
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "multiple errors encountered")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "multiple errors encountered")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "multiple errors encountered")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "multiple errors encountered")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "parsing \\\"abc\\\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
+
+func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	g := gin.New()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+		MultiErrorHandler: func(me openapi3.MultiError) error {
+			return fmt.Errorf("Bad stuff -  %s", me.Error())
+		},
+	}
+
+	// register middleware
+	g.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	g.GET("/multiparamresource", func(c *gin.Context) {
+		called = true
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "Bad stuff")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "Bad stuff")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "Bad stuff")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, g, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "Bad stuff")
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "parsing \\\"abc\\\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
diff --git a/pkg/gin-middleware/test_spec.yaml b/pkg/gin-middleware/test_spec.yaml
index f2ce175..3a7b169 100644
--- a/pkg/gin-middleware/test_spec.yaml
+++ b/pkg/gin-middleware/test_spec.yaml
@@ -66,6 +66,35 @@ paths:
       responses:
         '401':
           description: no content
+  /multiparamresource:
+    get:
+      operationId: getResource
+      parameters:
+        - name: id
+          in: query
+          required: true
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+        - name: id2
+          required: true
+          in: query
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+      responses:
+        '200':
+            description: success
+            content:
+              application/json:
+                schema:
+                  properties:
+                    name:
+                      type: string
+                    id:
+                      type: integer
 components:
   securitySchemes:
     BearerAuth:
diff --git a/pkg/middleware/oapi_validate_test.go b/pkg/middleware/oapi_validate_test.go
index 5c3989b..80a221d 100644
--- a/pkg/middleware/oapi_validate_test.go
+++ b/pkg/middleware/oapi_validate_test.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	_ "embed"
 	"errors"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -206,6 +207,215 @@ func TestOapiRequestValidator(t *testing.T) {
 	}
 }
 
+func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	// Create a new echo router
+	e := echo.New()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+	}
+
+	// register middleware
+	e.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	e.GET("/multiparamresource", func(c echo.Context) error {
+		called = true
+		return nil
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "parsing \\\"abc\\\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
+
+func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) {
+	swagger, err := openapi3.NewLoader().LoadFromData([]byte(testSchema))
+	require.NoError(t, err, "Error initializing swagger")
+
+	// Create a new echo router
+	e := echo.New()
+
+	// Set up an authenticator to check authenticated function. It will allow
+	// access to "someScope", but disallow others.
+	options := Options{
+		Options: openapi3filter.Options{
+			ExcludeRequestBody:    false,
+			ExcludeResponseBody:   false,
+			IncludeResponseStatus: true,
+			MultiError:            true,
+		},
+		MultiErrorHandler: func(me openapi3.MultiError) *echo.HTTPError {
+			return &echo.HTTPError{
+				Code:     http.StatusTeapot,
+				Message:  me.Error(),
+				Internal: me,
+			}
+		},
+	}
+
+	// register middleware
+	e.Use(OapiRequestValidatorWithOptions(swagger, &options))
+
+	called := false
+
+	// Install a request handler for /resource. We want to make sure it doesn't
+	// get called.
+	e.GET("/multiparamresource", func(c echo.Context) error {
+		called = true
+		return nil
+	})
+
+	// Let's send a good request, it should pass
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50&id2=50")
+		assert.Equal(t, http.StatusOK, rec.Code)
+		assert.True(t, called, "Handler should have been called")
+		called = false
+	}
+
+	// Let's send a request with a missing parameter, it should return
+	// a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=50")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 2 missing parameters, it should return
+	// a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a 1 missing parameter, and another outside
+	// or the parameters. It should return a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=500")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "number must be at most 100")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "value is required but missing")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+
+	// Let's send a request with a parameters that do not meet spec. It should
+	// return a bad status
+	{
+		rec := doGet(t, e, "http://deepmap.ai/multiparamresource?id=abc&id2=1")
+		assert.Equal(t, http.StatusTeapot, rec.Code)
+		body, err := ioutil.ReadAll(rec.Body)
+		if assert.NoError(t, err) {
+			assert.Contains(t, string(body), "parameter \\\"id\\\"")
+			assert.Contains(t, string(body), "parsing \\\"abc\\\": invalid syntax")
+			assert.Contains(t, string(body), "parameter \\\"id2\\\"")
+			assert.Contains(t, string(body), "number must be at least 10")
+		}
+		assert.False(t, called, "Handler should not have been called")
+		called = false
+	}
+}
+
 func TestGetSkipperFromOptions(t *testing.T) {
 
 	options := new(Options)
diff --git a/pkg/middleware/test_spec.yaml b/pkg/middleware/test_spec.yaml
index a279068..7c8ea6a 100644
--- a/pkg/middleware/test_spec.yaml
+++ b/pkg/middleware/test_spec.yaml
@@ -66,6 +66,35 @@ paths:
       responses:
         '401':
           description: no content
+  /multiparamresource:
+    get:
+      operationId: getResource
+      parameters:
+        - name: id
+          in: query
+          required: true
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+        - name: id2
+          required: true
+          in: query
+          schema:
+            type: integer
+            minimum: 10
+            maximum: 100
+      responses:
+        '200':
+            description: success
+            content:
+              application/json:
+                schema:
+                  properties:
+                    name:
+                      type: string
+                    id:
+                      type: integer
 components:
   securitySchemes:
     BearerAuth: