mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:26:26 +01:00
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:
parent
9d63e3194f
commit
dd5de52f34
|
@ -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.
|
||||
//
|
||||
// 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:
|
||||
// app.Handle(iris.MethodGet, "/user", userHandler)
|
||||
// app.Handle(iris.MethodGet, "/user/{id:int}", userHandler)
|
||||
// app.Handle(iris.MethodGet, "/user/me", userHandler)
|
||||
// app.Handle("GET", "/user", userHandler)
|
||||
// app.Handle("GET", "/user/{id:int}", userByIDHandler)
|
||||
// app.Handle("GET", "/user/me", userMeHandler)
|
||||
//
|
||||
// This method is used behind the scenes at the `Controller` function
|
||||
// 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,
|
||||
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
|
||||
r := api.HandleMany(method, relPath, handlers...)
|
||||
routes = append(routes, r...)
|
||||
|
|
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/router/macro"
|
||||
"github.com/kataras/iris/mvc/activator"
|
||||
)
|
||||
|
||||
|
@ -13,6 +14,12 @@ import (
|
|||
//
|
||||
// Look the "APIBuilder" for its implementation.
|
||||
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,
|
||||
// returns that new rich subrouter.
|
||||
//
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
package activator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// CallOnActivate simply calls the "controller"'s `OnActivate(*ActivatePayload)` function,
|
||||
// CallOnActivate simply calls the "controller"'s `OnActivate(*TController)` function,
|
||||
// if any.
|
||||
//
|
||||
// Look `activator.go#Register` and `ActivateListener` for more.
|
||||
func CallOnActivate(controller interface{},
|
||||
bindValues *[]interface{}, registerFunc RegisterFunc) {
|
||||
func CallOnActivate(controller interface{}, tController *TController) {
|
||||
|
||||
if ac, ok := controller.(ActivateListener); ok {
|
||||
p := &ActivatePayload{
|
||||
BindValues: bindValues,
|
||||
Handle: registerFunc,
|
||||
}
|
||||
ac.OnActivate(p)
|
||||
ac.OnActivate(tController)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,52 +18,13 @@ func CallOnActivate(controller interface{},
|
|||
// then the `OnActivate` function will be called ONCE, NOT in every request
|
||||
// but ONCE at the application's lifecycle.
|
||||
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
|
||||
// to register custom routes
|
||||
// or modify the provided values that will be binded to the
|
||||
// controller later on.
|
||||
//
|
||||
// Look `ActivatePayload` for more.
|
||||
OnActivate(*ActivatePayload)
|
||||
}
|
||||
|
||||
// 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
|
||||
// Look `TController` for more.
|
||||
OnActivate(*TController)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ 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"
|
||||
|
@ -32,6 +33,12 @@ type (
|
|||
// 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
|
||||
|
@ -69,37 +76,33 @@ type BaseController interface {
|
|||
}
|
||||
|
||||
// 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.
|
||||
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(reflect.ValueOf(base))
|
||||
val := reflect.Indirect(valPointer)
|
||||
|
||||
ctrlName := val.Type().Name()
|
||||
pkgPath := val.Type().PkgPath()
|
||||
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
|
||||
|
||||
// set the binder, can be nil this check at made at runtime.
|
||||
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{
|
||||
t := &TController{
|
||||
Name: ctrlName,
|
||||
FullName: fullName,
|
||||
Type: typ,
|
||||
Value: val,
|
||||
binder: binder,
|
||||
valuePtr: valPointer,
|
||||
Router: router,
|
||||
binder: &binder{elemType: typ.Elem()},
|
||||
modelController: model.Load(typ),
|
||||
persistenceController: persistence.Load(typ, val),
|
||||
}
|
||||
|
@ -107,12 +110,35 @@ func ActivateController(base BaseController, bindValues []interface{}) (TControl
|
|||
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 {
|
||||
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
|
||||
|
@ -123,7 +149,7 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
|
|||
return func(ctx context.Context) {
|
||||
// create a new controller instance of that type(>ptr).
|
||||
c := reflect.New(elem)
|
||||
if t.binder != nil {
|
||||
if hasBinder {
|
||||
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.
|
||||
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) {
|
||||
func (t *TController) registerMethodFunc(m methodfunc.MethodFunc) {
|
||||
var middleware context.Handlers
|
||||
|
||||
if t.binder != nil {
|
||||
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())
|
||||
// don't stop here.
|
||||
return
|
||||
}
|
||||
// range over the type info's method funcs,
|
||||
// build a new handler for each of these
|
||||
|
@ -194,35 +229,118 @@ func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
|
|||
// responsible to convert these into routes
|
||||
// and add them to router via the APIBuilder.
|
||||
for _, m := range methods {
|
||||
h := t.HandlerOf(m)
|
||||
if h == nil {
|
||||
golog.Warnf("MVC %s: nil method handler found for %s", t.FullName, m.Name)
|
||||
continue
|
||||
t.registerMethodFunc(m)
|
||||
}
|
||||
}
|
||||
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",
|
||||
// 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 {
|
||||
|
||||
CallOnActivate(controller, &bindValues, registerFunc)
|
||||
|
||||
t, err := ActivateController(controller, bindValues)
|
||||
t, err := newController(controller, registerFunc)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -8,7 +8,15 @@ import (
|
|||
"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 {
|
||||
elemType reflect.Type
|
||||
// values and fields are matched on the `match`.
|
||||
values []interface{}
|
||||
fields []field.Field
|
||||
|
||||
|
@ -17,28 +25,24 @@ type binder struct {
|
|||
middleware context.Handlers
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
func (b *binder) bind(value interface{}) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b := &binder{values: values}
|
||||
b.fields = b.lookup(elemType)
|
||||
b.values = append(b.values, value) // keep values.
|
||||
|
||||
b.match(value)
|
||||
}
|
||||
|
||||
func (b *binder) isEmpty() bool {
|
||||
// if nothing valid found return nil, so the caller
|
||||
// can omit the binder.
|
||||
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 {
|
||||
|
@ -55,8 +59,7 @@ func (b *binder) storeValueIfMiddleware(value reflect.Value) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (b *binder) lookup(elem reflect.Type) (fields []field.Field) {
|
||||
for _, v := range b.values {
|
||||
func (b *binder) match(v interface{}) {
|
||||
value := reflect.ValueOf(v)
|
||||
// handlers will be recognised as middleware, not struct fields.
|
||||
// End-Developer has the option to call any handler inside
|
||||
|
@ -65,7 +68,7 @@ func (b *binder) lookup(elem reflect.Type) (fields []field.Field) {
|
|||
if b.storeValueIfMiddleware(value) {
|
||||
// stored as middleware, continue to the next field, we don't have
|
||||
// to bind anything here.
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
matcher := func(elemField reflect.StructField) bool {
|
||||
|
@ -87,9 +90,7 @@ func (b *binder) lookup(elem reflect.Type) (fields []field.Field) {
|
|||
f.Value = value
|
||||
}
|
||||
|
||||
fields = append(fields, field.LookupFields(elem, matcher, handler)...)
|
||||
}
|
||||
return
|
||||
b.fields = append(b.fields, field.LookupFields(b.elemType, matcher, handler)...)
|
||||
}
|
||||
|
||||
func (b *binder) handle(c reflect.Value) {
|
||||
|
|
|
@ -53,6 +53,16 @@ func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
|
|||
// and add that.
|
||||
for i, n := 0, typ.NumMethod(); i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
|
||||
if method, ok := FetchFuncInfo(m); ok {
|
||||
methods = append(methods, method)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
@ -71,18 +81,19 @@ func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
methodInfo := FuncInfo{
|
||||
info := FuncInfo{
|
||||
Name: name,
|
||||
Trailing: trailing,
|
||||
Type: m.Type,
|
||||
HTTPMethod: method,
|
||||
Index: m.Index,
|
||||
}
|
||||
methods = append(methods, methodInfo)
|
||||
return info, true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return FuncInfo{}, false
|
||||
}
|
||||
|
||||
func methodTitle(httpMethod string) string {
|
||||
|
|
|
@ -182,6 +182,7 @@ func (a *ast) paramValues(ctx context.Context) []reflect.Value {
|
|||
|
||||
l := len(a.paramKeys)
|
||||
values := make([]reflect.Value, l, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
paramKey := a.paramKeys[i]
|
||||
paramType := a.paramTypes[i]
|
||||
|
|
|
@ -31,11 +31,27 @@ func Resolve(typ reflect.Type) ([]MethodFunc, error) {
|
|||
var methodFuncs []MethodFunc
|
||||
infos := fetchInfos(typ)
|
||||
for _, info := range infos {
|
||||
parser := newFuncParser(info)
|
||||
a, err := parser.parse()
|
||||
methodFunc, err := ResolveMethodFunc(info)
|
||||
if r.AddErr(err) {
|
||||
continue
|
||||
}
|
||||
methodFuncs = append(methodFuncs, methodFunc)
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -43,8 +59,10 @@ func Resolve(typ reflect.Type) ([]MethodFunc, error) {
|
|||
MethodCall: buildMethodCall(a),
|
||||
}
|
||||
|
||||
methodFuncs = append(methodFuncs, methodFunc)
|
||||
}
|
||||
/* 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 methodFuncs, r.Return()
|
||||
*/
|
||||
|
||||
return methodFunc, nil
|
||||
}
|
||||
|
|
|
@ -306,6 +306,7 @@ func (t *testControllerBindDeep) Get() {
|
|||
}
|
||||
func TestControllerBind(t *testing.T) {
|
||||
app := iris.New()
|
||||
// app.Logger().SetLevel("debug")
|
||||
|
||||
t1, t2 := "my pointer title", "val title"
|
||||
// test bind pointer to pointer of the correct type
|
||||
|
@ -505,8 +506,8 @@ type testControllerActivateListener struct {
|
|||
TitlePointer *testBindType
|
||||
}
|
||||
|
||||
func (c *testControllerActivateListener) OnActivate(p *activator.ActivatePayload) {
|
||||
p.EnsureBindValue(&testBindType{
|
||||
func (c *testControllerActivateListener) OnActivate(t *activator.TController) {
|
||||
t.BindValue(&testBindType{
|
||||
title: "default title",
|
||||
})
|
||||
}
|
||||
|
|
11
mvc/go19.go
11
mvc/go19.go
|
@ -13,15 +13,14 @@ type (
|
|||
// in order to be marked as safe content, to be rendered as html and not escaped.
|
||||
HTML = template.HTML
|
||||
|
||||
// ActivatePayload contains the necessary information and the ability
|
||||
// to alt a controller's registration options, i.e the binder.
|
||||
// TController contains the necessary controller's pre-serve information.
|
||||
//
|
||||
// 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
|
||||
// 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.
|
||||
ActivatePayload = activator.ActivatePayload
|
||||
// A shortcut for the `mvc/activator#TController`, useful when `OnActivate` is being used.
|
||||
TController = activator.TController
|
||||
)
|
||||
|
|
|
@ -24,8 +24,9 @@ type SessionController struct {
|
|||
// every single time the dev registers a specific SessionController-based controller.
|
||||
// It makes sure that its "Manager" field is filled
|
||||
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
||||
func (s *SessionController) OnActivate(p *activator.ActivatePayload) {
|
||||
if p.EnsureBindValue(defaultManager) {
|
||||
func (s *SessionController) OnActivate(t *activator.TController) {
|
||||
if !t.BindValueTypeExists(defaultManager) {
|
||||
t.BindValue(defaultManager)
|
||||
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.
|
||||
Please refer to the documentation to learn how you can provide the session manager`)
|
||||
|
|
96
mvc2/controller.go
Normal file
96
mvc2/controller.go
Normal 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)
|
||||
// }
|
||||
// }
|
81
mvc2/controller_handler_test.go
Normal file
81
mvc2/controller_handler_test.go
Normal 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")
|
||||
|
||||
}
|
|
@ -28,22 +28,25 @@ func testBinderFunc(ctx iris.Context) testUserStruct {
|
|||
|
||||
// service
|
||||
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
|
||||
}
|
||||
testServiceImpl struct {
|
||||
TestServiceImpl struct {
|
||||
prefix string
|
||||
}
|
||||
)
|
||||
|
||||
func (s *testServiceImpl) Say(message string) string {
|
||||
func (s *TestServiceImpl) Say(message string) string {
|
||||
return s.prefix + " " + message
|
||||
}
|
||||
|
||||
var (
|
||||
// binders, as user-defined
|
||||
testBinderFuncUserStruct = testBinderFunc
|
||||
testBinderService = &testServiceImpl{prefix: "say"}
|
||||
testBinderService = &TestServiceImpl{prefix: "say"}
|
||||
testBinderFuncParam = func(ctx iris.Context) string {
|
||||
return ctx.Params().Get("param")
|
||||
}
|
||||
|
@ -56,7 +59,7 @@ var (
|
|||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
// just one input arg, a standar string which is binded by the #3 func(ctx) any binder.
|
||||
|
|
|
@ -35,7 +35,7 @@ func (m *Mvc) Child() *Mvc {
|
|||
return child
|
||||
}
|
||||
|
||||
func (m *Mvc) In(binders ...interface{}) {
|
||||
func (m *Mvc) In(binders ...interface{}) *Mvc {
|
||||
for _, binder := range binders {
|
||||
typ := resolveBinderType(binder)
|
||||
|
||||
|
@ -58,6 +58,8 @@ func (m *Mvc) In(binders ...interface{}) {
|
|||
|
||||
m.binders = append(m.binders, b)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Mvc) Handler(handler interface{}) context.Handler {
|
||||
|
|
|
@ -9,8 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMvcInAndHandler(t *testing.T) {
|
||||
m := New()
|
||||
m.In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||
m := New().In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||
|
||||
var (
|
||||
h1 = m.Handler(testConsumeUserHandler)
|
||||
|
|
Loading…
Reference in New Issue
Block a user