implement a way to add controller functions as handlers with the existing rules respected but it's a bit dirty I will change the implementation and move the mvc2 to mvc and make the api builder's PartyFunc to be a critical part of the controller and the mvc2.Mvc bind values should be also respected to the controller and more

Former-commit-id: e452a916da80d886535b8ae9625d0ba8e2b58d6e
This commit is contained in:
kataras 2017-11-27 21:39:57 +02:00
parent 9d63e3194f
commit dd5de52f34
16 changed files with 496 additions and 206 deletions

View File

@ -190,11 +190,11 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
// otherwise use `Party` which can handle many paths with different handlers and middlewares. // otherwise use `Party` which can handle many paths with different handlers and middlewares.
// //
// Usage: // Usage:
// app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler) // app.HandleMany("GET", "/user /user/{id:int} /user/me", genericUserHandler)
// At the other side, with `Handle` we've had to write: // At the other side, with `Handle` we've had to write:
// app.Handle(iris.MethodGet, "/user", userHandler) // app.Handle("GET", "/user", userHandler)
// app.Handle(iris.MethodGet, "/user/{id:int}", userHandler) // app.Handle("GET", "/user/{id:int}", userByIDHandler)
// app.Handle(iris.MethodGet, "/user/me", userHandler) // app.Handle("GET", "/user/me", userMeHandler)
// //
// This method is used behind the scenes at the `Controller` function // This method is used behind the scenes at the `Controller` function
// in order to handle more than one paths for the same controller instance. // in order to handle more than one paths for the same controller instance.
@ -536,7 +536,7 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController, func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController,
bindValues ...interface{}) (routes []*Route) { bindValues ...interface{}) (routes []*Route) {
registerFunc := func(ifRelPath string, method string, handlers ...context.Handler) { registerFunc := func(method string, ifRelPath string, handlers ...context.Handler) {
relPath := relativePath + ifRelPath relPath := relativePath + ifRelPath
r := api.HandleMany(method, relPath, handlers...) r := api.HandleMany(method, relPath, handlers...)
routes = append(routes, r...) routes = append(routes, r...)

View File

@ -2,6 +2,7 @@ package router
import ( import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/mvc/activator" "github.com/kataras/iris/mvc/activator"
) )
@ -13,6 +14,12 @@ import (
// //
// Look the "APIBuilder" for its implementation. // Look the "APIBuilder" for its implementation.
type Party interface { type Party interface {
// Macros returns the macro map which is responsible
// to register custom macro functions for all routes.
//
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
Macros() *macro.Map
// Party groups routes which may have the same prefix and share same handlers, // Party groups routes which may have the same prefix and share same handlers,
// returns that new rich subrouter. // returns that new rich subrouter.
// //

View File

@ -1,22 +1,13 @@
package activator package activator
import ( // CallOnActivate simply calls the "controller"'s `OnActivate(*TController)` function,
"reflect"
)
// CallOnActivate simply calls the "controller"'s `OnActivate(*ActivatePayload)` function,
// if any. // if any.
// //
// Look `activator.go#Register` and `ActivateListener` for more. // Look `activator.go#Register` and `ActivateListener` for more.
func CallOnActivate(controller interface{}, func CallOnActivate(controller interface{}, tController *TController) {
bindValues *[]interface{}, registerFunc RegisterFunc) {
if ac, ok := controller.(ActivateListener); ok { if ac, ok := controller.(ActivateListener); ok {
p := &ActivatePayload{ ac.OnActivate(tController)
BindValues: bindValues,
Handle: registerFunc,
}
ac.OnActivate(p)
} }
} }
@ -27,52 +18,13 @@ func CallOnActivate(controller interface{},
// then the `OnActivate` function will be called ONCE, NOT in every request // then the `OnActivate` function will be called ONCE, NOT in every request
// but ONCE at the application's lifecycle. // but ONCE at the application's lifecycle.
type ActivateListener interface { type ActivateListener interface {
// OnActivate accepts a pointer to the `ActivatePayload`. // OnActivate accepts a pointer to the `TController`.
// //
// The `Controller` can make use of the `OnActivate` function // The `Controller` can make use of the `OnActivate` function
// to register custom routes // to register custom routes
// or modify the provided values that will be binded to the // or modify the provided values that will be binded to the
// controller later on. // controller later on.
// //
// Look `ActivatePayload` for more. // Look `TController` for more.
OnActivate(*ActivatePayload) OnActivate(*TController)
}
// ActivatePayload contains the necessary information and the ability
// to alt a controller's registration options, i.e the binder.
//
// With `ActivatePayload` the `Controller` can register custom routes
// or modify the provided values that will be binded to the
// controller later on.
type ActivatePayload struct {
BindValues *[]interface{}
Handle RegisterFunc
}
// EnsureBindValue will make sure that this "bindValue"
// will be registered to the controller's binder
// if its type is not already passed by the caller..
//
// For example, on `SessionController` it looks if *sessions.Sessions
// has been binded from the caller and if not then the "bindValue"
// will be binded and used as a default sessions manager instead.
//
// At general, if the caller has already provided a value with the same Type
// then the "bindValue" will be ignored and not be added to the controller's bind values.
//
// Returns true if the caller has NOT already provided a value with the same Type
// and "bindValue" is NOT ignored therefore is appended to the controller's bind values.
func (i *ActivatePayload) EnsureBindValue(bindValue interface{}) bool {
valueTyp := reflect.TypeOf(bindValue)
localBindValues := *i.BindValues
for _, bindedValue := range localBindValues {
// type already exists, remember: binding here is per-type.
if reflect.TypeOf(bindedValue) == valueTyp {
return false
}
}
*i.BindValues = append(localBindValues, bindValue)
return true
} }

View File

@ -4,6 +4,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/mvc/activator/methodfunc" "github.com/kataras/iris/mvc/activator/methodfunc"
"github.com/kataras/iris/mvc/activator/model" "github.com/kataras/iris/mvc/activator/model"
"github.com/kataras/iris/mvc/activator/persistence" "github.com/kataras/iris/mvc/activator/persistence"
@ -32,6 +33,12 @@ type (
// we need this to collect and save the persistence fields' values. // we need this to collect and save the persistence fields' values.
Value reflect.Value 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. binder *binder // executed even before the BeginRequest if not nil.
modelController *model.Controller modelController *model.Controller
persistenceController *persistence.Controller persistenceController *persistence.Controller
@ -69,37 +76,33 @@ type BaseController interface {
} }
// ActivateController returns a new controller type info description. // ActivateController returns a new controller type info description.
func ActivateController(base BaseController, bindValues []interface{}) (TController, error) { func newController(base BaseController, router RegisterFunc) (*TController, error) {
// get and save the type. // get and save the type.
typ := reflect.TypeOf(base) typ := reflect.TypeOf(base)
if typ.Kind() != reflect.Ptr { if typ.Kind() != reflect.Ptr {
typ = reflect.PtrTo(typ) typ = reflect.PtrTo(typ)
} }
valPointer := reflect.ValueOf(base) // or value raw
// first instance value, needed to validate // first instance value, needed to validate
// the actual type of the controller field // the actual type of the controller field
// and to collect and save the instance's persistence fields' // and to collect and save the instance's persistence fields'
// values later on. // values later on.
val := reflect.Indirect(reflect.ValueOf(base)) val := reflect.Indirect(valPointer)
ctrlName := val.Type().Name() ctrlName := val.Type().Name()
pkgPath := val.Type().PkgPath() pkgPath := val.Type().PkgPath()
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
// set the binder, can be nil this check at made at runtime. t := &TController{
binder := newBinder(typ.Elem(), bindValues)
if binder != nil {
for _, bf := range binder.fields {
golog.Debugf("MVC %s: binder loaded for '%s' with value:\n%#v",
fullName, bf.GetFullName(), bf.GetValue())
}
}
t := TController{
Name: ctrlName, Name: ctrlName,
FullName: fullName, FullName: fullName,
Type: typ, Type: typ,
Value: val, Value: val,
binder: binder, valuePtr: valPointer,
Router: router,
binder: &binder{elemType: typ.Elem()},
modelController: model.Load(typ), modelController: model.Load(typ),
persistenceController: persistence.Load(typ, val), persistenceController: persistence.Load(typ, val),
} }
@ -107,12 +110,35 @@ func ActivateController(base BaseController, bindValues []interface{}) (TControl
return t, nil 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. // HandlerOf builds the handler for a type based on the specific method func.
func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler { func (t *TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler {
var ( var (
// shared, per-controller // shared, per-controller
elem = t.Type.Elem() elem = t.Type.Elem()
ctrlName = t.Name ctrlName = t.Name
hasBinder = !t.binder.isEmpty()
hasPersistenceData = t.persistenceController != nil hasPersistenceData = t.persistenceController != nil
hasModels = t.modelController != nil hasModels = t.modelController != nil
@ -123,7 +149,7 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
return func(ctx context.Context) { return func(ctx context.Context) {
// create a new controller instance of that type(>ptr). // create a new controller instance of that type(>ptr).
c := reflect.New(elem) c := reflect.New(elem)
if t.binder != nil { if hasBinder {
t.binder.handle(c) t.binder.handle(c)
} }
@ -163,29 +189,38 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
} }
} }
// RegisterFunc used by the caller to register the result routes. func (t *TController) registerMethodFunc(m methodfunc.MethodFunc) {
type RegisterFunc func(relPath string, httpMethod string, handler ...context.Handler)
// RegisterMethodHandlers receives a `TController`, description of the
// user's controller, and calls the "registerFunc" for each of its
// method handlers.
//
// Not useful for the end-developer, but may needed for debugging
// at the future.
func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
var middleware context.Handlers var middleware context.Handlers
if t.binder != nil { if !t.binder.isEmpty() {
if m := t.binder.middleware; len(m) > 0 { if m := t.binder.middleware; len(m) > 0 {
middleware = m 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 // the actual method functions
// i.e for "GET" it's the `Get()`. // i.e for "GET" it's the `Get()`.
methods, err := methodfunc.Resolve(t.Type) methods, err := methodfunc.Resolve(t.Type)
if err != nil { if err != nil {
golog.Errorf("MVC %s: %s", t.FullName, err.Error()) golog.Errorf("MVC %s: %s", t.FullName, err.Error())
// don't stop here. return
} }
// range over the type info's method funcs, // range over the type info's method funcs,
// build a new handler for each of these // build a new handler for each of these
@ -194,35 +229,118 @@ func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
// responsible to convert these into routes // responsible to convert these into routes
// and add them to router via the APIBuilder. // and add them to router via the APIBuilder.
for _, m := range methods { for _, m := range methods {
h := t.HandlerOf(m) t.registerMethodFunc(m)
if h == nil {
golog.Warnf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
continue
}
registeredHandlers := append(middleware, h)
registerFunc(m.RelPath, m.HTTPMethod, registeredHandlers...)
golog.Debugf("MVC %s: %s %s maps to function[%d] '%s'", t.FullName,
m.HTTPMethod,
m.RelPath,
m.Index,
m.Name)
} }
} }
// 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", // Register receives a "controller",
// a pointer of an instance which embeds the `Controller`, // a pointer of an instance which embeds the `Controller`,
// the value of "baseControllerFieldName" should be `Controller`. // the value of "baseControllerFieldName" should be `Controller`.
func Register(controller BaseController, bindValues []interface{}, func Register(controller BaseController, bindValues []interface{},
registerFunc RegisterFunc) error { registerFunc RegisterFunc) error {
CallOnActivate(controller, &bindValues, registerFunc) t, err := newController(controller, registerFunc)
t, err := ActivateController(controller, bindValues)
if err != nil { if err != nil {
return err return err
} }
RegisterMethodHandlers(t, registerFunc) 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 return nil
} }

View File

@ -8,7 +8,15 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
) )
// binder accepts a value of something
// and tries to find its equalivent type
// inside the controller and sets that to it,
// after that each new instance of the controller will have
// this value on the specific field, like persistence data control does.
type binder struct { type binder struct {
elemType reflect.Type
// values and fields are matched on the `match`.
values []interface{} values []interface{}
fields []field.Field fields []field.Field
@ -17,28 +25,24 @@ type binder struct {
middleware context.Handlers middleware context.Handlers
} }
// binder accepts a value of something func (b *binder) bind(value interface{}) {
// and tries to find its equalivent type if value == nil {
// inside the controller and sets that to it, return
// after that each new instance of the controller will have
// this value on the specific field, like persistence data control does.
//
// returns a nil binder if values are not valid bindable data to the controller type.
func newBinder(elemType reflect.Type, values []interface{}) *binder {
if len(values) == 0 {
return nil
} }
b := &binder{values: values} b.values = append(b.values, value) // keep values.
b.fields = b.lookup(elemType)
b.match(value)
}
func (b *binder) isEmpty() bool {
// if nothing valid found return nil, so the caller // if nothing valid found return nil, so the caller
// can omit the binder. // can omit the binder.
if len(b.fields) == 0 && len(b.middleware) == 0 { if len(b.fields) == 0 && len(b.middleware) == 0 {
return nil return true
} }
return b return false
} }
func (b *binder) storeValueIfMiddleware(value reflect.Value) bool { func (b *binder) storeValueIfMiddleware(value reflect.Value) bool {
@ -55,41 +59,38 @@ func (b *binder) storeValueIfMiddleware(value reflect.Value) bool {
return false return false
} }
func (b *binder) lookup(elem reflect.Type) (fields []field.Field) { func (b *binder) match(v interface{}) {
for _, v := range b.values { value := reflect.ValueOf(v)
value := reflect.ValueOf(v) // handlers will be recognised as middleware, not struct fields.
// handlers will be recognised as middleware, not struct fields. // End-Developer has the option to call any handler inside
// End-Developer has the option to call any handler inside // the controller's `BeginRequest` and `EndRequest`, the
// the controller's `BeginRequest` and `EndRequest`, the // state is respected from the method handler already.
// state is respected from the method handler already. if b.storeValueIfMiddleware(value) {
if b.storeValueIfMiddleware(value) { // stored as middleware, continue to the next field, we don't have
// stored as middleware, continue to the next field, we don't have // to bind anything here.
// to bind anything here. return
continue
}
matcher := func(elemField reflect.StructField) bool {
// If the controller's field is interface then check
// if the given binded value implements that interface.
// i.e MovieController { Service services.MovieService /* interface */ }
// app.Controller("/", new(MovieController),
// services.NewMovieMemoryService(...))
//
// `services.NewMovieMemoryService` returns a `*MovieMemoryService`
// that implements the `MovieService` interface.
if elemField.Type.Kind() == reflect.Interface {
return value.Type().Implements(elemField.Type)
}
return elemField.Type == value.Type()
}
handler := func(f *field.Field) {
f.Value = value
}
fields = append(fields, field.LookupFields(elem, matcher, handler)...)
} }
return
matcher := func(elemField reflect.StructField) bool {
// If the controller's field is interface then check
// if the given binded value implements that interface.
// i.e MovieController { Service services.MovieService /* interface */ }
// app.Controller("/", new(MovieController),
// services.NewMovieMemoryService(...))
//
// `services.NewMovieMemoryService` returns a `*MovieMemoryService`
// that implements the `MovieService` interface.
if elemField.Type.Kind() == reflect.Interface {
return value.Type().Implements(elemField.Type)
}
return elemField.Type == value.Type()
}
handler := func(f *field.Field) {
f.Value = value
}
b.fields = append(b.fields, field.LookupFields(b.elemType, matcher, handler)...)
} }
func (b *binder) handle(c reflect.Value) { func (b *binder) handle(c reflect.Value) {

View File

@ -53,38 +53,49 @@ func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
// and add that. // and add that.
for i, n := 0, typ.NumMethod(); i < n; i++ { for i, n := 0, typ.NumMethod(); i < n; i++ {
m := typ.Method(i) m := typ.Method(i)
name := m.Name
for _, method := range availableMethods { if method, ok := FetchFuncInfo(m); ok {
possibleMethodFuncName := methodTitle(method) methods = append(methods, method)
if strings.Index(name, possibleMethodFuncName) == 0 {
trailing := ""
// if has chars after the method itself
if lname, lmethod := len(name), len(possibleMethodFuncName); lname > lmethod {
ch := rune(name[lmethod])
// if the next char is upper, otherise just skip the whole func info.
if unicode.IsUpper(ch) {
trailing = name[lmethod:]
} else {
continue
}
}
methodInfo := FuncInfo{
Name: name,
Trailing: trailing,
Type: m.Type,
HTTPMethod: method,
Index: m.Index,
}
methods = append(methods, methodInfo)
}
} }
} }
return return
} }
// FetchFuncInfo returns a FuncInfo based on the method of the controller.
func FetchFuncInfo(m reflect.Method) (FuncInfo, bool) {
name := m.Name
for _, method := range availableMethods {
possibleMethodFuncName := methodTitle(method)
if strings.Index(name, possibleMethodFuncName) == 0 {
trailing := ""
// if has chars after the method itself
if lname, lmethod := len(name), len(possibleMethodFuncName); lname > lmethod {
ch := rune(name[lmethod])
// if the next char is upper, otherise just skip the whole func info.
if unicode.IsUpper(ch) {
trailing = name[lmethod:]
} else {
continue
}
}
info := FuncInfo{
Name: name,
Trailing: trailing,
Type: m.Type,
HTTPMethod: method,
Index: m.Index,
}
return info, true
}
}
return FuncInfo{}, false
}
func methodTitle(httpMethod string) string { func methodTitle(httpMethod string) string {
httpMethodFuncName := strings.Title(strings.ToLower(httpMethod)) httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
return httpMethodFuncName return httpMethodFuncName

View File

@ -182,6 +182,7 @@ func (a *ast) paramValues(ctx context.Context) []reflect.Value {
l := len(a.paramKeys) l := len(a.paramKeys)
values := make([]reflect.Value, l, l) values := make([]reflect.Value, l, l)
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
paramKey := a.paramKeys[i] paramKey := a.paramKeys[i]
paramType := a.paramTypes[i] paramType := a.paramTypes[i]

View File

@ -31,20 +31,38 @@ func Resolve(typ reflect.Type) ([]MethodFunc, error) {
var methodFuncs []MethodFunc var methodFuncs []MethodFunc
infos := fetchInfos(typ) infos := fetchInfos(typ)
for _, info := range infos { for _, info := range infos {
parser := newFuncParser(info) methodFunc, err := ResolveMethodFunc(info)
a, err := parser.parse()
if r.AddErr(err) { if r.AddErr(err) {
continue continue
} }
methodFunc := MethodFunc{
RelPath: a.relPath,
FuncInfo: info,
MethodCall: buildMethodCall(a),
}
methodFuncs = append(methodFuncs, methodFunc) methodFuncs = append(methodFuncs, methodFunc)
} }
return methodFuncs, r.Return() return methodFuncs, r.Return()
} }
// ResolveMethodFunc resolves a single `MethodFunc` from a single `FuncInfo`.
func ResolveMethodFunc(info FuncInfo, paramKeys ...string) (MethodFunc, error) {
parser := newFuncParser(info)
a, err := parser.parse()
if err != nil {
return MethodFunc{}, err
}
if len(paramKeys) > 0 {
a.paramKeys = paramKeys
}
methodFunc := MethodFunc{
RelPath: a.relPath,
FuncInfo: info,
MethodCall: buildMethodCall(a),
}
/* TODO: split the method path and ast param keys, and all that
because now we want to use custom param keys but 'paramfirst' is set-ed.
*/
return methodFunc, nil
}

View File

@ -306,6 +306,7 @@ func (t *testControllerBindDeep) Get() {
} }
func TestControllerBind(t *testing.T) { func TestControllerBind(t *testing.T) {
app := iris.New() app := iris.New()
// app.Logger().SetLevel("debug")
t1, t2 := "my pointer title", "val title" t1, t2 := "my pointer title", "val title"
// test bind pointer to pointer of the correct type // test bind pointer to pointer of the correct type
@ -505,8 +506,8 @@ type testControllerActivateListener struct {
TitlePointer *testBindType TitlePointer *testBindType
} }
func (c *testControllerActivateListener) OnActivate(p *activator.ActivatePayload) { func (c *testControllerActivateListener) OnActivate(t *activator.TController) {
p.EnsureBindValue(&testBindType{ t.BindValue(&testBindType{
title: "default title", title: "default title",
}) })
} }

View File

@ -13,15 +13,14 @@ type (
// in order to be marked as safe content, to be rendered as html and not escaped. // in order to be marked as safe content, to be rendered as html and not escaped.
HTML = template.HTML HTML = template.HTML
// ActivatePayload contains the necessary information and the ability // TController contains the necessary controller's pre-serve information.
// to alt a controller's registration options, i.e the binder.
// //
// With `ActivatePayload` the `Controller` can register custom routes // With `TController` the `Controller` can register custom routes
// or modify the provided values that will be binded to the // or modify the provided values that will be binded to the
// controller later on. // controller later on.
// //
// Look the `mvc/activator#ActivatePayload` for its implementation. // Look the `mvc/activator#TController` for its implementation.
// //
// A shortcut for the `mvc/activator#ActivatePayload`, useful when `OnActivate` is being used. // A shortcut for the `mvc/activator#TController`, useful when `OnActivate` is being used.
ActivatePayload = activator.ActivatePayload TController = activator.TController
) )

View File

@ -24,8 +24,9 @@ type SessionController struct {
// every single time the dev registers a specific SessionController-based controller. // every single time the dev registers a specific SessionController-based controller.
// It makes sure that its "Manager" field is filled // It makes sure that its "Manager" field is filled
// even if the caller didn't provide any sessions manager via the `app.Controller` function. // even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) OnActivate(p *activator.ActivatePayload) { func (s *SessionController) OnActivate(t *activator.TController) {
if p.EnsureBindValue(defaultManager) { if !t.BindValueTypeExists(defaultManager) {
t.BindValue(defaultManager)
golog.Warnf(`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, golog.Warnf(`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
therefore this controller is using the default sessions manager instead. therefore this controller is using the default sessions manager instead.
Please refer to the documentation to learn how you can provide the session manager`) Please refer to the documentation to learn how you can provide the session manager`)

96
mvc2/controller.go Normal file
View File

@ -0,0 +1,96 @@
package mvc2
import (
// "reflect"
// "github.com/kataras/golog"
// "github.com/kataras/iris/context"
// // "github.com/kataras/iris/core/router"
// "github.com/kataras/iris/mvc/activator"
// "github.com/kataras/iris/mvc/activator/methodfunc"
)
// no, we will not make any changes to the controller's implementation
// let's no re-write the godlike code I wrote two months ago
// , just improve it by implementing the only one missing feature:
// bind/map/handle custom controller's functions to a custom router path
// like regexed.
//
// // BaseController is the interface that all controllers should implement.
// type BaseController interface {
// BeginRequest(ctx context.Context)
// EndRequest(ctx context.Context)
// }
// // type ControllerInitializer interface {
// // Init(r router.Party)
// // }
// // type activator struct {
// // Router router.Party
// // container *Mvc
// // }
// func registerController(m *Mvc, r router.Party, c BaseController) {
// }
// // ControllerHandler is responsible to dynamically bind a controller's functions
// // to the controller's http mechanism, can be used on the controller's `OnActivate` event.
// func ControllerHandler(controller activator.BaseController, funcName string) context.Handler {
// // we use funcName instead of an interface{} which can be safely binded with something like:
// // myController.HandleThis because we want to make sure that the end-developer
// // will make use a function of that controller that owns it because if not then
// // the BeginRequest and EndRequest will be called from other handler and also
// // the first input argument, which should be the controller itself may not be binded
// // to the current controller, all that are solved if end-dev knows what to do
// // but we can't bet on it.
// cVal := reflect.ValueOf(controller)
// elemTyp := reflect.TypeOf(controller) // with the pointer.
// m, exists := elemTyp.MethodByName(funcName)
// if !exists {
// golog.Errorf("mvc controller handler: function '%s' doesn't exist inside the '%s' controller",
// funcName, elemTyp.String())
// return nil
// }
// fn := cVal.MethodByName(funcName)
// if !fn.IsValid() {
// golog.Errorf("mvc controller handler: function '%s' inside the '%s' controller has not a valid value",
// funcName, elemTyp.String())
// return nil
// }
// info, ok := methodfunc.FetchFuncInfo(m)
// if !ok {
// golog.Errorf("mvc controller handler: could not resolve the func info from '%s'", funcName)
// return nil
// }
// methodFunc, err := methodfunc.ResolveMethodFunc(info)
// if err != nil {
// golog.Errorf("mvc controller handler: %v", err)
// return nil
// }
// m := New()
// m.In(controller) // bind the controller itself?
// /// TODO: first we must enable interface{} to be used as 'servetime input binder'
// // because it will try to match the type and add to its input if the
// // func input is that, and this binder will be available to every handler after that,
// // so it will be included to its 'in'.
// // MakeFuncInputBinder(func(ctx context.Context) interface{} {
// // // job here.
// // return controller
// // })
// h := m.Handler(fn.Interface())
// return func(ctx context.Context) {
// controller.BeginRequest(ctx)
// h(ctx)
// controller.EndRequest(ctx)
// }
// }

View File

@ -0,0 +1,81 @@
package mvc2_test
import (
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
"github.com/kataras/iris/mvc"
// "github.com/kataras/iris/mvc/activator/methodfunc"
//. "github.com/kataras/iris/mvc2"
)
type testController struct {
mvc.C
Service *TestServiceImpl
reqField string
}
func (c *testController) Get() string {
return "index"
}
func (c *testController) BeginRequest(ctx iris.Context) {
c.C.BeginRequest(ctx)
c.reqField = ctx.URLParam("reqfield")
}
func (c *testController) OnActivate(t *mvc.TController) {
t.Handle("GET", "/histatic", "HiStatic")
t.Handle("GET", "/hiservice", "HiService")
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
}
func (c *testController) HiStatic() string {
return c.reqField
}
func (c *testController) HiService() string {
return c.Service.Say("hi")
}
func (c *testController) HiParamBy(v string) string {
return v
}
func (c *testController) HiParamEmptyInputBy() string {
return "empty in but served with ctx.Params.Get('ps')=" + c.Ctx.Params().Get("ps")
}
func TestControllerHandler(t *testing.T) {
app := iris.New()
app.Controller("/", new(testController), &TestServiceImpl{prefix: "service:"})
e := httptest.New(t, app, httptest.LogLevel("debug"))
// test the index, is not part of the current package's implementation but do it.
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
// the important things now.
// this test ensures that the BeginRequest of the controller will be
// called correctly and also the controller is binded to the first input argument
// (which is the function's receiver, if any, in this case the *testController in go).
expectedReqField := "this is a request field filled by this url param"
e.GET("/histatic").WithQuery("reqfield", expectedReqField).Expect().Status(httptest.StatusOK).
Body().Equal(expectedReqField)
// this test makes sure that the binded values of the controller is handled correctly
// and can be used in a user-defined, dynamic "mvc handler".
e.GET("/hiservice").Expect().Status(httptest.StatusOK).
Body().Equal("service: hi")
// this worked with a temporary variadic on the resolvemethodfunc which is not
// correct design, I should split the path and params with the rest of implementation
// in order a simple template.Src can be given.
e.GET("/hiparam/value").Expect().Status(httptest.StatusOK).
Body().Equal("value")
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
Body().Equal("empty in but served with ctx.Params.Get('ps')=value")
}

View File

@ -28,22 +28,25 @@ func testBinderFunc(ctx iris.Context) testUserStruct {
// service // service
type ( type (
testService interface { // these TestService and TestServiceImpl could be in lowercase, unexported
// but the `Say` method should be exported however we have those exported
// because of the controller handler test.
TestService interface {
Say(string) string Say(string) string
} }
testServiceImpl struct { TestServiceImpl struct {
prefix string prefix string
} }
) )
func (s *testServiceImpl) Say(message string) string { func (s *TestServiceImpl) Say(message string) string {
return s.prefix + " " + message return s.prefix + " " + message
} }
var ( var (
// binders, as user-defined // binders, as user-defined
testBinderFuncUserStruct = testBinderFunc testBinderFuncUserStruct = testBinderFunc
testBinderService = &testServiceImpl{prefix: "say"} testBinderService = &TestServiceImpl{prefix: "say"}
testBinderFuncParam = func(ctx iris.Context) string { testBinderFuncParam = func(ctx iris.Context) string {
return ctx.Params().Get("param") return ctx.Params().Get("param")
} }
@ -56,7 +59,7 @@ var (
} }
// just one input arg, the service which is binded by the #2 service binder. // just one input arg, the service which is binded by the #2 service binder.
testConsumeServiceHandler = func(service testService) string { testConsumeServiceHandler = func(service TestService) string {
return service.Say("something") return service.Say("something")
} }
// just one input arg, a standar string which is binded by the #3 func(ctx) any binder. // just one input arg, a standar string which is binded by the #3 func(ctx) any binder.

View File

@ -35,7 +35,7 @@ func (m *Mvc) Child() *Mvc {
return child return child
} }
func (m *Mvc) In(binders ...interface{}) { func (m *Mvc) In(binders ...interface{}) *Mvc {
for _, binder := range binders { for _, binder := range binders {
typ := resolveBinderType(binder) typ := resolveBinderType(binder)
@ -58,6 +58,8 @@ func (m *Mvc) In(binders ...interface{}) {
m.binders = append(m.binders, b) m.binders = append(m.binders, b)
} }
return m
} }
func (m *Mvc) Handler(handler interface{}) context.Handler { func (m *Mvc) Handler(handler interface{}) context.Handler {

View File

@ -9,8 +9,7 @@ import (
) )
func TestMvcInAndHandler(t *testing.T) { func TestMvcInAndHandler(t *testing.T) {
m := New() m := New().In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
m.In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
var ( var (
h1 = m.Handler(testConsumeUserHandler) h1 = m.Handler(testConsumeUserHandler)