diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 475b68d7..5ce9150b 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -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...) diff --git a/core/router/party.go b/core/router/party.go index 16834468..ea0fecc6 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -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. // diff --git a/mvc/activator/activate_listener.go b/mvc/activator/activate_listener.go index 708d08fc..3e2a6120 100644 --- a/mvc/activator/activate_listener.go +++ b/mvc/activator/activate_listener.go @@ -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) } diff --git a/mvc/activator/activator.go b/mvc/activator/activator.go index 67ac4a39..4be839a4 100644 --- a/mvc/activator/activator.go +++ b/mvc/activator/activator.go @@ -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 + 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 - } - 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) + t.registerMethodFunc(m) } } +// Handle registers a method func but with a custom http method and relative route's path, +// it respects the rest of the controller's rules and guidelines. +func (t *TController) Handle(httpMethod, path, handlerFuncName string) bool { + cTyp := t.Type // with the pointer. + m, exists := cTyp.MethodByName(handlerFuncName) + if !exists { + golog.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", + handlerFuncName, t.FullName) + return false + } + + info := methodfunc.FuncInfo{ + Name: m.Name, + Trailing: m.Name, + Type: m.Type, + Index: m.Index, + HTTPMethod: httpMethod, + } + + tmpl, err := macro.Parse(path, macro.NewMap()) + if err != nil { + golog.Errorf("MVC: fail to parse the path for '%s.%s': %v", t.FullName, handlerFuncName, err) + return false + } + + paramKeys := make([]string, len(tmpl.Params), len(tmpl.Params)) + for i, param := range tmpl.Params { + paramKeys[i] = param.Name + } + + methodFunc, err := methodfunc.ResolveMethodFunc(info, paramKeys...) + if err != nil { + golog.Errorf("MVC: function '%s' inside the '%s' controller: %v", handlerFuncName, t.FullName, err) + return false + } + + methodFunc.RelPath = path + + t.registerMethodFunc(methodFunc) + return true +} + +// func (t *TController) getMethodFuncByName(funcName string) (methodfunc.MethodFunc, bool) { +// cVal := t.Value +// cTyp := t.Type // with the pointer. +// m, exists := cTyp.MethodByName(funcName) +// if !exists { +// golog.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", +// funcName, cTyp.String()) +// return methodfunc.MethodFunc{}, false +// } + +// fn := cVal.MethodByName(funcName) +// if !fn.IsValid() { +// golog.Errorf("MVC: function '%s' inside the '%s' controller has not a valid value", +// funcName, cTyp.String()) +// return methodfunc.MethodFunc{}, false +// } + +// info, ok := methodfunc.FetchFuncInfo(m) +// if !ok { +// golog.Errorf("MVC: could not resolve the func info from '%s'", funcName) +// return methodfunc.MethodFunc{}, false +// } + +// methodFunc, err := methodfunc.ResolveMethodFunc(info) +// if err != nil { +// golog.Errorf("MVC: %v", err) +// return methodfunc.MethodFunc{}, false +// } + +// return methodFunc, true +// } + +// // RegisterName registers a function by its name +// func (t *TController) RegisterName(funcName string) bool { +// methodFunc, ok := t.getMethodFuncByName(funcName) +// if !ok { +// return false +// } +// t.registerMethodFunc(methodFunc) +// return true +// } + +// RegisterFunc used by the caller to register the result routes. +type RegisterFunc func(httpMethod string, relPath string, handler ...context.Handler) + // Register receives a "controller", // a pointer of an instance which embeds the `Controller`, // the value of "baseControllerFieldName" should be `Controller`. func Register(controller BaseController, bindValues []interface{}, registerFunc RegisterFunc) error { - 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 } diff --git a/mvc/activator/binder.go b/mvc/activator/binder.go index b0aee3b1..81d64dd3 100644 --- a/mvc/activator/binder.go +++ b/mvc/activator/binder.go @@ -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,41 +59,38 @@ 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 { - value := reflect.ValueOf(v) - // handlers will be recognised as middleware, not struct fields. - // End-Developer has the option to call any handler inside - // the controller's `BeginRequest` and `EndRequest`, the - // state is respected from the method handler already. - if b.storeValueIfMiddleware(value) { - // stored as middleware, continue to the next field, we don't have - // to bind anything here. - 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)...) +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 + // the controller's `BeginRequest` and `EndRequest`, the + // state is respected from the method handler already. + if b.storeValueIfMiddleware(value) { + // stored as middleware, continue to the next field, we don't have + // to bind anything here. + return } - 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) { diff --git a/mvc/activator/methodfunc/func_info.go b/mvc/activator/methodfunc/func_info.go index 836dfa6c..ca9672cc 100644 --- a/mvc/activator/methodfunc/func_info.go +++ b/mvc/activator/methodfunc/func_info.go @@ -53,38 +53,49 @@ func fetchInfos(typ reflect.Type) (methods []FuncInfo) { // and add that. for i, n := 0, typ.NumMethod(); i < n; i++ { m := typ.Method(i) - 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 - } - } - - methodInfo := FuncInfo{ - Name: name, - Trailing: trailing, - Type: m.Type, - HTTPMethod: method, - Index: m.Index, - } - methods = append(methods, methodInfo) - } + 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 { + 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 { httpMethodFuncName := strings.Title(strings.ToLower(httpMethod)) return httpMethodFuncName diff --git a/mvc/activator/methodfunc/func_parser.go b/mvc/activator/methodfunc/func_parser.go index 511b2090..6ce24e7e 100644 --- a/mvc/activator/methodfunc/func_parser.go +++ b/mvc/activator/methodfunc/func_parser.go @@ -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] diff --git a/mvc/activator/methodfunc/methodfunc.go b/mvc/activator/methodfunc/methodfunc.go index f76a9a90..3a8f6065 100644 --- a/mvc/activator/methodfunc/methodfunc.go +++ b/mvc/activator/methodfunc/methodfunc.go @@ -31,20 +31,38 @@ 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 } - - methodFunc := MethodFunc{ - RelPath: a.relPath, - FuncInfo: info, - MethodCall: buildMethodCall(a), - } - 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, + 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 +} diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 67a1baa5..362249d5 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -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", }) } diff --git a/mvc/go19.go b/mvc/go19.go index 8473fa69..7467090f 100644 --- a/mvc/go19.go +++ b/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 ) diff --git a/mvc/session_controller.go b/mvc/session_controller.go index 06c17f6a..240a88be 100644 --- a/mvc/session_controller.go +++ b/mvc/session_controller.go @@ -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`) diff --git a/mvc2/controller.go b/mvc2/controller.go new file mode 100644 index 00000000..452da4b6 --- /dev/null +++ b/mvc2/controller.go @@ -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) +// } +// } diff --git a/mvc2/controller_handler_test.go b/mvc2/controller_handler_test.go new file mode 100644 index 00000000..d3e11ea7 --- /dev/null +++ b/mvc2/controller_handler_test.go @@ -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") + +} diff --git a/mvc2/handler_test.go b/mvc2/handler_test.go index 3a806728..bc8def58 100644 --- a/mvc2/handler_test.go +++ b/mvc2/handler_test.go @@ -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. diff --git a/mvc2/mvc.go b/mvc2/mvc.go index 9ae84450..e02bc43d 100644 --- a/mvc2/mvc.go +++ b/mvc2/mvc.go @@ -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 { diff --git a/mvc2/mvc_test.go b/mvc2/mvc_test.go index e8ab81b7..4bcbfe06 100644 --- a/mvc2/mvc_test.go +++ b/mvc2/mvc_test.go @@ -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)