Implement ResultHandler as requested at: https://github.com/kataras/iris/issues/1465

Former-commit-id: 9d76c2f00766afd53cf6e591c25f861f179dd817
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-04-18 22:40:47 +03:00
parent 68c5883bce
commit dcf02480b3
7 changed files with 94 additions and 46 deletions

View File

@ -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")
}

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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)
}

View File

@ -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)
}
}