Repository: deepmap/oapi-codegen
Pull request #182 (2020-05-07 21:53:11)

Update docs with more examples of registering http handlers
Based on https://github.com/deepmap/oapi-codegen/pull/110, https://github.com/deepmap/oapi-codegen/pull/124 it looks like there's some missing docs on how to setup the chi based server.

Open to feedback if docs aren't quite inline with style!

cc @m110 @aca @deepmap-marcinr 


Patch:
diff --git a/README.md b/README.md
index b30b013..6f7d9ae 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ point of use.
 
 For each element in the `paths` map in OpenAPI, we will generate a Go handler
 function in an interface object. Here is the generated Go interface for our
-server.
+Echo server.
 
 ```go
 type ServerInterface interface {
@@ -122,6 +122,11 @@ type FindPetsParams struct {
 }
 ```
 
+### Registering handlers
+There are a few ways of registering your http handler based on the type of server generated i.e. `-generate server` or `-generate chi-server`
+
+<details><summary><code>Echo</code></summary>
+
 The usage of `Echo` is out of scope of this doc, but once you have an
 echo instance, we generate a utility function to help you associate your handlers
 with this autogenerated code. For the pet store, it looks like this:
@@ -150,6 +155,41 @@ func SetupHandler() {
 }
 ```
 
+</summary></details>
+
+<details><summary><code>Chi</code></summary>
+
+```go
+type PetStoreImpl struct {}
+func (*PetStoreImpl) GetPets(r *http.Request, w *http.ResponseWriter) {
+    // Implement me
+}
+
+func SetupHandler() {
+    var myApi PetStoreImpl
+
+    r := chi.Router()
+    r.Mount("/", Handler(&myApi))
+}
+```
+</summary></details>
+
+<details><summary><code>net/http</code></summary>
+
+```go
+type PetStoreImpl struct {}
+func (*PetStoreImpl) GetPets(r *http.Request, w *http.ResponseWriter) {
+    // Implement me
+}
+
+func SetupHandler() {
+    var myApi PetStoreImpl
+
+    http.Handle("/", Handler(&myApi))
+}
+```
+</summary></details>
+
 #### Additional Properties in type definitions
 
 [OpenAPI Schemas](https://swagger.io/specification/#schemaObject) implicitly
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)
+}
diff --git a/pkg/testutil/request_helpers.go b/pkg/testutil/request_helpers.go
index 16ea47f..6898823 100644
--- a/pkg/testutil/request_helpers.go
+++ b/pkg/testutil/request_helpers.go
@@ -162,6 +162,14 @@ func (r *RequestBuilder) Go(t *testing.T, e *echo.Echo) *CompletedRequest {
 // ResponseRecorder with some nice helper functions.
 type CompletedRequest struct {
 	Recorder *httptest.ResponseRecorder
+
+	// When set to true, decoders will be more strict. In the default JSON
+	// recorder, unknown fields will cause errors.
+	Strict bool
+}
+
+func (c *CompletedRequest) DisallowUnknownFields() {
+	c.Strict = true
 }
 
 // This function takes a destination object as input, and unmarshals the object
@@ -177,7 +185,7 @@ func (c *CompletedRequest) UnmarshalBodyToObject(obj interface{}) error {
 		return fmt.Errorf("unhandled content: %s", content)
 	}
 
-	return handler(ctype, c.Recorder.Body, obj)
+	return handler(ctype, c.Recorder.Body, obj, c.Strict)
 }
 
 // This function assumes that the response contains JSON and unmarshals it
diff --git a/pkg/testutil/response_handlers.go b/pkg/testutil/response_handlers.go
index 52e83a4..520658b 100644
--- a/pkg/testutil/response_handlers.go
+++ b/pkg/testutil/response_handlers.go
@@ -17,7 +17,7 @@ var (
 	knownHandlers   map[string]ResponseHandler
 )
 
-type ResponseHandler func(contentType string, raw io.Reader, obj interface{}) error
+type ResponseHandler func(contentType string, raw io.Reader, obj interface{}, strict bool) error
 
 func RegisterResponseHandler(mime string, handler ResponseHandler) {
 	knownHandlersMu.Lock()
@@ -35,6 +35,10 @@ func getHandler(mime string) ResponseHandler {
 
 // This function assumes that the response contains JSON and unmarshals it
 // into the specified object.
-func jsonHandler(_ string, r io.Reader, obj interface{}) error {
+func jsonHandler(_ string, r io.Reader, obj interface{}, strict bool) error {
+	d := json.NewDecoder(r)
+	if strict {
+		d.DisallowUnknownFields()
+	}
 	return json.NewDecoder(r).Decode(obj)
 }