mirror of
https://github.com/kataras/iris.git
synced 2025-01-24 03:01:03 +01:00
dd5de52f34
Former-commit-id: e452a916da80d886535b8ae9625d0ba8e2b58d6e
347 lines
10 KiB
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
|
|
}
|