mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +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.
|
// 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...)
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
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.
|
// 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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
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
|
// 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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user