package hero import ( "fmt" "reflect" "github.com/kataras/iris/v12/context" ) type ( // ErrorHandler describes an interface to handle errors per hero handler and its dependencies. // // Handles non-nil errors return from a hero handler or a controller's method (see `getBindingsFor` and `Handler`) // the error may return from a request-scoped dependency too (see `Handler`). ErrorHandler interface { HandleError(context.Context, error) } // ErrorHandlerFunc implements the `ErrorHandler`. // It describes the type defnition for an error function handler. ErrorHandlerFunc func(context.Context, error) ) // HandleError fires when a non-nil error returns from a request-scoped dependency at serve-time or the handler itself. func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) { fn(ctx, err) } var ( // ErrSeeOther may be returned from a dependency handler to skip a specific dependency // based on custom logic. ErrSeeOther = fmt.Errorf("see other") // ErrStopExecution may be returned from a dependency handler to stop // and return the execution of the function without error (it calls ctx.StopExecution() too). // It may be occurred from request-scoped dependencies as well. ErrStopExecution = fmt.Errorf("stop execution") ) var ( // DefaultErrStatusCode is the default error status code (400) // when the response contains a non-nil error or a request-scoped binding error occur. DefaultErrStatusCode = 400 // DefaultErrorHandler is the default error handler which is fired // when a function returns a non-nil error or a request-scoped dependency failed to binded. DefaultErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) { if err != ErrStopExecution { if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) { ctx.StatusCode(DefaultErrStatusCode) } _, _ = ctx.WriteString(err.Error()) } ctx.StopExecution() }) ) func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler { if fn == nil { panic("makeHandler: function is nil") } // 0. A normal handler. if handler, ok := isHandler(fn); ok { return handler } // 1. A handler which returns just an error, handle it faster. if handlerWithErr, ok := isHandlerWithError(fn); ok { return func(ctx context.Context) { if err := handlerWithErr(ctx); err != nil { c.GetErrorHandler(ctx).HandleError(ctx, err) } } } v := valueOf(fn) numIn := v.Type().NumIn() bindings := getBindingsForFunc(v, c.Dependencies, paramsCount) return func(ctx context.Context) { inputs := make([]reflect.Value, numIn) for _, binding := range bindings { input, err := binding.Dependency.Handle(ctx, binding.Input) if err != nil { if err == ErrSeeOther { continue } // handled inside ErrorHandler. // else if err == ErrStopExecution { // ctx.StopExecution() // return // return without error. // } c.GetErrorHandler(ctx).HandleError(ctx, err) return } inputs[binding.Input.Index] = input } outputs := v.Call(inputs) if err := dispatchFuncResult(ctx, outputs); err != nil { c.GetErrorHandler(ctx).HandleError(ctx, err) } } } func isHandler(fn interface{}) (context.Handler, bool) { if handler, ok := fn.(context.Handler); ok { return handler, ok } if handler, ok := fn.(func(context.Context)); ok { return handler, ok } return nil, false } func isHandlerWithError(fn interface{}) (func(context.Context) error, bool) { if handlerWithErr, ok := fn.(func(context.Context) error); ok { return handlerWithErr, true } return nil, false }