1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-24 05:17:03 +00:00

implement mvc HandleError as requested at #1244

Former-commit-id: 58a69f9cffe67c3aa1bab5d9425c5df65e2367ed
This commit is contained in:
Gerasimos (Makis) Maropoulos
2019-04-16 18:01:48 +03:00
parent df3a68255c
commit 0d4d2bd3fa
13 changed files with 145 additions and 363 deletions

View File

@@ -29,7 +29,7 @@ type shared interface {
Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route
}
// BeforeActivation is being used as the onle one input argument of a
// BeforeActivation is being used as the only one input argument of a
// `func(c *Controller) BeforeActivation(b mvc.BeforeActivation) {}`.
//
// It's being called before the controller's dependencies binding to the fields or the input arguments
@@ -42,11 +42,11 @@ type BeforeActivation interface {
Dependencies() *di.Values
}
// AfterActivation is being used as the onle one input argument of a
// AfterActivation is being used as the only one input argument of a
// `func(c *Controller) AfterActivation(a mvc.AfterActivation) {}`.
//
// It's being called after the `BeforeActivation`,
// and after controller's dependencies binded to the fields or the input arguments but before server ran.
// and after controller's dependencies bind-ed to the fields or the input arguments but before server ran.
//
// It's being used to customize a controller if needed inside the controller itself,
// it's called once per application.
@@ -81,10 +81,12 @@ type ControllerActivator struct {
routes map[string]*router.Route
// the bindings that comes from the Engine and the controller's filled fields if any.
// Can be binded to the the new controller's fields and method that is fired
// Can be bind-ed to the the new controller's fields and method that is fired
// on incoming requests.
dependencies di.Values
errorHandler hero.ErrorHandler
// initialized on the first `Handle`.
injector *di.StructInjector
}
@@ -101,7 +103,7 @@ func NameOf(v interface{}) string {
return fullname
}
func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value) *ControllerActivator {
func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, errorHandler hero.ErrorHandler) *ControllerActivator {
typ := reflect.TypeOf(controller)
c := &ControllerActivator{
@@ -121,6 +123,7 @@ func newControllerActivator(router router.Party, controller interface{}, depende
routes: whatReservedMethods(typ),
// CloneWithFieldsOf: include the manual fill-ed controller struct's fields to the dependencies.
dependencies: di.Values(dependencies).CloneWithFieldsOf(controller),
errorHandler: errorHandler,
}
return c
@@ -326,7 +329,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
// Remember:
// The `Handle->handlerOf` can be called from `BeforeActivation` event
// then, the c.injector is nil because
// we may not have the dependencies binded yet.
// we may not have the dependencies bind-ed yet.
// To solve this we're doing a check on the FIRST `Handle`,
// if c.injector is nil, then set it with the current bindings,
// these bindings can change after, so first add dependencies and after register routes.
@@ -346,24 +349,27 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
}
var (
implementsBase = isBaseController(c.Type)
hasBindableFields = c.injector.CanInject
hasBindableFuncInputs = funcInjector.Has
implementsBase = isBaseController(c.Type)
implementsErrorHandler = isErrorHandler(c.Type)
hasBindableFields = c.injector.CanInject
hasBindableFuncInputs = funcInjector.Has
funcHasErrorOut = hasErrorOutArgs(m)
call = m.Func.Call
)
if !implementsBase && !hasBindableFields && !hasBindableFuncInputs {
if !implementsBase && !hasBindableFields && !hasBindableFuncInputs && !implementsErrorHandler {
return func(ctx context.Context) {
hero.DispatchFuncResult(ctx, call(c.injector.AcquireSlice()))
hero.DispatchFuncResult(ctx, c.errorHandler, call(c.injector.AcquireSlice()))
}
}
n := m.Type.NumIn()
return func(ctx context.Context) {
var (
ctrl = c.injector.Acquire()
ctxValue reflect.Value
ctrl = c.injector.Acquire()
ctxValue reflect.Value
errorHandler = c.errorHandler
)
// inject struct fields first before the BeginRequest and EndRequest, if any,
@@ -388,6 +394,10 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
defer b.EndRequest(ctx)
}
if funcHasErrorOut && implementsErrorHandler {
errorHandler = ctrl.Interface().(hero.ErrorHandler)
}
if hasBindableFuncInputs {
// means that ctxValue is not initialized before by the controller's struct injector.
if !hasBindableFields {
@@ -406,11 +416,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
// println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx)
// }
hero.DispatchFuncResult(ctx, call(in))
hero.DispatchFuncResult(ctx, errorHandler, call(in))
return
}
hero.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
hero.DispatchFuncResult(ctx, errorHandler, ctrl.Method(m.Index).Call(emptyIn))
}
}

View File

@@ -27,7 +27,7 @@ var (
HeroDependencies = true
)
// Application is the high-level compoment of the "mvc" package.
// Application is the high-level component of the "mvc" package.
// It's the API that you will be using to register controllers among with their
// dependencies that your controllers may expecting.
// It contains the Router(iris.Party) in order to be able to register
@@ -42,6 +42,7 @@ type Application struct {
Dependencies di.Values
Router router.Party
Controllers []*ControllerActivator
ErrorHandler hero.ErrorHandler
}
func newApp(subRouter router.Party, values di.Values) *Application {
@@ -99,7 +100,7 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat
// The value can be a single struct value-instance or a function
// which has one input and one output, the input should be
// an `iris.Context` and the output can be any type, that output type
// will be binded to the controller's field, if matching or to the
// will be bind-ed to the controller's field, if matching or to the
// controller's methods, if matching.
//
// These dependencies "values" can be changed per-controller as well,
@@ -172,7 +173,7 @@ Set the Logger's Level to "debug" to view the active dependencies per controller
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc
func (app *Application) Handle(controller interface{}) *Application {
// initialize the controller's activator, nothing too magical so far.
c := newControllerActivator(app.Router, controller, app.Dependencies)
c := newControllerActivator(app.Router, controller, app.Dependencies, app.ErrorHandler)
// check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
// call, which is simply parses the controller's methods, end-dev can register custom controller's methods
@@ -195,12 +196,22 @@ func (app *Application) Handle(controller interface{}) *Application {
return app
}
// HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
// application's controllers' functions returns an non-nil error.
// Each controller can override it by implementing the `hero.ErrorHandler`.
func (app *Application) HandleError(errHandler hero.ErrorHandlerFunc) *Application {
app.ErrorHandler = errHandler
return app
}
// Clone returns a new mvc Application which has the dependencies
// of the current mvc Mpplication's dependencies.
// of the current mvc Application's `Dependencies` and its `ErrorHandler`.
//
// Example: `.Clone(app.Party("/path")).Handle(new(TodoSubController))`.
func (app *Application) Clone(party router.Party) *Application {
return newApp(party, app.Dependencies.Clone())
cloned := newApp(party, app.Dependencies.Clone())
cloned.ErrorHandler = app.ErrorHandler
return cloned
}
// Party returns a new child mvc Application based on the current path + "relativePath".

View File

@@ -1,13 +1,42 @@
package mvc
import "reflect"
import (
"reflect"
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
"github.com/kataras/iris/hero"
)
var (
baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
errorHandlerTyp = reflect.TypeOf((*hero.ErrorHandler)(nil)).Elem()
errorTyp = reflect.TypeOf((*error)(nil)).Elem()
)
func isBaseController(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(baseControllerTyp)
}
func isErrorHandler(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(errorHandlerTyp)
}
func hasErrorOutArgs(fn reflect.Method) bool {
n := fn.Type.NumOut()
if n == 0 {
return false
}
for i := 0; i < n; i++ {
if out := fn.Type.Out(i); out.Kind() == reflect.Interface {
if out.Implements(errorTyp) {
return true
}
}
}
return false
}
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)