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.
//
// 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...)

View File

@ -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.
//

View File

@ -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)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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]

View File

@ -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
}

View File

@ -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",
})
}

View File

@ -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
)

View File

@ -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
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
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.

View File

@ -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 {

View File

@ -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)