From dcf02480b3cdf15d8bcf004ae4cde5d0551c8eec Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 18 Apr 2020 22:40:47 +0300 Subject: [PATCH] Implement ResultHandler as requested at: https://github.com/kataras/iris/issues/1465 Former-commit-id: 9d76c2f00766afd53cf6e591c25f861f179dd817 --- _examples/dependency-injection/basic/main.go | 17 +++- context/context.go | 2 +- core/router/api_container.go | 8 ++ go19.go | 4 + hero/container.go | 13 +++ hero/func_result.go | 89 +++++++++++--------- hero/handler.go | 7 +- 7 files changed, 94 insertions(+), 46 deletions(-) diff --git a/_examples/dependency-injection/basic/main.go b/_examples/dependency-injection/basic/main.go index 3fd838d3..d464f562 100644 --- a/_examples/dependency-injection/basic/main.go +++ b/_examples/dependency-injection/basic/main.go @@ -20,10 +20,21 @@ func handler(id int, in testInput) testOutput { } } +func configureAPI(api *iris.APIContainer) { + /* Here is how you can inject a return value from a handler, + in this case the "testOutput": + api.UseResultHandler(func(next iris.ResultHandler) iris.ResultHandler { + return func(ctx iris.Context, v interface{}) error { + return next(ctx, map[string]interface{}{"injected": true}) + } + }) + */ + + api.Post("/{id:int}", handler) +} + func main() { app := iris.New() - app.ConfigureContainer(func(api *iris.APIContainer) { - api.Post("/{id:int}", handler) - }) + app.ConfigureContainer(configureAPI) app.Listen(":8080") } diff --git a/context/context.go b/context/context.go index 5eccd251..d7cc1963 100644 --- a/context/context.go +++ b/context/context.go @@ -2819,7 +2819,7 @@ func (ctx *context) ReadBody(ptr interface{}) error { } switch ctx.GetContentTypeRequested() { - case ContentXMLHeaderValue: + case ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue: return ctx.ReadXML(ptr) case ContentYAMLHeaderValue: return ctx.ReadYAML(ptr) diff --git a/core/router/api_container.go b/core/router/api_container.go index 2fde365f..b3def2d1 100644 --- a/core/router/api_container.go +++ b/core/router/api_container.go @@ -64,6 +64,14 @@ func (api *APIContainer) RegisterDependency(dependency interface{}) *hero.Depend return api.Container.Register(dependency) } +// UseResultHandler adds a result handler to the Container. +// A result handler can be used to inject the struct value +// or to replace the default renderer. +func (api *APIContainer) UseResultHandler(handler func(next hero.ResultHandler) hero.ResultHandler) *APIContainer { + api.Container.UseResultHandler(handler) + return api +} + // convertHandlerFuncs accepts Iris hero handlers and returns a slice of native Iris handlers. func (api *APIContainer) convertHandlerFuncs(relativePath string, handlersFn ...interface{}) context.Handlers { fullpath := api.Self.GetRelPath() + relativePath diff --git a/go19.go b/go19.go index 4930431c..59587e66 100644 --- a/go19.go +++ b/go19.go @@ -6,6 +6,7 @@ import ( "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/core/host" "github.com/kataras/iris/v12/core/router" + "github.com/kataras/iris/v12/hero" ) type ( @@ -88,6 +89,9 @@ type ( // // A shortcut for the `core/router#APIContainer`. APIContainer = router.APIContainer + // ResultHandler describes the function type which should serve the "v" struct value. + // See `APIContainer.UseResultHandler`. + ResultHandler = hero.ResultHandler // DirOptions contains the optional settings that // `FileServer` and `Party#HandleDir` can use to serve files and assets. // A shortcut for the `router.DirOptions`, useful when `FileServer` or `HandleDir` is being used. diff --git a/hero/container.go b/hero/container.go index 607fb8a3..2f4c01a0 100644 --- a/hero/container.go +++ b/hero/container.go @@ -33,6 +33,10 @@ type Container struct { // GetErrorHandler should return a valid `ErrorHandler` to handle bindings AND handler dispatch errors. // Defaults to a functon which returns the `DefaultErrorHandler`. GetErrorHandler func(context.Context) ErrorHandler // cannot be nil. + + // resultHandlers is a list of functions that serve the return struct value of a function handler. + // Defaults to "defaultResultHandler" but it can be overridden. + resultHandlers []func(next ResultHandler) ResultHandler } // BuiltinDependencies is a list of builtin dependencies that are added on Container's initilization. @@ -103,6 +107,7 @@ func (c *Container) Clone() *Container { clonedDeps := make([]*Dependency, len(c.Dependencies)) copy(clonedDeps, c.Dependencies) cloned.Dependencies = clonedDeps + cloned.resultHandlers = c.resultHandlers return cloned } @@ -149,6 +154,14 @@ func (c *Container) Register(dependency interface{}) *Dependency { return d } +// UseResultHandler adds a result handler to the Container. +// A result handler can be used to inject the struct value +// or to replace the default renderer. +func (c *Container) UseResultHandler(handler func(next ResultHandler) ResultHandler) *Container { + c.resultHandlers = append(c.resultHandlers, handler) + return c +} + // Handler accepts a "handler" function which can accept any input arguments that match // with the Container's `Dependencies` and any output result; like string, int (string,int), // custom structs, Result(View | Response) and anything you can imagine. diff --git a/hero/func_result.go b/hero/func_result.go index 58b6ff77..bd6d1ab7 100644 --- a/hero/func_result.go +++ b/hero/func_result.go @@ -10,6 +10,46 @@ import ( "github.com/fatih/structs" ) +// ResultHandler describes the function type which should serve the "v" struct value. +type ResultHandler func(ctx context.Context, v interface{}) error + +func defaultResultHandler(ctx context.Context, v interface{}) error { + if p, ok := v.(PreflightResult); ok { + if err := p.Preflight(ctx); err != nil { + return err + } + } + + if d, ok := v.(Result); ok { + d.Dispatch(ctx) + return nil + } + + switch context.TrimHeaderValue(ctx.GetContentType()) { + case context.ContentXMLHeaderValue, context.ContentXMLUnreadableHeaderValue: + _, err := ctx.XML(v) + return err + case context.ContentYAMLHeaderValue: + _, err := ctx.YAML(v) + return err + case context.ContentProtobufHeaderValue: + msg, ok := v.(proto.Message) + if !ok { + return context.ErrContentNotSupported + } + + _, err := ctx.Protobuf(msg) + return err + case context.ContentMsgPackHeaderValue, context.ContentMsgPack2HeaderValue: + _, err := ctx.MsgPack(v) + return err + default: + // otherwise default to JSON. + _, err := ctx.JSON(v) + return err + } +} + // Result is a response dispatcher. // All types that complete this interface // can be returned as values from the method functions. @@ -114,7 +154,7 @@ func dispatchErr(ctx context.Context, status int, err error) bool { // Result or (Result, error) and so on... // // where Get is an HTTP METHOD. -func dispatchFuncResult(ctx context.Context, values []reflect.Value) error { +func dispatchFuncResult(ctx context.Context, values []reflect.Value, handler ResultHandler) error { if len(values) == 0 { return nil } @@ -278,13 +318,13 @@ func dispatchFuncResult(ctx context.Context, values []reflect.Value) error { } } - return dispatchCommon(ctx, statusCode, contentType, content, custom, found) + return dispatchCommon(ctx, statusCode, contentType, content, custom, handler, found) } // dispatchCommon is being used internally to send // commonly used data to the response writer with a smart way. func dispatchCommon(ctx context.Context, - statusCode int, contentType string, content []byte, v interface{}, found bool) error { + statusCode int, contentType string, content []byte, v interface{}, handler ResultHandler, found bool) error { // if we have a false boolean as a return value // then skip everything and fire a not found, // we even don't care about the given status code or the object or the content. @@ -310,46 +350,13 @@ func dispatchCommon(ctx context.Context, } } + // write the content type now (internal check for empty value) + ctx.ContentType(contentType) + if v != nil { - if p, ok := v.(PreflightResult); ok { - if err := p.Preflight(ctx); err != nil { - return err - } - } - - if d, ok := v.(Result); ok { - // write the content type now (internal check for empty value) - ctx.ContentType(contentType) - d.Dispatch(ctx) - return nil - } - - switch context.TrimHeaderValue(contentType) { - case context.ContentXMLHeaderValue: - _, err := ctx.XML(v) - return err - case context.ContentYAMLHeaderValue: - _, err := ctx.YAML(v) - return err - case context.ContentProtobufHeaderValue: - msg, ok := v.(proto.Message) - if !ok { - return context.ErrContentNotSupported - } - - _, err := ctx.Protobuf(msg) - return err - case context.ContentMsgPackHeaderValue, context.ContentMsgPack2HeaderValue: - _, err := ctx.MsgPack(v) - return err - default: - // otherwise default to JSON. - _, err := ctx.JSON(v) - return err - } + return handler(ctx, v) } - ctx.ContentType(contentType) // .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader, // it will not cost anything. _, err := ctx.Write(content) @@ -420,7 +427,7 @@ func (r Response) Dispatch(ctx context.Context) { return } - err := dispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, true) + err := dispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, defaultResultHandler, true) dispatchErr(ctx, r.Code, err) } diff --git a/hero/handler.go b/hero/handler.go index 002375df..4e6b72e6 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -79,6 +79,11 @@ func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler bindings := getBindingsForFunc(v, c.Dependencies, paramsCount) + resultHandler := defaultResultHandler + for i, lidx := 0, len(c.resultHandlers)-1; i <= lidx; i++ { + resultHandler = c.resultHandlers[lidx-i](resultHandler) + } + return func(ctx context.Context) { inputs := make([]reflect.Value, numIn) @@ -102,7 +107,7 @@ func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler } outputs := v.Call(inputs) - if err := dispatchFuncResult(ctx, outputs); err != nil { + if err := dispatchFuncResult(ctx, outputs, resultHandler); err != nil { c.GetErrorHandler(ctx).HandleError(ctx, err) } }