Repository: deepmap/oapi-codegen
Pull request #176 (2020-04-25 23:32:48)

feat: Add skipper using the echo convention to enable just that
The goal of this PR is to enable skipping validation for things like health checks and endpoints which are managed by other validation when adding this validator at the base path.

I have tried to follow the echo convention.

Cheers


Patch:
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index 36a1ac2..94c1aa4 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -222,6 +222,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
 				return Schema{}, errors.Wrap(err, "error generating type for array")
 			}
 			outSchema.GoType = "[]" + arrayType.TypeDecl()
+			outSchema.Properties = arrayType.Properties
 		case "integer":
 			// We default to int if format doesn't ask for something else.
 			if f == "int64" {
diff --git a/pkg/codegen/templates/chi-middleware.tmpl b/pkg/codegen/templates/chi-middleware.tmpl
index 68d8664..4ec3b39 100644
--- a/pkg/codegen/templates/chi-middleware.tmpl
+++ b/pkg/codegen/templates/chi-middleware.tmpl
@@ -113,7 +113,7 @@ func {{$opid}}Ctx(next http.Handler) http.Handler {
             params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
 
           } {{if .Required}}else {
-              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found", err), http.StatusBadRequest)
+              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest)
               return
           }{{end}}
 
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index a7d8eb4..04e7fc3 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -209,7 +209,7 @@ func {{$opid}}Ctx(next http.Handler) http.Handler {
             params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
 
           } {{if .Required}}else {
-              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found", err), http.StatusBadRequest)
+              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest)
               return
           }{{end}}
 
diff --git a/pkg/middleware/oapi_validate.go b/pkg/middleware/oapi_validate.go
index 7480936..b990333 100644
--- a/pkg/middleware/oapi_validate.go
+++ b/pkg/middleware/oapi_validate.go
@@ -24,6 +24,7 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/openapi3filter"
 	"github.com/labstack/echo/v4"
+	echomiddleware "github.com/labstack/echo/v4/middleware"
 )
 
 const EchoContextKey = "oapi-codegen/echo-context"
@@ -59,13 +60,19 @@ type Options struct {
 	Options      openapi3filter.Options
 	ParamDecoder openapi3filter.ContentParameterDecoder
 	UserData     interface{}
+	Skipper      echomiddleware.Skipper
 }
 
 // Create a validator from a swagger object, with validation options
 func OapiRequestValidatorWithOptions(swagger *openapi3.Swagger, options *Options) echo.MiddlewareFunc {
 	router := openapi3filter.NewRouter().WithSwagger(swagger)
+	skipper := getSkipperFromOptions(options)
 	return func(next echo.HandlerFunc) echo.HandlerFunc {
 		return func(c echo.Context) error {
+			if skipper(c) {
+				return next(c)
+			}
+
 			err := ValidateRequestFromContext(c, router, options)
 			if err != nil {
 				return err
@@ -167,3 +174,16 @@ func GetEchoContext(c context.Context) echo.Context {
 func GetUserData(c context.Context) interface{} {
 	return c.Value(UserDataKey)
 }
+
+// attempt to get the skipper from the options whether it is set or not
+func getSkipperFromOptions(options *Options) echomiddleware.Skipper {
+	if options == nil {
+		return echomiddleware.DefaultSkipper
+	}
+
+	if options.Skipper == nil {
+		return echomiddleware.DefaultSkipper
+	}
+
+	return options.Skipper
+}
diff --git a/pkg/securityprovider/securityprovider.go b/pkg/securityprovider/securityprovider.go
index 265d788..940fc5b 100644
--- a/pkg/securityprovider/securityprovider.go
+++ b/pkg/securityprovider/securityprovider.go
@@ -41,7 +41,7 @@ type SecurityProviderBasicAuth struct {
 
 // Intercept will attach an Authorization header to the request and ensures that
 // the username, password are base64 encoded and attached to the header.
-func (s *SecurityProviderBasicAuth) Intercept(req *http.Request, ctx context.Context) error {
+func (s *SecurityProviderBasicAuth) Intercept(ctx context.Context, req *http.Request) error {
 	req.SetBasicAuth(s.username, s.password)
 	return nil
 }
@@ -62,7 +62,7 @@ type SecurityProviderBearerToken struct {
 
 // Intercept will attach an Authorization header to the request
 // and ensures that the bearer token is attached to the header.
-func (s *SecurityProviderBearerToken) Intercept(req *http.Request, ctx context.Context) error {
+func (s *SecurityProviderBearerToken) Intercept(ctx context.Context, req *http.Request) error {
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.token))
 	return nil
 }
@@ -70,16 +70,16 @@ func (s *SecurityProviderBearerToken) Intercept(req *http.Request, ctx context.C
 // NewSecurityProviderApiKey will attach a generic apiKey for a given name
 // either to a cookie, header or as a query parameter.
 func NewSecurityProviderApiKey(in, name, apiKey string) (*SecurityProviderApiKey, error) {
-	interceptors := map[string]func(req *http.Request, ctx context.Context) error{
-		"cookie": func(req *http.Request, ctx context.Context) error {
+	interceptors := map[string]func(ctx context.Context, req *http.Request) error{
+		"cookie": func(ctx context.Context, req *http.Request) error {
 			req.AddCookie(&http.Cookie{Name: name, Value: apiKey})
 			return nil
 		},
-		"header": func(req *http.Request, ctx context.Context) error {
+		"header": func(ctx context.Context, req *http.Request) error {
 			req.Header.Add(name, apiKey)
 			return nil
 		},
-		"query": func(req *http.Request, ctx context.Context) error {
+		"query": func(ctx context.Context, req *http.Request) error {
 			query := req.URL.Query()
 			query.Add(name, apiKey)
 			req.URL.RawQuery = query.Encode()
@@ -100,11 +100,11 @@ func NewSecurityProviderApiKey(in, name, apiKey string) (*SecurityProviderApiKey
 // SecurityProviderApiKey will attach an apiKey either to a
 // cookie, header or query.
 type SecurityProviderApiKey struct {
-	interceptor func(req *http.Request, ctx context.Context) error
+	interceptor func(ctx context.Context, req *http.Request) error
 }
 
 // Intercept will attach a cookie, header or query param for the configured
 // name and apiKey.
-func (s *SecurityProviderApiKey) Intercept(req *http.Request, ctx context.Context) error {
-	return s.interceptor(req, ctx)
+func (s *SecurityProviderApiKey) Intercept(ctx context.Context, req *http.Request) error {
+	return s.interceptor(ctx, req)
 }


Test patch:
diff --git a/pkg/middleware/oapi_validate_test.go b/pkg/middleware/oapi_validate_test.go
index 4320986..50b9e2c 100644
--- a/pkg/middleware/oapi_validate_test.go
+++ b/pkg/middleware/oapi_validate_test.go
@@ -21,12 +21,12 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/deepmap/oapi-codegen/pkg/testutil"
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/openapi3filter"
 	"github.com/labstack/echo/v4"
+	echomiddleware "github.com/labstack/echo/v4/middleware"
 	"github.com/stretchr/testify/assert"
-
-	"github.com/deepmap/oapi-codegen/pkg/testutil"
 )
 
 var testSchema = `openapi: "3.0.0"
@@ -259,3 +259,17 @@ func TestOapiRequestValidator(t *testing.T) {
 		called = false
 	}
 }
+
+func TestGetSkipperFromOptions(t *testing.T) {
+
+	options := new(Options)
+	assert.NotNil(t, getSkipperFromOptions(options))
+
+	options = &Options{}
+	assert.NotNil(t, getSkipperFromOptions(options))
+
+	options = &Options{
+		Skipper: echomiddleware.DefaultSkipper,
+	}
+	assert.NotNil(t, getSkipperFromOptions(options))
+}
diff --git a/pkg/securityprovider/securityprovider_test.go b/pkg/securityprovider/securityprovider_test.go
new file mode 100644
index 0000000..efea758
--- /dev/null
+++ b/pkg/securityprovider/securityprovider_test.go
@@ -0,0 +1,42 @@
+package securityprovider
+
+import (
+	"testing"
+
+	"github.com/deepmap/oapi-codegen/internal/test/client"
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	withTrailingSlash string = "https://my-api.com/some-base-url/v1/"
+)
+
+func TestSecurityProviders(t *testing.T) {
+	bearer, err := NewSecurityProviderBearerToken("mytoken")
+	assert.NoError(t, err)
+	client1, err := client.NewClient(
+		withTrailingSlash,
+		client.WithRequestEditorFn(bearer.Intercept),
+	)
+	assert.NoError(t, err)
+
+	apiKey, err := NewSecurityProviderApiKey("cookie", "apikey", "mykey")
+	assert.NoError(t, err)
+	client2, err := client.NewClient(
+		withTrailingSlash,
+		client.WithRequestEditorFn(apiKey.Intercept),
+	)
+	assert.NoError(t, err)
+
+	basicAuth, err := NewSecurityProviderBasicAuth("username", "password")
+	assert.NoError(t, err)
+	client3, err := client.NewClient(
+		withTrailingSlash,
+		client.WithRequestEditorFn(basicAuth.Intercept),
+	)
+	assert.NoError(t, err)
+
+	assert.Equal(t, withTrailingSlash, client1.Server)
+	assert.Equal(t, withTrailingSlash, client2.Server)
+	assert.Equal(t, withTrailingSlash, client3.Server)
+}