iris/mvc/activator/activator.go

347 lines
10 KiB
Go

package activator
import (
"reflect"
"strings"
"github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/mvc/activator/methodfunc"
"github.com/kataras/iris/mvc/activator/model"
"github.com/kataras/iris/mvc/activator/persistence"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
type (
// TController is the type of the controller,
// it contains all the necessary information to load
// and serve the controller to the outside world,
// think it as a "supervisor" of your Controller which
// cares about you.
TController struct {
// The name of the front controller struct.
Name string
// FullName it's the last package path segment + "." + the Name.
// i.e: if login-example/user/controller.go, the FullName is "user.Controller".
FullName string
// the type of the user/dev's "c" controller (interface{}).
Type reflect.Type
// it's the first passed value of the controller instance,
// we need this to collect and save the persistence fields' values.
Value reflect.Value
valuePtr reflect.Value
// // Methods and handlers, available after the Activate, can be seted `OnActivate` event as well.
// Methods []methodfunc.MethodFunc
Router RegisterFunc
binder *binder // executed even before the BeginRequest if not nil.
modelController *model.Controller
persistenceController *persistence.Controller
}
)
// the parent package should complete this "interface"
// it's not exported, so their functions
// but reflect doesn't care about it, so we are ok
// to compare the type of the base controller field
// with this "ctrl", see `buildTypeInfo` and `buildMethodHandler`.
var (
// ErrMissingControllerInstance is a static error which fired from `Controller` when
// the passed "c" instnace is not a valid type of `Controller` or `C`.
ErrMissingControllerInstance = errors.New("controller should have a field of mvc.Controller or mvc.C type")
// ErrInvalidControllerType fired when the "Controller" field is not
// the correct type.
ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
)
// BaseController is the controller interface,
// which the main request `Controller` will implement automatically.
// End-User doesn't need to have any knowledge of this if she/he doesn't want to implement
// a new Controller type.
// Controller looks the whole flow as one handler, so `ctx.Next`
// inside `BeginRequest` is not be respected.
// Alternative way to check if a middleware was procceed successfully
// and called its `ctx.Next` is the `ctx.Proceed(handler) bool`.
// You have to navigate to the `context/context#Proceed` function's documentation.
type BaseController interface {
SetName(name string)
BeginRequest(ctx context.Context)
EndRequest(ctx context.Context)
}
// ActivateController returns a new controller type info description.
func newController(base BaseController, router RegisterFunc) (*TController, error) {
// get and save the type.
typ := reflect.TypeOf(base)
if typ.Kind() != reflect.Ptr {
typ = reflect.PtrTo(typ)
}
valPointer := reflect.ValueOf(base) // or value raw
// first instance value, needed to validate
// the actual type of the controller field
// and to collect and save the instance's persistence fields'
// values later on.
val := reflect.Indirect(valPointer)
ctrlName := val.Type().Name()
pkgPath := val.Type().PkgPath()
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
t := &TController{
Name: ctrlName,
FullName: fullName,
Type: typ,
Value: val,
valuePtr: valPointer,
Router: router,
binder: &binder{elemType: typ.Elem()},
modelController: model.Load(typ),
persistenceController: persistence.Load(typ, val),
}
return t, nil
}
// BindValueTypeExists returns true if at least one type of "bindValue"
// is already binded to this `TController`.
func (t *TController) BindValueTypeExists(bindValue interface{}) bool {
valueTyp := reflect.TypeOf(bindValue)
for _, bindedValue := range t.binder.values {
// type already exists, remember: binding here is per-type.
if typ := reflect.TypeOf(bindedValue); typ == valueTyp ||
(valueTyp.Kind() == reflect.Interface && typ.Implements(valueTyp)) {
return true
}
}
return false
}
// BindValue binds a value to a controller's field when request is served.
func (t *TController) BindValue(bindValues ...interface{}) {
for _, bindValue := range bindValues {
t.binder.bind(bindValue)
}
}
// HandlerOf builds the handler for a type based on the specific method func.
func (t *TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler {
var (
// shared, per-controller
elem = t.Type.Elem()
ctrlName = t.Name
hasBinder = !t.binder.isEmpty()
hasPersistenceData = t.persistenceController != nil
hasModels = t.modelController != nil
// per-handler
handleRequest = methodFunc.MethodCall
)
return func(ctx context.Context) {
// create a new controller instance of that type(>ptr).
c := reflect.New(elem)
if hasBinder {
t.binder.handle(c)
}
b := c.Interface().(BaseController)
b.SetName(ctrlName)
// if has persistence data then set them
// before the end-developer's handler in order to be available there.
if hasPersistenceData {
t.persistenceController.Handle(c)
}
// if previous (binded) handlers stopped the execution
// we should know that.
if ctx.IsStopped() {
return
}
// init the request.
b.BeginRequest(ctx)
if ctx.IsStopped() { // if begin request stopped the execution
return
}
// the most important, execute the specific function
// from the controller that is responsible to handle
// this request, by method and path.
handleRequest(ctx, c.Method(methodFunc.Index))
// if had models, set them after the end-developer's handler.
if hasModels {
t.modelController.Handle(ctx, c)
}
// end the request, don't check for stopped because this does the actual writing
// if no response written already.
b.EndRequest(ctx)
}
}
func (t *TController) registerMethodFunc(m methodfunc.MethodFunc) {
var middleware context.Handlers
if !t.binder.isEmpty() {
if m := t.binder.middleware; len(m) > 0 {
middleware = m
}
}
h := t.HandlerOf(m)
if h == nil {
golog.Warnf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
return
}
registeredHandlers := append(middleware, h)
t.Router(m.HTTPMethod, m.RelPath, registeredHandlers...)
golog.Debugf("MVC %s: %s %s maps to function[%d] '%s'", t.FullName,
m.HTTPMethod,
m.RelPath,
m.Index,
m.Name)
}
func (t *TController) resolveAndRegisterMethods() {
// the actual method functions
// i.e for "GET" it's the `Get()`.
methods, err := methodfunc.Resolve(t.Type)
if err != nil {
golog.Errorf("MVC %s: %s", t.FullName, err.Error())
return
}
// range over the type info's method funcs,
// build a new handler for each of these
// methods and register them to their
// http methods using the registerFunc, which is
// responsible to convert these into routes
// and add them to router via the APIBuilder.
for _, m := range methods {
t.registerMethodFunc(m)
}
}
// Handle registers a method func but with a custom http method and relative route's path,
// it respects the rest of the controller's rules and guidelines.
func (t *TController) Handle(httpMethod, path, handlerFuncName string) bool {
cTyp := t.Type // with the pointer.
m, exists := cTyp.MethodByName(handlerFuncName)
if !exists {
golog.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
handlerFuncName, t.FullName)
return false
}
info := methodfunc.FuncInfo{
Name: m.Name,
Trailing: m.Name,
Type: m.Type,
Index: m.Index,
HTTPMethod: httpMethod,
}
tmpl, err := macro.Parse(path, macro.NewMap())
if err != nil {
golog.Errorf("MVC: fail to parse the path for '%s.%s': %v", t.FullName, handlerFuncName, err)
return false
}
paramKeys := make([]string, len(tmpl.Params), len(tmpl.Params))
for i, param := range tmpl.Params {
paramKeys[i] = param.Name
}
methodFunc, err := methodfunc.ResolveMethodFunc(info, paramKeys...)
if err != nil {
golog.Errorf("MVC: function '%s' inside the '%s' controller: %v", handlerFuncName, t.FullName, err)
return false
}
methodFunc.RelPath = path
t.registerMethodFunc(methodFunc)
return true
}
// func (t *TController) getMethodFuncByName(funcName string) (methodfunc.MethodFunc, bool) {
// cVal := t.Value
// cTyp := t.Type // with the pointer.
// m, exists := cTyp.MethodByName(funcName)
// if !exists {
// golog.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
// funcName, cTyp.String())
// return methodfunc.MethodFunc{}, false
// }
// fn := cVal.MethodByName(funcName)
// if !fn.IsValid() {
// golog.Errorf("MVC: function '%s' inside the '%s' controller has not a valid value",
// funcName, cTyp.String())
// return methodfunc.MethodFunc{}, false
// }
// info, ok := methodfunc.FetchFuncInfo(m)
// if !ok {
// golog.Errorf("MVC: could not resolve the func info from '%s'", funcName)
// return methodfunc.MethodFunc{}, false
// }
// methodFunc, err := methodfunc.ResolveMethodFunc(info)
// if err != nil {
// golog.Errorf("MVC: %v", err)
// return methodfunc.MethodFunc{}, false
// }
// return methodFunc, true
// }
// // RegisterName registers a function by its name
// func (t *TController) RegisterName(funcName string) bool {
// methodFunc, ok := t.getMethodFuncByName(funcName)
// if !ok {
// return false
// }
// t.registerMethodFunc(methodFunc)
// return true
// }
// RegisterFunc used by the caller to register the result routes.
type RegisterFunc func(httpMethod string, relPath string, handler ...context.Handler)
// Register receives a "controller",
// a pointer of an instance which embeds the `Controller`,
// the value of "baseControllerFieldName" should be `Controller`.
func Register(controller BaseController, bindValues []interface{},
registerFunc RegisterFunc) error {
t, err := newController(controller, registerFunc)
if err != nil {
return err
}
t.BindValue(bindValues...)
CallOnActivate(controller, t)
for _, bf := range t.binder.fields {
golog.Debugf("MVC %s: binder loaded for '%s' with value:\n%#v",
t.FullName, bf.GetFullName(), bf.GetValue())
}
t.resolveAndRegisterMethods()
return nil
}