diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go
index 69923ea2..ce2b8869 100644
--- a/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go
@@ -10,17 +10,19 @@ import (
// TodoController is our TODO app's web controller.
type TodoController struct {
- service todo.Service
+ Service todo.Service
- session *sessions.Session
+ Session *sessions.Session
}
-// OnActivate called once before the server ran, can bind custom
-// things to the controller.
-func (c *TodoController) OnActivate(ca *mvc.ControllerActivator) {
+// BeforeActivate called once before the server ran, and before
+// the routes and dependency binder builded.
+// You can bind custom things to the controller, add new methods, add middleware,
+// add dependencies to the struct or the method(s) and more.
+func (c *TodoController) BeforeActivate(ca *mvc.ControllerActivator) {
// this could be binded to a controller's function input argument
// if any, or struct field if any:
- ca.Bind(func(ctx iris.Context) todo.Item {
+ ca.Dependencies.Add(func(ctx iris.Context) todo.Item {
// ctx.ReadForm(&item)
var (
owner = ctx.PostValue("owner")
@@ -35,25 +37,30 @@ func (c *TodoController) OnActivate(ca *mvc.ControllerActivator) {
}
})
+ // ca.Router.Use(...).Done(...).Layout(...)
+ // TODO:(?)
+ // m := ca.Method("PutCompleteBy")
+ // m.Route.Use(...).Done(...) <- we don't have the route here but I can find something to solve this.
+ // m.Dependencies.Add(...)
}
// Get handles the GET: /todo route.
func (c *TodoController) Get() []todo.Item {
- return c.service.GetByOwner(c.session.ID())
+ return c.Service.GetByOwner(c.Session.ID())
}
// PutCompleteBy handles the PUT: /todo/complete/{id:long} route.
func (c *TodoController) PutCompleteBy(id int64) int {
- item, found := c.service.GetByID(id)
+ item, found := c.Service.GetByID(id)
if !found {
return iris.StatusNotFound
}
- if item.OwnerID != c.session.ID() {
+ if item.OwnerID != c.Session.ID() {
return iris.StatusForbidden
}
- if !c.service.Complete(item) {
+ if !c.Service.Complete(item) {
return iris.StatusBadRequest
}
@@ -62,11 +69,11 @@ func (c *TodoController) PutCompleteBy(id int64) int {
// Post handles the POST: /todo route.
func (c *TodoController) Post(newItem todo.Item) int {
- if newItem.OwnerID != c.session.ID() {
+ if newItem.OwnerID != c.Session.ID() {
return iris.StatusForbidden
}
- if err := c.service.Save(newItem); err != nil {
+ if err := c.Service.Save(newItem); err != nil {
return iris.StatusBadRequest
}
return iris.StatusOK
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index 5ce9150b..7105e9fc 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -10,7 +10,6 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro"
- "github.com/kataras/iris/mvc/activator"
)
const (
@@ -478,85 +477,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
return
}
-// Controller registers a `Controller` instance and returns the registered Routes.
-// The "controller" receiver should embed a field of `Controller` in order
-// to be compatible Iris `Controller`.
-//
-// It's just an alternative way of building an API for a specific
-// path, the controller can register all type of http methods.
-//
-// Keep note that controllers are bit slow
-// because of the reflection use however it's as fast as possible because
-// it does preparation before the serve-time handler but still
-// remains slower than the low-level handlers
-// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
-//
-//
-// All fields that are tagged with iris:"persistence"` or binded
-// are being persistence and kept the same between the different requests.
-//
-// An Example Controller can be:
-//
-// type IndexController struct {
-// Controller
-// }
-//
-// func (c *IndexController) Get() {
-// c.Tmpl = "index.html"
-// c.Data["title"] = "Index page"
-// c.Data["message"] = "Hello world!"
-// }
-//
-// Usage: app.Controller("/", new(IndexController))
-//
-//
-// Another example with bind:
-//
-// type UserController struct {
-// Controller
-//
-// DB *DB
-// CreatedAt time.Time
-//
-// }
-//
-// // Get serves using the User controller when HTTP Method is "GET".
-// func (c *UserController) Get() {
-// c.Tmpl = "user/index.html"
-// c.Data["title"] = "User Page"
-// c.Data["username"] = "kataras " + c.Params.Get("userid")
-// c.Data["connstring"] = c.DB.Connstring
-// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
-// }
-//
-// Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
-// Note: Binded values of context.Handler type are being recognised as middlewares by the router.
-//
-// Read more at `/mvc#Controller`.
-func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController,
- bindValues ...interface{}) (routes []*Route) {
-
- registerFunc := func(method string, ifRelPath string, handlers ...context.Handler) {
- relPath := relativePath + ifRelPath
- r := api.HandleMany(method, relPath, handlers...)
- routes = append(routes, r...)
- }
-
- // bind any values to the controller's relative fields
- // and set them on each new request controller,
- // binder is an alternative method
- // of the persistence data control which requires the
- // user already set the values manually to controller's fields
- // and tag them with `iris:"persistence"`.
- //
- // don't worry it will never be handled if empty values.
- if err := activator.Register(controller, bindValues, registerFunc); err != nil {
- api.reporter.Add("%v for path: '%s'", err, relativePath)
- }
-
- return
-}
-
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
// which can be changed.
var StaticCacheDuration = 20 * time.Second
diff --git a/core/router/party.go b/core/router/party.go
index 84b17fb0..f1a1a9d0 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -4,7 +4,6 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro"
- "github.com/kataras/iris/mvc/activator"
)
// Party is here to separate the concept of
@@ -130,63 +129,6 @@ type Party interface {
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
Any(registeredPath string, handlers ...context.Handler) []*Route
- // Controller registers a `Controller` instance and returns the registered Routes.
- // The "controller" receiver should embed a field of `Controller` in order
- // to be compatible Iris `Controller`.
- //
- // It's just an alternative way of building an API for a specific
- // path, the controller can register all type of http methods.
- //
- // Keep note that controllers are bit slow
- // because of the reflection use however it's as fast as possible because
- // it does preparation before the serve-time handler but still
- // remains slower than the low-level handlers
- // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
- //
- //
- // All fields that are tagged with iris:"persistence"` or binded
- // are being persistence and kept the same between the different requests.
- //
- // An Example Controller can be:
- //
- // type IndexController struct {
- // Controller
- // }
- //
- // func (c *IndexController) Get() {
- // c.Tmpl = "index.html"
- // c.Data["title"] = "Index page"
- // c.Data["message"] = "Hello world!"
- // }
- //
- // Usage: app.Controller("/", new(IndexController))
- //
- //
- // Another example with bind:
- //
- // type UserController struct {
- // Controller
- //
- // DB *DB
- // CreatedAt time.Time
- //
- // }
- //
- // // Get serves using the User controller when HTTP Method is "GET".
- // func (c *UserController) Get() {
- // c.Tmpl = "user/index.html"
- // c.Data["title"] = "User Page"
- // c.Data["username"] = "kataras " + c.Params.Get("userid")
- // c.Data["connstring"] = c.DB.Connstring
- // c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
- // }
- //
- // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
- // Note: Binded values of context.Handler type are being recognised as middlewares by the router.
- //
- // Read more at `/mvc#Controller`.
- Controller(relativePath string, controller activator.BaseController, bindValues ...interface{}) []*Route
-
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
diff --git a/go19.go b/go19.go
index 4df85681..8a16665e 100644
--- a/go19.go
+++ b/go19.go
@@ -6,7 +6,6 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/host"
"github.com/kataras/iris/core/router"
- "github.com/kataras/iris/mvc"
)
type (
@@ -48,132 +47,4 @@ type (
//
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
Party = router.Party
-
- // Controller is the base controller for the high level controllers instances.
- //
- // This base controller is used as an alternative way of building
- // APIs, the controller can register all type of http methods.
- //
- // Keep note that controllers are bit slow
- // because of the reflection use however it's as fast as possible because
- // it does preparation before the serve-time handler but still
- // remains slower than the low-level handlers
- // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
- //
- //
- // All fields that are tagged with iris:"persistence"`
- // are being persistence and kept between the different requests,
- // meaning that these data will not be reset-ed on each new request,
- // they will be the same for all requests.
- //
- // An Example Controller can be:
- //
- // type IndexController struct {
- // iris.Controller
- // }
- //
- // func (c *IndexController) Get() {
- // c.Tmpl = "index.html"
- // c.Data["title"] = "Index page"
- // c.Data["message"] = "Hello world!"
- // }
- //
- // Usage: app.Controller("/", new(IndexController))
- //
- //
- // Another example with persistence data:
- //
- // type UserController struct {
- // iris.Controller
- //
- // CreatedAt time.Time `iris:"persistence"`
- // Title string `iris:"persistence"`
- // DB *DB `iris:"persistence"`
- // }
- //
- // // Get serves using the User controller when HTTP Method is "GET".
- // func (c *UserController) Get() {
- // c.Tmpl = "user/index.html"
- // c.Data["title"] = c.Title
- // c.Data["username"] = "kataras " + c.Params.Get("userid")
- // c.Data["connstring"] = c.DB.Connstring
- // c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
- // }
- //
- // Usage: app.Controller("/user/{id:int}", &UserController{
- // CreatedAt: time.Now(),
- // Title: "User page",
- // DB: yourDB,
- // })
- //
- // Look `core/router#APIBuilder#Controller` method too.
- //
- // A shortcut for the `mvc#Controller`,
- // useful when `app.Controller` method is being used.
- //
- // A Controller can be declared by importing
- // the "github.com/kataras/iris/mvc"
- // package for machines that have not installed go1.9 yet.
- Controller = mvc.Controller
- // SessionController is a simple `Controller` implementation
- // which requires a binded session manager in order to give
- // direct access to the current client's session via its `Session` field.
- SessionController = mvc.SessionController
- // C is the lightweight BaseController type as an alternative of the `Controller` struct type.
- // It contains only the Name of the controller and the Context, it's the best option
- // to balance the performance cost reflection uses
- // if your controller uses the new func output values dispatcher feature;
- // func(c *ExampleController) Get() string |
- // (string, string) |
- // (string, int) |
- // int |
- // (int, string |
- // (string, error) |
- // error |
- // (int, error) |
- // (customStruct, error) |
- // customStruct |
- // (customStruct, int) |
- // (customStruct, string) |
- // Result or (Result, error)
- // where Get is an HTTP Method func.
- //
- // Look `core/router#APIBuilder#Controller` method too.
- //
- // A shortcut for the `mvc#C`,
- // useful when `app.Controller` method is being used.
- //
- // A C controller can be declared by importing
- // the "github.com/kataras/iris/mvc" as well.
- C = mvc.C
- // Response completes the `mvc/activator/methodfunc.Result` interface.
- // It's being used as an alternative return value which
- // wraps the status code, the content type, a content as bytes or as string
- // and an error, it's smart enough to complete the request and send the correct response to the client.
- //
- // A shortcut for the `mvc#Response`,
- // useful when return values from method functions, i.e
- // GetHelloworld() iris.Response { iris.Response{ Text:"Hello World!", Code: 200 }}
- Response = mvc.Response
- // View completes the `mvc/activator/methodfunc.Result` interface.
- // It's being used as an alternative return value which
- // wraps the template file name, layout, (any) view data, status code and error.
- // It's smart enough to complete the request and send the correct response to the client.
- //
- // A shortcut for the `mvc#View`,
- // useful when return values from method functions, i.e
- // GetUser() iris.View { iris.View{ Name:"user.html", Data: currentUser } }
- View = mvc.View
- // Result is a response dispatcher.
- // All types that complete this interface
- // can be returned as values from the method functions.
- // A shortcut for the `mvc#Result` which is a shortcut for `mvc/activator/methodfunc#Result`,
- // useful when return values from method functions, i.e
- // GetUser() iris.Result { iris.Response{} or a custom iris.Result }
- // Can be also used for the TryResult function.
- Result = mvc.Result
)
-
-// Try is a shortcut for the function `mvc.Try` result.
-// See more at `mvc#Try` documentation.
-var Try = mvc.Try
diff --git a/mvc/activator/activate_listener.go b/mvc/activator/activate_listener.go
deleted file mode 100644
index 3e2a6120..00000000
--- a/mvc/activator/activate_listener.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package activator
-
-// CallOnActivate simply calls the "controller"'s `OnActivate(*TController)` function,
-// if any.
-//
-// Look `activator.go#Register` and `ActivateListener` for more.
-func CallOnActivate(controller interface{}, tController *TController) {
-
- if ac, ok := controller.(ActivateListener); ok {
- ac.OnActivate(tController)
- }
-}
-
-// ActivateListener is an interface which should be declared
-// on a Controller which needs to register or change the bind values
-// that the caller-"user" has been passed to; via the `app.Controller`.
-// If that interface is completed by a controller
-// 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 `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 `TController` for more.
- OnActivate(*TController)
-}
diff --git a/mvc/activator/activator.go b/mvc/activator/activator.go
deleted file mode 100644
index 4be839a4..00000000
--- a/mvc/activator/activator.go
+++ /dev/null
@@ -1,346 +0,0 @@
-package activator
-
-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"
-
- "github.com/kataras/golog"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/errors"
-)
-
-type (
- // TController is the type of the controller,
- // it contains all the necessary information to load
- // and serve the controller to the outside world,
- // think it as a "supervisor" of your Controller which
- // cares about you.
- TController struct {
- // The name of the front controller struct.
- Name string
- // FullName it's the last package path segment + "." + the Name.
- // i.e: if login-example/user/controller.go, the FullName is "user.Controller".
- FullName string
- // the type of the user/dev's "c" controller (interface{}).
- Type reflect.Type
- // it's the first passed value of the controller instance,
- // 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
- }
-)
-
-// the parent package should complete this "interface"
-// it's not exported, so their functions
-// but reflect doesn't care about it, so we are ok
-// to compare the type of the base controller field
-// with this "ctrl", see `buildTypeInfo` and `buildMethodHandler`.
-
-var (
- // ErrMissingControllerInstance is a static error which fired from `Controller` when
- // the passed "c" instnace is not a valid type of `Controller` or `C`.
- ErrMissingControllerInstance = errors.New("controller should have a field of mvc.Controller or mvc.C type")
- // ErrInvalidControllerType fired when the "Controller" field is not
- // the correct type.
- ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
-)
-
-// BaseController is the controller interface,
-// which the main request `Controller` will implement automatically.
-// End-User doesn't need to have any knowledge of this if she/he doesn't want to implement
-// a new Controller type.
-// Controller looks the whole flow as one handler, so `ctx.Next`
-// inside `BeginRequest` is not be respected.
-// Alternative way to check if a middleware was procceed successfully
-// and called its `ctx.Next` is the `ctx.Proceed(handler) bool`.
-// You have to navigate to the `context/context#Proceed` function's documentation.
-type BaseController interface {
- SetName(name string)
- BeginRequest(ctx context.Context)
- EndRequest(ctx context.Context)
-}
-
-// ActivateController returns a new controller type info description.
-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(valPointer)
-
- ctrlName := val.Type().Name()
- pkgPath := val.Type().PkgPath()
- fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
-
- t := &TController{
- Name: ctrlName,
- FullName: fullName,
- Type: typ,
- Value: val,
- valuePtr: valPointer,
- Router: router,
- binder: &binder{elemType: typ.Elem()},
- modelController: model.Load(typ),
- persistenceController: persistence.Load(typ, val),
- }
-
- 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 {
- var (
- // shared, per-controller
- elem = t.Type.Elem()
- ctrlName = t.Name
- hasBinder = !t.binder.isEmpty()
-
- hasPersistenceData = t.persistenceController != nil
- hasModels = t.modelController != nil
- // per-handler
- handleRequest = methodFunc.MethodCall
- )
-
- return func(ctx context.Context) {
- // create a new controller instance of that type(>ptr).
- c := reflect.New(elem)
- if hasBinder {
- t.binder.handle(c)
- }
-
- b := c.Interface().(BaseController)
- b.SetName(ctrlName)
-
- // if has persistence data then set them
- // before the end-developer's handler in order to be available there.
- if hasPersistenceData {
- t.persistenceController.Handle(c)
- }
-
- // if previous (binded) handlers stopped the execution
- // we should know that.
- if ctx.IsStopped() {
- return
- }
-
- // init the request.
- b.BeginRequest(ctx)
- if ctx.IsStopped() { // if begin request stopped the execution
- return
- }
-
- // the most important, execute the specific function
- // from the controller that is responsible to handle
- // this request, by method and path.
- handleRequest(ctx, c.Method(methodFunc.Index))
- // if had models, set them after the end-developer's handler.
- if hasModels {
- t.modelController.Handle(ctx, c)
- }
-
- // end the request, don't check for stopped because this does the actual writing
- // if no response written already.
- b.EndRequest(ctx)
- }
-}
-
-func (t *TController) registerMethodFunc(m methodfunc.MethodFunc) {
- var middleware context.Handlers
-
- 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())
- return
- }
- // range over the type info's method funcs,
- // build a new handler for each of these
- // methods and register them to their
- // http methods using the registerFunc, which is
- // responsible to convert these into routes
- // and add them to router via the APIBuilder.
- for _, m := range methods {
- 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 {
-
- t, err := newController(controller, registerFunc)
- if err != nil {
- return err
- }
-
- 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
deleted file mode 100644
index 81d64dd3..00000000
--- a/mvc/activator/binder.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package activator
-
-import (
- "reflect"
-
- "github.com/kataras/iris/mvc/activator/field"
-
- "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
-
- // saves any middleware that may need to be passed to the router,
- // statically, to gain performance.
- middleware context.Handlers
-}
-
-func (b *binder) bind(value interface{}) {
- if value == nil {
- return
- }
-
- 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 true
- }
-
- return false
-}
-
-func (b *binder) storeValueIfMiddleware(value reflect.Value) bool {
- if value.CanInterface() {
- if m, ok := value.Interface().(context.Handler); ok {
- b.middleware = append(b.middleware, m)
- return true
- }
- if m, ok := value.Interface().(func(context.Context)); ok {
- b.middleware = append(b.middleware, m)
- return true
- }
- }
- return false
-}
-
-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
- }
-
- 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) {
- // we could make check for middlewares here but
- // these could easly be used outside of the controller
- // so we don't have to initialize a controller to call them
- // so they don't belong actually here, we will register them to the
- // router itself, before the controller's handler to gain performance,
- // look `activator.go#RegisterMethodHandlers` for more.
-
- elem := c.Elem() // controller should always be a pointer at this state
- for _, f := range b.fields {
- f.SendTo(elem)
- }
-}
diff --git a/mvc/activator/field/field.go b/mvc/activator/field/field.go
deleted file mode 100644
index 1a3d0413..00000000
--- a/mvc/activator/field/field.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package field
-
-import (
- "reflect"
-)
-
-// Field is a controller's field
-// contains all the necessary, internal, information
-// to work with.
-type Field struct {
- Name string // the field's original name
- // but if a tag with `name: "other"`
- // exist then this fill is filled, otherwise it's the same as the Name.
- TagName string
- Index int
- Type reflect.Type
- Value reflect.Value
-
- embedded *Field
-}
-
-// GetIndex returns all the "dimensions"
-// of the controller struct field's position that this field is referring to,
-// recursively.
-// Usage: elem.FieldByIndex(field.getIndex())
-// for example the {0,1} means that the field is on the second field of the first's
-// field of this struct.
-func (ff Field) GetIndex() []int {
- deepIndex := []int{ff.Index}
-
- if emb := ff.embedded; emb != nil {
- deepIndex = append(deepIndex, emb.GetIndex()...)
- }
-
- return deepIndex
-}
-
-// GetType returns the type of the referring field, recursively.
-func (ff Field) GetType() reflect.Type {
- typ := ff.Type
- if emb := ff.embedded; emb != nil {
- return emb.GetType()
- }
-
- return typ
-}
-
-// GetFullName returns the full name of that field
-// i.e: UserController.SessionController.Manager,
-// it's useful for debugging only.
-func (ff Field) GetFullName() string {
- name := ff.Name
-
- if emb := ff.embedded; emb != nil {
- return name + "." + emb.GetFullName()
- }
-
- return name
-}
-
-// GetTagName returns the tag name of the referring field
-// recursively.
-func (ff Field) GetTagName() string {
- name := ff.TagName
-
- if emb := ff.embedded; emb != nil {
- return emb.GetTagName()
- }
-
- return name
-}
-
-// checkVal checks if that value
-// is valid to be set-ed to the new controller's instance.
-// Used on binder.
-func checkVal(val reflect.Value) bool {
- return val.IsValid() && (val.Kind() == reflect.Ptr && !val.IsNil()) && val.CanInterface()
-}
-
-// GetValue returns a valid value of the referring field, recursively.
-func (ff Field) GetValue() interface{} {
- if ff.embedded != nil {
- return ff.embedded.GetValue()
- }
-
- if checkVal(ff.Value) {
- return ff.Value.Interface()
- }
-
- return "undefinied value"
-}
-
-// SendTo should be used when this field or its embedded
-// has a Value on it.
-// It sets the field's value to the "elem" instance, it's the new controller.
-func (ff Field) SendTo(elem reflect.Value) {
- // note:
- // we don't use the getters here
- // because we do recursively search by our own here
- // to be easier to debug if ever needed.
- if embedded := ff.embedded; embedded != nil {
- if ff.Index >= 0 {
- embedded.SendTo(elem.Field(ff.Index))
- }
- return
- }
- elemField := elem.Field(ff.Index)
- if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
- return
- }
-
- elemField.Set(ff.Value)
-}
-
-// lookupTagName checks if the "elemField" struct's field
-// contains a tag `name`, if it contains then it returns its value
-// otherwise returns the field's original Name.
-func lookupTagName(elemField reflect.StructField) string {
- vname := elemField.Name
-
- if taggedName, ok := elemField.Tag.Lookup("name"); ok {
- vname = taggedName
- }
- return vname
-}
-
-// LookupFields iterates all "elem"'s fields and its fields
-// if structs, recursively.
-// Compares them to the "matcher", if they passed
-// then it executes the "handler" if any,
-// the handler can change the field as it wants to.
-//
-// It finally returns that collection of the valid fields, can be empty.
-func LookupFields(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*Field)) (fields []Field) {
- for i, n := 0, elem.NumField(); i < n; i++ {
- elemField := elem.Field(i)
- if matcher(elemField) {
- field := Field{
- Index: i,
- Name: elemField.Name,
- TagName: lookupTagName(elemField),
- Type: elemField.Type,
- }
-
- if handler != nil {
- handler(&field)
- }
-
- // we area inside the correct type
- fields = append(fields, field)
- continue
- }
-
- f := lookupStructField(elemField.Type, matcher, handler)
- if f != nil {
- fields = append(fields, Field{
- Index: i,
- Name: elemField.Name,
- Type: elemField.Type,
- embedded: f,
- })
- }
-
- }
- return
-}
-
-// lookupStructField is here to search for embedded field only,
-// is working with the "lookupFields".
-// We could just one one function
-// for both structured (embedded) fields and normal fields
-// but we keep that as it's, a new function like this
-// is easier for debugging, if ever needed.
-func lookupStructField(elem reflect.Type, matcher func(reflect.StructField) bool, handler func(*Field)) *Field {
- // fmt.Printf("lookup struct for elem: %s\n", elem.Name())
- // ignore if that field is not a struct
- if elem.Kind() != reflect.Struct {
- return nil
- }
-
- // search by fields.
- for i, n := 0, elem.NumField(); i < n; i++ {
- elemField := elem.Field(i)
-
- if matcher(elemField) {
- // we area inside the correct type.
- f := &Field{
- Index: i,
- Name: elemField.Name,
- TagName: lookupTagName(elemField),
- Type: elemField.Type,
- }
-
- if handler != nil {
- handler(f)
- }
-
- return f
- }
-
- // if field is struct and the value is struct
- // then try inside its fields for a compatible
- // field type.
- if elemField.Type.Kind() == reflect.Struct { // 3-level
- elemFieldEmb := elem.Field(i)
- f := lookupStructField(elemFieldEmb.Type, matcher, handler)
- if f != nil {
- fp := &Field{
- Index: i,
- Name: elemFieldEmb.Name,
- TagName: lookupTagName(elemFieldEmb),
- Type: elemFieldEmb.Type,
- embedded: f,
- }
- return fp
- }
- }
- }
- return nil
-}
diff --git a/mvc/activator/methodfunc/func_caller.go b/mvc/activator/methodfunc/func_caller.go
deleted file mode 100644
index 108b40c0..00000000
--- a/mvc/activator/methodfunc/func_caller.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package methodfunc
-
-import (
- "reflect"
-
- "github.com/kataras/iris/context"
-)
-
-// buildMethodCall builds the method caller.
-// We have repeated code here but it's the only way
-// to support more than one input arguments without performance cost compared to previous implementation.
-// so it's hard-coded written to check the length of input args and their types.
-func buildMethodCall(a *ast) func(ctx context.Context, f reflect.Value) {
- // if func input arguments are more than one then
- // use the Call method (slower).
- return func(ctx context.Context, f reflect.Value) {
- DispatchFuncResult(ctx, f.Call(a.paramValues(ctx)))
- }
-}
diff --git a/mvc/activator/methodfunc/func_info.go b/mvc/activator/methodfunc/func_info.go
deleted file mode 100644
index ca9672cc..00000000
--- a/mvc/activator/methodfunc/func_info.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package methodfunc
-
-import (
- "reflect"
- "strings"
- "unicode"
-)
-
-var availableMethods = [...]string{
- "ANY", // will be registered using the `core/router#APIBuilder#Any`
- "ALL", // same as ANY
- "NONE", // offline route
- // valid http methods
- "GET",
- "POST",
- "PUT",
- "DELETE",
- "CONNECT",
- "HEAD",
- "PATCH",
- "OPTIONS",
- "TRACE",
-}
-
-// FuncInfo is part of the `TController`,
-// it contains the index for a specific http method,
-// taken from user's controller struct.
-type FuncInfo struct {
- // Name is the map function name.
- Name string
- // Trailing is not empty when the Name contains
- // characters after the titled method, i.e
- // if Name = Get -> empty
- // if Name = GetLogin -> Login
- // if Name = GetUserPost -> UserPost
- Trailing string
-
- // The Type of the method, includes the receivers.
- Type reflect.Type
-
- // Index is the index of this function inside the controller type.
- Index int
- // HTTPMethod is the original http method that this
- // function should be registered to and serve.
- // i.e "GET","POST","PUT"...
- HTTPMethod string
-}
-
-// or resolve methods
-func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
- // search the entire controller
- // for any compatible method function
- // 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 {
- 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_lexer.go b/mvc/activator/methodfunc/func_lexer.go
deleted file mode 100644
index e06352a7..00000000
--- a/mvc/activator/methodfunc/func_lexer.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package methodfunc
-
-import (
- "unicode"
-)
-
-const (
- tokenBy = "By"
- tokenWildcard = "Wildcard" // should be followed by "By",
-)
-
-// word lexer, not characters.
-type lexer struct {
- words []string
- cur int
-}
-
-func newLexer(s string) *lexer {
- l := new(lexer)
- l.reset(s)
- return l
-}
-
-func (l *lexer) reset(trailing string) {
- l.cur = -1
- var words []string
- if trailing != "" {
- end := len(trailing)
- start := -1
-
- for i, n := 0, end; i < n; i++ {
- c := rune(trailing[i])
- if unicode.IsUpper(c) {
- // it doesn't count the last uppercase
- if start != -1 {
- end = i
- words = append(words, trailing[start:end])
- }
- start = i
- continue
- }
- end = i + 1
- }
-
- if end > 0 && len(trailing) >= end {
- words = append(words, trailing[start:end])
- }
- }
-
- l.words = words
-}
-
-func (l *lexer) next() (w string) {
- cur := l.cur + 1
-
- if w = l.peek(cur); w != "" {
- l.cur++
- }
-
- return
-}
-
-func (l *lexer) skip() {
- if cur := l.cur + 1; cur < len(l.words) {
- l.cur = cur
- } else {
- l.cur = len(l.words) - 1
- }
-}
-
-func (l *lexer) peek(idx int) string {
- if idx < len(l.words) {
- return l.words[idx]
- }
- return ""
-}
-
-func (l *lexer) peekNext() (w string) {
- return l.peek(l.cur + 1)
-}
-
-func (l *lexer) peekPrev() (w string) {
- if l.cur > 0 {
- cur := l.cur - 1
- w = l.words[cur]
- }
-
- return w
-}
diff --git a/mvc/activator/methodfunc/func_parser.go b/mvc/activator/methodfunc/func_parser.go
deleted file mode 100644
index 6ce24e7e..00000000
--- a/mvc/activator/methodfunc/func_parser.go
+++ /dev/null
@@ -1,213 +0,0 @@
-package methodfunc
-
-import (
- "errors"
- "fmt"
- "reflect"
- "strings"
-
- "github.com/kataras/iris/context"
-)
-
-var posWords = map[int]string{
- 0: "",
- 1: "first",
- 2: "second",
- 3: "third",
- 4: "forth",
- 5: "five",
- 6: "sixth",
- 7: "seventh",
- 8: "eighth",
- 9: "ninth",
-}
-
-func genParamKey(argIdx int) string {
- return "param" + posWords[argIdx] // paramfirst, paramsecond...
-}
-
-const (
- paramTypeInt = "int"
- paramTypeLong = "long"
- paramTypeBoolean = "boolean"
- paramTypeString = "string"
- paramTypePath = "path"
-)
-
-var macroTypes = map[string]string{
- "int": paramTypeInt,
- "int64": paramTypeLong,
- "bool": paramTypeBoolean,
- "string": paramTypeString,
- // there is "path" param type but it's being captured "on-air"
- // "file" param type is not supported by the current implementation, yet
- // but if someone ask for it I'll implement it, it's easy.
-}
-
-type funcParser struct {
- info FuncInfo
- lexer *lexer
-}
-
-func newFuncParser(info FuncInfo) *funcParser {
- return &funcParser{
- info: info,
- lexer: newLexer(info.Trailing),
- }
-}
-
-func (p *funcParser) parse() (*ast, error) {
- a := new(ast)
- funcArgPos := 0
-
- for {
- w := p.lexer.next()
- if w == "" {
- break
- }
-
- if w == tokenBy {
- funcArgPos++ // starting with 1 because in typ.NumIn() the first is the struct receiver.
-
- // No need for these:
- // ByBy will act like /{param:type}/{param:type} as users expected
- // if func input arguments are there, else act By like normal path /by.
- //
- // if p.lexer.peekPrev() == tokenBy || typ.NumIn() == 1 { // ByBy, then act this second By like a path
- // a.relPath += "/" + strings.ToLower(w)
- // continue
- // }
-
- if err := p.parsePathParam(a, w, funcArgPos); err != nil {
- return nil, err
- }
-
- continue
- }
-
- a.relPath += "/" + strings.ToLower(w)
- }
-
- // This fixes a problem when developer misses to append the keyword `By`
- // to the method function when input arguments are declared (for path parameters binding).
- // We could just use it with `By` keyword but this is not a good practise
- // because what happens if we will become naive and declare something like
- // Get(id int) and GetBy(username string) or GetBy(id int) ? it's not working because of duplication of the path.
- // Docs are clear about that but we are humans, they may do a mistake by accident but
- // framework will not allow that.
- // So the best thing we can do to help prevent those errors is by printing that message
- // below to the developer.
- // Note: it should be at the end of the words loop because a.dynamic may be true later on.
- if numIn := p.info.Type.NumIn(); numIn > 1 && !a.dynamic {
- return nil, fmt.Errorf("found %d input arguments but keyword 'By' is missing from '%s'",
- // -1 because end-developer wants to know the actual input arguments, without the struct holder.
- numIn-1, p.info.Name)
- }
- return a, nil
-}
-
-func (p *funcParser) parsePathParam(a *ast, w string, funcArgPos int) error {
- typ := p.info.Type
-
- if typ.NumIn() <= funcArgPos {
- // old:
- // return nil, errors.New("keyword 'By' found but length of input receivers are not match for " +
- // p.info.Name)
-
- // By found but input arguments are not there, so act like /by path without restricts.
- a.relPath += "/" + strings.ToLower(w)
- return nil
- }
-
- var (
- paramKey = genParamKey(funcArgPos) // paramfirst, paramsecond...
- paramType = paramTypeString // default string
- )
-
- // string, int...
- goType := typ.In(funcArgPos).Name()
- nextWord := p.lexer.peekNext()
-
- if nextWord == tokenWildcard {
- p.lexer.skip() // skip the Wildcard word.
- paramType = paramTypePath
- } else if pType, ok := macroTypes[goType]; ok {
- // it's not wildcard, so check base on our available macro types.
- paramType = pType
- } else {
- return errors.New("invalid syntax for " + p.info.Name)
- }
-
- a.paramKeys = append(a.paramKeys, paramKey)
- a.paramTypes = append(a.paramTypes, paramType)
- // /{paramfirst:path}, /{paramfirst:long}...
- a.relPath += fmt.Sprintf("/{%s:%s}", paramKey, paramType)
- a.dynamic = true
-
- if nextWord == "" && typ.NumIn() > funcArgPos+1 {
- // By is the latest word but func is expected
- // more path parameters values, i.e:
- // GetBy(name string, age int)
- // The caller (parse) doesn't need to know
- // about the incremental funcArgPos because
- // it will not need it.
- return p.parsePathParam(a, nextWord, funcArgPos+1)
- }
-
- return nil
-}
-
-type ast struct {
- paramKeys []string // paramfirst, paramsecond... [0]
- paramTypes []string // string, int, long, path... [0]
- relPath string
- dynamic bool // when paramKeys (and paramTypes, are equal) > 0
-}
-
-// moved to func_caller#buildMethodcall, it's bigger and with repeated code
-// than this, below function but it's faster.
-// func (a *ast) MethodCall(ctx context.Context, f reflect.Value) {
-// if a.dynamic {
-// f.Call(a.paramValues(ctx))
-// return
-// }
-//
-// f.Interface().(func())()
-// }
-
-func (a *ast) paramValues(ctx context.Context) []reflect.Value {
- if !a.dynamic {
- return nil
- }
-
- l := len(a.paramKeys)
- values := make([]reflect.Value, l, l)
-
- for i := 0; i < l; i++ {
- paramKey := a.paramKeys[i]
- paramType := a.paramTypes[i]
- values[i] = getParamValueFromType(ctx, paramType, paramKey)
- }
-
- return values
-}
-
-func getParamValueFromType(ctx context.Context, paramType string, paramKey string) reflect.Value {
- if paramType == paramTypeInt {
- v, _ := ctx.Params().GetInt(paramKey)
- return reflect.ValueOf(v)
- }
-
- if paramType == paramTypeLong {
- v, _ := ctx.Params().GetInt64(paramKey)
- return reflect.ValueOf(v)
- }
-
- if paramType == paramTypeBoolean {
- v, _ := ctx.Params().GetBool(paramKey)
- return reflect.ValueOf(v)
- }
-
- // string, path...
- return reflect.ValueOf(ctx.Params().Get(paramKey))
-}
diff --git a/mvc/activator/methodfunc/func_result_dispatcher.go b/mvc/activator/methodfunc/func_result_dispatcher.go
deleted file mode 100644
index 480a9e36..00000000
--- a/mvc/activator/methodfunc/func_result_dispatcher.go
+++ /dev/null
@@ -1,230 +0,0 @@
-package methodfunc
-
-import (
- "reflect"
- "strings"
-
- "github.com/kataras/iris/context"
-)
-
-// Result is a response dispatcher.
-// All types that complete this interface
-// can be returned as values from the method functions.
-type Result interface {
- // Dispatch should sends the response to the context's response writer.
- Dispatch(ctx context.Context)
-}
-
-const slashB byte = '/'
-
-type compatibleErr interface {
- Error() string
-}
-
-// DefaultErrStatusCode is the default error status code (400)
-// when the response contains an error which is not nil.
-var DefaultErrStatusCode = 400
-
-// DispatchErr writes the error to the response.
-func DispatchErr(ctx context.Context, status int, err error) {
- if status < 400 {
- status = DefaultErrStatusCode
- }
- ctx.StatusCode(status)
- if text := err.Error(); text != "" {
- ctx.WriteString(text)
- ctx.StopExecution()
- }
-}
-
-// DispatchCommon is being used internally to send
-// commonly used data to the response writer with a smart way.
-func DispatchCommon(ctx context.Context,
- statusCode int, contentType string, content []byte, v interface{}, err error, found bool) {
-
- // if we have a false boolean as a return value
- // then skip everything and fire a not found,
- // we even don't care about the given status code or the object or the content.
- if !found {
- ctx.NotFound()
- return
- }
-
- status := statusCode
- if status == 0 {
- status = 200
- }
-
- if err != nil {
- DispatchErr(ctx, status, err)
- return
- }
-
- // write the status code, the rest will need that before any write ofc.
- ctx.StatusCode(status)
- if contentType == "" {
- // to respect any ctx.ContentType(...) call
- // especially if v is not nil.
- contentType = ctx.GetContentType()
- }
-
- if v != nil {
- if d, ok := v.(Result); ok {
- // write the content type now (internal check for empty value)
- ctx.ContentType(contentType)
- d.Dispatch(ctx)
- return
- }
-
- if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
- _, err = ctx.JSONP(v)
- } else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
- _, err = ctx.XML(v, context.XML{Indent: " "})
- } else {
- // defaults to json if content type is missing or its application/json.
- _, err = ctx.JSON(v, context.JSON{Indent: " "})
- }
-
- if err != nil {
- DispatchErr(ctx, status, err)
- }
-
- return
- }
-
- ctx.ContentType(contentType)
- // .Write even len(content) == 0 , this should be called in order to call the internal tryWriteHeader,
- // it will not cost anything.
- ctx.Write(content)
-}
-
-// DispatchFuncResult is being used internally to resolve
-// and send the method function's output values to the
-// context's response writer using a smart way which
-// respects status code, content type, content, custom struct
-// and an error type.
-// Supports for:
-// func(c *ExampleController) Get() string |
-// (string, string) |
-// (string, int) |
-// ...
-// int |
-// (int, string |
-// (string, error) |
-// ...
-// error |
-// (int, error) |
-// (customStruct, error) |
-// ...
-// bool |
-// (int, bool) |
-// (string, bool) |
-// (customStruct, bool) |
-// ...
-// customStruct |
-// (customStruct, int) |
-// (customStruct, string) |
-// Result or (Result, error) and so on...
-//
-// where Get is an HTTP METHOD.
-func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
- numOut := len(values)
- if numOut == 0 {
- return
- }
-
- var (
- // if statusCode > 0 then send this status code.
- // Except when err != nil then check if status code is < 400 and
- // if it's set it as DefaultErrStatusCode.
- // Except when found == false, then the status code is 404.
- statusCode int
- // if not empty then use that as content type,
- // if empty and custom != nil then set it to application/json.
- contentType string
- // if len > 0 then write that to the response writer as raw bytes,
- // except when found == false or err != nil or custom != nil.
- content []byte
- // if not nil then check
- // for content type (or json default) and send the custom data object
- // except when found == false or err != nil.
- custom interface{}
- // if not nil then check for its status code,
- // if not status code or < 400 then set it as DefaultErrStatusCode
- // and fire the error's text.
- err error
- // if false then skip everything and fire 404.
- found = true // defaults to true of course, otherwise will break :)
- )
-
- for _, v := range values {
- // order of these checks matters
- // for example, first we need to check for status code,
- // secondly the string (for content type and content)...
- if !v.IsValid() {
- continue
- }
-
- f := v.Interface()
-
- if b, ok := f.(bool); ok {
- found = b
- if !found {
- // skip everything, we don't care about other return values,
- // this boolean is the higher in order.
- break
- }
- continue
- }
-
- if i, ok := f.(int); ok {
- statusCode = i
- continue
- }
-
- if s, ok := f.(string); ok {
- // a string is content type when it contains a slash and
- // content or custom struct is being calculated already;
- // (string -> content, string-> content type)
- // (customStruct, string -> content type)
- if (len(content) > 0 || custom != nil) && strings.IndexByte(s, slashB) > 0 {
- contentType = s
- } else {
- // otherwise is content
- content = []byte(s)
- }
-
- continue
- }
-
- if b, ok := f.([]byte); ok {
- // it's raw content, get the latest
- content = b
- continue
- }
-
- if e, ok := f.(compatibleErr); ok {
- if e != nil { // it's always not nil but keep it here.
- err = e
- if statusCode < 400 {
- statusCode = DefaultErrStatusCode
- }
- break // break on first error, error should be in the end but we
- // need to know break the dispatcher if any error.
- // at the end; we don't want to write anything to the response if error is not nil.
- }
- continue
- }
-
- // else it's a custom struct or a dispatcher, we'll decide later
- // because content type and status code matters
- // do that check in order to be able to correctly dispatch:
- // (customStruct, error) -> customStruct filled and error is nil
- if custom == nil && f != nil {
- custom = f
- }
-
- }
-
- DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
-}
diff --git a/mvc/activator/methodfunc/methodfunc.go b/mvc/activator/methodfunc/methodfunc.go
deleted file mode 100644
index 3a8f6065..00000000
--- a/mvc/activator/methodfunc/methodfunc.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package methodfunc
-
-import (
- "reflect"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/errors"
-)
-
-// MethodFunc the handler function.
-type MethodFunc struct {
- FuncInfo
- // MethodCall fires the actual handler.
- // The "ctx" is the current context, helps us to get any path parameter's values.
- //
- // The "f" is the controller's function which is responsible
- // for that request for this http method.
- // That function can accept one parameter.
- //
- // The default callers (and the only one for now)
- // are pre-calculated by the framework.
- MethodCall func(ctx context.Context, f reflect.Value)
- RelPath string
-}
-
-// Resolve returns all the method funcs
-// necessary information and actions to
-// perform the request.
-func Resolve(typ reflect.Type) ([]MethodFunc, error) {
- r := errors.NewReporter()
- var methodFuncs []MethodFunc
- infos := fetchInfos(typ)
- for _, info := range infos {
- 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,
- 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/activator/model/model.go b/mvc/activator/model/model.go
deleted file mode 100644
index 078cc6d4..00000000
--- a/mvc/activator/model/model.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package model
-
-import (
- "reflect"
-
- "github.com/kataras/iris/mvc/activator/field"
-
- "github.com/kataras/iris/context"
-)
-
-// Controller is responsible
-// to load and handle the `Model(s)` inside a controller struct
-// via the `iris:"model"` tag field.
-// It stores the optional models from
-// the struct's fields values that
-// are being setted by the method function
-// and set them as ViewData.
-type Controller struct {
- fields []field.Field
-}
-
-// Load tries to lookup and set for any valid model field.
-// Returns nil if no models are being used.
-func Load(typ reflect.Type) *Controller {
- matcher := func(f reflect.StructField) bool {
- if tag, ok := f.Tag.Lookup("iris"); ok {
- if tag == "model" {
- return true
- }
- }
- return false
- }
-
- fields := field.LookupFields(typ.Elem(), matcher, nil)
-
- if len(fields) == 0 {
- return nil
- }
-
- mc := &Controller{
- fields: fields,
- }
- return mc
-}
-
-// Handle transfer the models to the view.
-func (mc *Controller) Handle(ctx context.Context, c reflect.Value) {
- elem := c.Elem() // controller should always be a pointer at this state
-
- for _, f := range mc.fields {
-
- index := f.GetIndex()
- typ := f.GetType()
- name := f.GetTagName()
-
- elemField := elem.FieldByIndex(index)
- // check if current controller's element field
- // is valid, is not nil and it's type is the same (should be but make that check to be sure).
- if !elemField.IsValid() ||
- (elemField.Kind() == reflect.Ptr && elemField.IsNil()) ||
- elemField.Type() != typ {
- continue
- }
-
- fieldValue := elemField.Interface()
- ctx.ViewData(name, fieldValue)
- // /*maybe some time in the future*/ if resetable {
- // // clean up
- // elemField.Set(reflect.Zero(typ))
- // }
-
- }
-}
diff --git a/mvc/activator/persistence/persistence.go b/mvc/activator/persistence/persistence.go
deleted file mode 100644
index 8b81ccbc..00000000
--- a/mvc/activator/persistence/persistence.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package persistence
-
-import (
- "reflect"
-
- "github.com/kataras/iris/mvc/activator/field"
-)
-
-// Controller is responsible to load from the original
-// end-developer's main controller's value
-// and re-store the persistence data by scanning the original.
-// It stores and sets to each new controller
-// the optional data that should be shared among all requests.
-type Controller struct {
- fields []field.Field
-}
-
-// Load scans and load for persistence data based on the `iris:"persistence"` tag.
-//
-// The type is the controller's Type.
-// the "val" is the original end-developer's controller's Value.
-// Returns nil if no persistence data to store found.
-func Load(typ reflect.Type, val reflect.Value) *Controller {
- matcher := func(elemField reflect.StructField) bool {
- if tag, ok := elemField.Tag.Lookup("iris"); ok {
- if tag == "persistence" {
- return true
- }
- }
- return false
- }
-
- handler := func(f *field.Field) {
- valF := val.Field(f.Index)
- if valF.IsValid() || (valF.Kind() == reflect.Ptr && !valF.IsNil()) {
- val := reflect.ValueOf(valF.Interface())
- if val.IsValid() || (val.Kind() == reflect.Ptr && !val.IsNil()) {
- f.Value = val
- }
- }
- }
-
- fields := field.LookupFields(typ.Elem(), matcher, handler)
-
- if len(fields) == 0 {
- return nil
- }
-
- return &Controller{
- fields: fields,
- }
-}
-
-// Handle re-stores the persistence data at the current controller.
-func (pc *Controller) Handle(c reflect.Value) {
- elem := c.Elem() // controller should always be a pointer at this state
- for _, f := range pc.fields {
- f.SendTo(elem)
- }
-}
diff --git a/mvc/controller.go b/mvc/controller.go
deleted file mode 100644
index fe506638..00000000
--- a/mvc/controller.go
+++ /dev/null
@@ -1,369 +0,0 @@
-package mvc
-
-import (
- "reflect"
- "strings"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/memstore"
- "github.com/kataras/iris/mvc/activator"
-)
-
-// C is the lightweight BaseController type as an alternative of the `Controller` struct type.
-// It contains only the Name of the controller and the Context, it's the best option
-// to balance the performance cost reflection uses
-// if your controller uses the new func output values dispatcher feature;
-// func(c *ExampleController) Get() string |
-// (string, string) |
-// (string, int) |
-// int |
-// (int, string |
-// (string, error) |
-// bool |
-// (any, bool) |
-// error |
-// (int, error) |
-// (customStruct, error) |
-// customStruct |
-// (customStruct, int) |
-// (customStruct, string) |
-// Result or (Result, error)
-// where Get is an HTTP Method func.
-//
-// Look `core/router#APIBuilder#Controller` method too.
-//
-// It completes the `activator.BaseController` interface.
-//
-// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview/web/controllers.
-// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go#L17.
-type C struct {
- // The Name of the `C` controller.
- Name string
- // The current context.Context.
- //
- // we have to name it for two reasons:
- // 1: can't ignore these via reflection, it doesn't give an option to
- // see if the functions is derived from another type.
- // 2: end-developer may want to use some method functions
- // or any fields that could be conflict with the context's.
- Ctx context.Context
-}
-
-var _ activator.BaseController = &C{}
-
-// SetName sets the controller's full name.
-// It's called internally.
-func (c *C) SetName(name string) { c.Name = name }
-
-// BeginRequest starts the request by initializing the `Context` field.
-func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx }
-
-// EndRequest does nothing, is here to complete the `BaseController` interface.
-func (c *C) EndRequest(ctx context.Context) {}
-
-// Controller is the base controller for the high level controllers instances.
-//
-// This base controller is used as an alternative way of building
-// APIs, the controller can register all type of http methods.
-//
-// Keep note that controllers are bit slow
-// because of the reflection use however it's as fast as possible because
-// it does preparation before the serve-time handler but still
-// remains slower than the low-level handlers
-// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
-//
-//
-// All fields that are tagged with iris:"persistence"` or binded
-// are being persistence and kept the same between the different requests.
-//
-// An Example Controller can be:
-//
-// type IndexController struct {
-// Controller
-// }
-//
-// func (c *IndexController) Get() {
-// c.Tmpl = "index.html"
-// c.Data["title"] = "Index page"
-// c.Data["message"] = "Hello world!"
-// }
-//
-// Usage: app.Controller("/", new(IndexController))
-//
-//
-// Another example with bind:
-//
-// type UserController struct {
-// mvc.Controller
-//
-// DB *DB
-// CreatedAt time.Time
-// }
-//
-// // Get serves using the User controller when HTTP Method is "GET".
-// func (c *UserController) Get() {
-// c.Tmpl = "user/index.html"
-// c.Data["title"] = "User Page"
-// c.Data["username"] = "kataras " + c.Params.Get("userid")
-// c.Data["connstring"] = c.DB.Connstring
-// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
-// }
-//
-// Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
-// Note: Binded values of context.Handler type are being recognised as middlewares by the router.
-//
-// Look `core/router/APIBuilder#Controller` method too.
-//
-// It completes the `activator.BaseController` interface.
-type Controller struct {
- // Name contains the current controller's full name.
- //
- // doesn't change on different paths.
- Name string
-
- // contains the `Name` as different words, all lowercase,
- // without the "Controller" suffix if exists.
- // we need this as field because the activator
- // we will not try to parse these if not needed
- // it's up to the end-developer to call `RelPath()` or `RelTmpl()`
- // which will result to fill them.
- //
- // doesn't change on different paths.
- nameAsWords []string
-
- // relPath the "as assume" relative request path.
- //
- // If UserController and request path is "/user/messages" then it's "/messages"
- // if UserPostController and request path is "/user/post" then it's "/"
- // if UserProfile and request path is "/user/profile/likes" then it's "/likes"
- //
- // doesn't change on different paths.
- relPath string
-
- // request path and its parameters, read-write.
- // Path is the current request path, if changed then it redirects.
- Path string
- // Params are the request path's parameters, i.e
- // for route like "/user/{id}" and request to "/user/42"
- // it contains the "id" = 42.
- Params *context.RequestParams
-
- // some info read and write,
- // can be already set-ed by previous handlers as well.
- Status int
- Values *memstore.Store
-
- // relTmpl the "as assume" relative path to the view root folder.
- //
- // If UserController then it's "user/"
- // if UserPostController then it's "user/post/"
- // if UserProfile then it's "user/profile/".
- //
- // doesn't change on different paths.
- relTmpl string
-
- // view read and write,
- // can be already set-ed by previous handlers as well.
- Layout string
- Tmpl string
- Data map[string]interface{}
-
- ContentType string
- Text string // response as string
-
- // give access to the request context itself.
- Ctx context.Context
-}
-
-var _ activator.BaseController = &Controller{}
-
-var ctrlSuffix = reflect.TypeOf(Controller{}).Name()
-
-// SetName sets the controller's full name.
-// It's called internally.
-func (c *Controller) SetName(name string) {
- c.Name = name
-}
-
-func (c *Controller) getNameWords() []string {
- if len(c.nameAsWords) == 0 {
- c.nameAsWords = findCtrlWords(c.Name)
- }
- return c.nameAsWords
-}
-
-// Route returns the current request controller's context read-only access route.
-func (c *Controller) Route() context.RouteReadOnly {
- return c.Ctx.GetCurrentRoute()
-}
-
-const slashStr = "/"
-
-// RelPath tries to return the controller's name
-// without the "Controller" prefix, all lowercase
-// prefixed with slash and splited by slash appended
-// with the rest of the request path.
-// For example:
-// If UserController and request path is "/user/messages" then it's "/messages"
-// if UserPostController and request path is "/user/post" then it's "/"
-// if UserProfile and request path is "/user/profile/likes" then it's "/likes"
-//
-// It's useful for things like path checking and redirect.
-func (c *Controller) RelPath() string {
- if c.relPath == "" {
- w := c.getNameWords()
- rel := strings.Join(w, slashStr)
-
- reqPath := c.Ctx.Path()
- if len(reqPath) == 0 {
- // it never come here
- // but to protect ourselves just return an empty slash.
- return slashStr
- }
- // [1:]to ellimuate the prefixes like "//"
- // request path has always "/"
- rel = strings.Replace(reqPath[1:], rel, "", 1)
- if rel == "" {
- rel = slashStr
- }
- c.relPath = rel
- // this will return any dynamic path after the static one
- // or a a slash "/":
- //
- // reqPath := c.Ctx.Path()
- // if len(reqPath) == 0 {
- // // it never come here
- // // but to protect ourselves just return an empty slash.
- // return slashStr
- // }
- // var routeVParams []string
- // c.Params.Visit(func(key string, value string) {
- // routeVParams = append(routeVParams, value)
- // })
-
- // rel := c.Route().StaticPath()
- // println(rel)
- // // [1:]to ellimuate the prefixes like "//"
- // // request path has always "/"
- // rel = strings.Replace(reqPath, rel[1:], "", 1)
- // println(rel)
- // if rel == "" {
- // rel = slashStr
- // }
- // c.relPath = rel
- }
-
- return c.relPath
-}
-
-// RelTmpl tries to return the controller's name
-// without the "Controller" prefix, all lowercase
-// splited by slash and suffixed by slash.
-// For example:
-// If UserController then it's "user/"
-// if UserPostController then it's "user/post/"
-// if UserProfile then it's "user/profile/".
-//
-// It's useful to locate templates if the controller and views path have aligned names.
-func (c *Controller) RelTmpl() string {
- if c.relTmpl == "" {
- c.relTmpl = strings.Join(c.getNameWords(), slashStr) + slashStr
- }
- return c.relTmpl
-}
-
-// Write writes to the client via the context's ResponseWriter.
-// Controller completes the `io.Writer` interface for the shake of ease.
-func (c *Controller) Write(contents []byte) (int, error) {
- c.tryWriteHeaders()
- return c.Ctx.ResponseWriter().Write(contents)
-}
-
-// Writef formats according to a format specifier and writes to the response.
-func (c *Controller) Writef(format string, a ...interface{}) (int, error) {
- c.tryWriteHeaders()
- return c.Ctx.ResponseWriter().Writef(format, a...)
-}
-
-// BeginRequest starts the main controller
-// it initialize the Ctx and other fields.
-//
-// It's called internally.
-// End-Developer can ovverride it but it still MUST be called.
-func (c *Controller) BeginRequest(ctx context.Context) {
- // path and path params
- c.Path = ctx.Path()
- c.Params = ctx.Params()
- // response status code
- c.Status = ctx.GetStatusCode()
-
- // share values
- c.Values = ctx.Values()
- // view data for templates, remember
- // each controller is a new instance, so
- // checking for nil and then init those type of fields
- // have no meaning.
- c.Data = make(map[string]interface{}, 0)
-
- // context itself
- c.Ctx = ctx
-}
-
-func (c *Controller) tryWriteHeaders() {
- if c.Status > 0 && c.Status != c.Ctx.GetStatusCode() {
- c.Ctx.StatusCode(c.Status)
- }
-
- if c.ContentType != "" {
- c.Ctx.ContentType(c.ContentType)
- }
-}
-
-// EndRequest is the final method which will be executed
-// before response sent.
-//
-// It checks for the fields and calls the necessary context's
-// methods to modify the response to the client.
-//
-// It's called internally.
-// End-Developer can ovveride it but still should be called at the end.
-func (c *Controller) EndRequest(ctx context.Context) {
- if ctx.ResponseWriter().Written() >= 0 { // status code only (0) or actual body written(>0)
- return
- }
-
- if path := c.Path; path != "" && path != ctx.Path() {
- // then redirect and exit.
- ctx.Redirect(path, c.Status)
- return
- }
-
- c.tryWriteHeaders()
- if response := c.Text; response != "" {
- ctx.WriteString(response)
- return // exit here
- }
-
- if view := c.Tmpl; view != "" {
- if layout := c.Layout; layout != "" {
- ctx.ViewLayout(layout)
- }
- if len(c.Data) > 0 {
- dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
- // In order to respect any c.Ctx.ViewData that may called manually before;
- if ctx.Values().Get(dataKey) == nil {
- // if no c.Ctx.ViewData then it's empty do a
- // pure set, it's faster.
- ctx.Values().Set(dataKey, c.Data)
- } else {
- // else do a range loop and set the data one by one.
- for k, v := range c.Data {
- ctx.ViewData(k, v)
- }
- }
-
- }
-
- ctx.View(view)
- }
-}
diff --git a/mvc/controller_test.go b/mvc/controller_test.go
deleted file mode 100644
index 362249d5..00000000
--- a/mvc/controller_test.go
+++ /dev/null
@@ -1,531 +0,0 @@
-// black-box testing
-package mvc_test
-
-import (
- "testing"
-
- "github.com/kataras/iris"
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/mvc"
- "github.com/kataras/iris/mvc/activator"
-
- "github.com/kataras/iris/core/router"
- "github.com/kataras/iris/httptest"
-)
-
-type testController struct {
- mvc.Controller
-}
-
-var writeMethod = func(c mvc.Controller) {
- c.Ctx.Writef(c.Ctx.Method())
-}
-
-func (c *testController) Get() {
- writeMethod(c.Controller)
-}
-func (c *testController) Post() {
- writeMethod(c.Controller)
-}
-func (c *testController) Put() {
- writeMethod(c.Controller)
-}
-func (c *testController) Delete() {
- writeMethod(c.Controller)
-}
-func (c *testController) Connect() {
- writeMethod(c.Controller)
-}
-func (c *testController) Head() {
- writeMethod(c.Controller)
-}
-func (c *testController) Patch() {
- writeMethod(c.Controller)
-}
-func (c *testController) Options() {
- writeMethod(c.Controller)
-}
-func (c *testController) Trace() {
- writeMethod(c.Controller)
-}
-
-type (
- testControllerAll struct{ mvc.Controller }
- testControllerAny struct{ mvc.Controller } // exactly the same as All
-)
-
-func (c *testControllerAll) All() {
- writeMethod(c.Controller)
-}
-
-func (c *testControllerAny) Any() {
- writeMethod(c.Controller)
-}
-
-func TestControllerMethodFuncs(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testController))
- app.Controller("/all", new(testControllerAll))
- app.Controller("/any", new(testControllerAny))
-
- e := httptest.New(t, app)
- for _, method := range router.AllMethods {
-
- e.Request(method, "/").Expect().Status(iris.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/all").Expect().Status(iris.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/any").Expect().Status(iris.StatusOK).
- Body().Equal(method)
- }
-}
-
-func TestControllerMethodAndPathHandleMany(t *testing.T) {
- app := iris.New()
- app.Controller("/ /path1 /path2 /path3", new(testController))
-
- e := httptest.New(t, app)
- for _, method := range router.AllMethods {
-
- e.Request(method, "/").Expect().Status(iris.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/path1").Expect().Status(iris.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/path2").Expect().Status(iris.StatusOK).
- Body().Equal(method)
- }
-}
-
-type testControllerPersistence struct {
- mvc.Controller
- Data string `iris:"persistence"`
-}
-
-func (c *testControllerPersistence) Get() {
- c.Ctx.WriteString(c.Data)
-}
-
-func TestControllerPersistenceFields(t *testing.T) {
- data := "this remains the same for all requests"
- app := iris.New()
- app.Controller("/", &testControllerPersistence{Data: data})
- e := httptest.New(t, app)
- e.GET("/").Expect().Status(iris.StatusOK).
- Body().Equal(data)
-}
-
-type testControllerBeginAndEndRequestFunc struct {
- mvc.Controller
-
- Username string
-}
-
-// called before of every method (Get() or Post()).
-//
-// useful when more than one methods using the
-// same request values or context's function calls.
-func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
- c.Controller.BeginRequest(ctx)
- c.Username = ctx.Params().Get("username")
- // or t.Params.Get("username") because the
- // t.Ctx == ctx and is being initialized at the t.Controller.BeginRequest.
-}
-
-// called after every method (Get() or Post()).
-func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
- ctx.Writef("done") // append "done" to the response
- c.Controller.EndRequest(ctx)
-}
-
-func (c *testControllerBeginAndEndRequestFunc) Get() {
- c.Ctx.Writef(c.Username)
-}
-
-func (c *testControllerBeginAndEndRequestFunc) Post() {
- c.Ctx.Writef(c.Username)
-}
-
-func TestControllerBeginAndEndRequestFunc(t *testing.T) {
- app := iris.New()
- app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
-
- e := httptest.New(t, app)
- usernames := []string{
- "kataras",
- "makis",
- "efi",
- "rg",
- "bill",
- "whoisyourdaddy",
- }
- doneResponse := "done"
-
- for _, username := range usernames {
- e.GET("/profile/" + username).Expect().Status(iris.StatusOK).
- Body().Equal(username + doneResponse)
- e.POST("/profile/" + username).Expect().Status(iris.StatusOK).
- Body().Equal(username + doneResponse)
- }
-}
-
-func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
- app := iris.New()
- usernames := map[string]bool{
- "kataras": true,
- "makis": false,
- "efi": true,
- "rg": false,
- "bill": true,
- "whoisyourdaddy": false,
- }
- middlewareCheck := func(ctx context.Context) {
- for username, allow := range usernames {
- if ctx.Params().Get("username") == username && allow {
- ctx.Next()
- return
- }
- }
-
- ctx.StatusCode(iris.StatusForbidden)
- ctx.Writef("forbidden")
- }
-
- app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc), middlewareCheck)
-
- e := httptest.New(t, app)
-
- doneResponse := "done"
-
- for username, allow := range usernames {
- getEx := e.GET("/profile/" + username).Expect()
- if allow {
- getEx.Status(iris.StatusOK).
- Body().Equal(username + doneResponse)
- } else {
- getEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
- }
-
- postEx := e.POST("/profile/" + username).Expect()
- if allow {
- postEx.Status(iris.StatusOK).
- Body().Equal(username + doneResponse)
- } else {
- postEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
- }
- }
-}
-
-type Model struct {
- Username string
-}
-
-type testControllerModel struct {
- mvc.Controller
-
- TestModel Model `iris:"model" name:"myModel"`
- TestModel2 Model `iris:"model"`
-}
-
-func (c *testControllerModel) Get() {
- username := c.Ctx.Params().Get("username")
- c.TestModel = Model{Username: username}
- c.TestModel2 = Model{Username: username + "2"}
-}
-
-func writeModels(ctx context.Context, names ...string) {
- if expected, got := len(names), len(ctx.GetViewData()); expected != got {
- ctx.Writef("expected view data length: %d but got: %d for names: %s", expected, got, names)
- return
- }
-
- for _, name := range names {
-
- m, ok := ctx.GetViewData()[name]
- if !ok {
- ctx.Writef("fail load and set the %s", name)
- return
- }
-
- model, ok := m.(Model)
- if !ok {
- ctx.Writef("fail to override the %s' name by the tag", name)
- return
- }
-
- ctx.Writef(model.Username)
- }
-}
-
-func (c *testControllerModel) EndRequest(ctx context.Context) {
- writeModels(ctx, "myModel", "TestModel2")
- c.Controller.EndRequest(ctx)
-}
-func TestControllerModel(t *testing.T) {
- app := iris.New()
- app.Controller("/model/{username}", new(testControllerModel))
-
- e := httptest.New(t, app)
- usernames := []string{
- "kataras",
- "makis",
- }
-
- for _, username := range usernames {
- e.GET("/model/" + username).Expect().Status(iris.StatusOK).
- Body().Equal(username + username + "2")
- }
-}
-
-type testBindType struct {
- title string
-}
-
-type testControllerBindStruct struct {
- mvc.Controller
- // should start with upper letter of course
- TitlePointer *testBindType // should have the value of the "myTitlePtr" on test
- TitleValue testBindType // should have the value of the "myTitleV" on test
- Other string // just another type to check the field collection, should be empty
-}
-
-func (t *testControllerBindStruct) Get() {
- t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
-}
-
-type testControllerBindDeep struct {
- testControllerBindStruct
-}
-
-func (t *testControllerBindDeep) Get() {
- // t.testControllerBindStruct.Get()
- t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
-}
-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
- myTitlePtr := &testBindType{title: t1}
- // test bind value to value of the correct type
- myTitleV := testBindType{title: t2}
-
- app.Controller("/", new(testControllerBindStruct), myTitlePtr, myTitleV)
- app.Controller("/deep", new(testControllerBindDeep), myTitlePtr, myTitleV)
-
- e := httptest.New(t, app)
- expected := t1 + t2
- e.GET("/").Expect().Status(iris.StatusOK).
- Body().Equal(expected)
- e.GET("/deep").Expect().Status(iris.StatusOK).
- Body().Equal(expected)
-}
-
-type (
- UserController struct{ mvc.Controller }
- Profile struct{ mvc.Controller }
- UserProfilePostController struct{ mvc.Controller }
-)
-
-func writeRelatives(c mvc.Controller) {
- c.Ctx.JSON(context.Map{
- "RelPath": c.RelPath(),
- "TmplPath": c.RelTmpl(),
- })
-}
-func (c *UserController) Get() {
- writeRelatives(c.Controller)
-}
-
-func (c *Profile) Get() {
- writeRelatives(c.Controller)
-}
-
-func (c *UserProfilePostController) Get() {
- writeRelatives(c.Controller)
-}
-
-func TestControllerRelPathAndRelTmpl(t *testing.T) {
- app := iris.New()
- var tests = map[string]context.Map{
- // UserController
- "/user": {"RelPath": "/", "TmplPath": "user/"},
- "/user/42": {"RelPath": "/42", "TmplPath": "user/"},
- "/user/me": {"RelPath": "/me", "TmplPath": "user/"},
- // Profile (without Controller suffix, should work as expected)
- "/profile": {"RelPath": "/", "TmplPath": "profile/"},
- "/profile/42": {"RelPath": "/42", "TmplPath": "profile/"},
- "/profile/me": {"RelPath": "/me", "TmplPath": "profile/"},
- // UserProfilePost
- "/user/profile/post": {"RelPath": "/", "TmplPath": "user/profile/post/"},
- "/user/profile/post/42": {"RelPath": "/42", "TmplPath": "user/profile/post/"},
- "/user/profile/post/mine": {"RelPath": "/mine", "TmplPath": "user/profile/post/"},
- }
-
- app.Controller("/user /user/me /user/{id}",
- new(UserController))
-
- app.Controller("/profile /profile/me /profile/{id}",
- new(Profile))
-
- app.Controller("/user/profile/post /user/profile/post/mine /user/profile/post/{id}",
- new(UserProfilePostController))
-
- e := httptest.New(t, app)
- for path, tt := range tests {
- e.GET(path).Expect().Status(iris.StatusOK).JSON().Equal(tt)
- }
-}
-
-type testCtrl0 struct {
- testCtrl00
-}
-
-func (c *testCtrl0) Get() {
- username := c.Params.Get("username")
- c.Model = Model{Username: username}
-}
-
-func (c *testCtrl0) EndRequest(ctx context.Context) {
- writeModels(ctx, "myModel")
-
- if c.TitlePointer == nil {
- ctx.Writef("\nTitlePointer is nil!\n")
- } else {
- ctx.Writef(c.TitlePointer.title)
- }
-
- //should be the same as `.testCtrl000.testCtrl0000.EndRequest(ctx)`
- c.testCtrl00.EndRequest(ctx)
-}
-
-type testCtrl00 struct {
- testCtrl000
-
- Model Model `iris:"model" name:"myModel"`
-}
-
-type testCtrl000 struct {
- testCtrl0000
-
- TitlePointer *testBindType
-}
-
-type testCtrl0000 struct {
- mvc.Controller
-}
-
-func (c *testCtrl0000) EndRequest(ctx context.Context) {
- ctx.Writef("finish")
-}
-
-func TestControllerInsideControllerRecursively(t *testing.T) {
- var (
- username = "gerasimos"
- title = "mytitle"
- expected = username + title + "finish"
- )
-
- app := iris.New()
-
- app.Controller("/user/{username}", new(testCtrl0),
- &testBindType{title: title})
-
- e := httptest.New(t, app)
- e.GET("/user/" + username).Expect().
- Status(iris.StatusOK).Body().Equal(expected)
-}
-
-type testControllerRelPathFromFunc struct{ mvc.Controller }
-
-func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) {
- ctx.Writef("%s:%s", ctx.Method(), ctx.Path())
- c.Controller.EndRequest(ctx)
-}
-
-func (c *testControllerRelPathFromFunc) Get() {}
-func (c *testControllerRelPathFromFunc) GetBy(int64) {}
-func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {}
-
-func (c *testControllerRelPathFromFunc) GetLogin() {}
-func (c *testControllerRelPathFromFunc) PostLogin() {}
-
-func (c *testControllerRelPathFromFunc) GetAdminLogin() {}
-
-func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {}
-
-func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {}
-func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {}
-func (c *testControllerRelPathFromFunc) GetSomethingNewBy(string, int) {} // two input arguments, one By which is the latest word.
-func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments
-
-func TestControllerRelPathFromFunc(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testControllerRelPathFromFunc))
-
- e := httptest.New(t, app)
- e.GET("/").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/")
-
- e.GET("/42").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/42")
- e.GET("/something/true").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/something/true")
- e.GET("/something/false").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/something/false")
- e.GET("/something/truee").Expect().Status(iris.StatusNotFound)
- e.GET("/something/falsee").Expect().Status(iris.StatusNotFound)
- e.GET("/something/kataras/42").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/something/kataras/42")
- e.GET("/something/new/kataras/42").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/something/new/kataras/42")
- e.GET("/something/true/else/this/42").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/something/true/else/this/42")
-
- e.GET("/login").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/login")
- e.POST("/login").Expect().Status(iris.StatusOK).
- Body().Equal("POST:/login")
- e.GET("/admin/login").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/admin/login")
- e.PUT("/something/into/this").Expect().Status(iris.StatusOK).
- Body().Equal("PUT:/something/into/this")
- e.GET("/42").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/42")
- e.GET("/anything/here").Expect().Status(iris.StatusOK).
- Body().Equal("GET:/anything/here")
-}
-
-type testControllerActivateListener struct {
- mvc.Controller
-
- TitlePointer *testBindType
-}
-
-func (c *testControllerActivateListener) OnActivate(t *activator.TController) {
- t.BindValue(&testBindType{
- title: "default title",
- })
-}
-
-func (c *testControllerActivateListener) Get() {
- c.Text = c.TitlePointer.title
-}
-
-func TestControllerActivateListener(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testControllerActivateListener))
- app.Controller("/manual", new(testControllerActivateListener), &testBindType{
- title: "my title",
- })
-
- e := httptest.New(t, app)
- e.GET("/").Expect().Status(iris.StatusOK).
- Body().Equal("default title")
- e.GET("/manual").Expect().Status(iris.StatusOK).
- Body().Equal("my title")
-}
diff --git a/mvc/go19.go b/mvc/go19.go
deleted file mode 100644
index 7467090f..00000000
--- a/mvc/go19.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// +build go1.9
-
-package mvc
-
-import (
- "html/template"
-
- "github.com/kataras/iris/mvc/activator"
-)
-
-type (
- // HTML wraps the "s" with the template.HTML
- // in order to be marked as safe content, to be rendered as html and not escaped.
- HTML = template.HTML
-
- // TController contains the necessary controller's pre-serve information.
- //
- // 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#TController` for its implementation.
- //
- // A shortcut for the `mvc/activator#TController`, useful when `OnActivate` is being used.
- TController = activator.TController
-)
diff --git a/mvc/method_result.go b/mvc/method_result.go
deleted file mode 100644
index 7512ff59..00000000
--- a/mvc/method_result.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package mvc
-
-import (
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/mvc/activator/methodfunc"
-)
-
-// build go1.9 only(go19.go)-->
-// // Result is a response dispatcher.
-// // All types that complete this interface
-// // can be returned as values from the method functions.
-// Result = methodfunc.Result
-// <--
-// No, let's just copy-paste in order to go 1.8 users have this type
-// easy to be used from the root mvc package,
-// sometimes duplication doesn't hurt.
-
-// Result is a response dispatcher.
-// All types that complete this interface
-// can be returned as values from the method functions.
-//
-// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview.
-type Result interface { // NOTE: Should be always compatible with the methodfunc.Result.
- // Dispatch should sends the response to the context's response writer.
- Dispatch(ctx context.Context)
-}
-
-var defaultFailureResponse = Response{Code: methodfunc.DefaultErrStatusCode}
-
-// Try will check if "fn" ran without any panics,
-// using recovery,
-// and return its result as the final response
-// otherwise it returns the "failure" response if any,
-// if not then a 400 bad request is being sent.
-//
-// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go.
-func Try(fn func() Result, failure ...Result) Result {
- var failed bool
- var actionResponse Result
-
- func() {
- defer func() {
- if rec := recover(); rec != nil {
- failed = true
- }
- }()
- actionResponse = fn()
- }()
-
- if failed {
- if len(failure) > 0 {
- return failure[0]
- }
- return defaultFailureResponse
- }
-
- return actionResponse
-}
diff --git a/mvc/method_result_response.go b/mvc/method_result_response.go
deleted file mode 100644
index 4c11cdaa..00000000
--- a/mvc/method_result_response.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package mvc
-
-import (
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/mvc/activator/methodfunc"
-)
-
-// Response completes the `methodfunc.Result` interface.
-// It's being used as an alternative return value which
-// wraps the status code, the content type, a content as bytes or as string
-// and an error, it's smart enough to complete the request and send the correct response to the client.
-type Response struct {
- Code int
- ContentType string
- Content []byte
-
- // if not empty then content type is the text/plain
- // and content is the text as []byte.
- Text string
- // If not nil then it will fire that as "application/json" or the
- // "ContentType" if not empty.
- Object interface{}
-
- // If Path is not empty then it will redirect
- // the client to this Path, if Code is >= 300 and < 400
- // then it will use that Code to do the redirection, otherwise
- // StatusFound(302) or StatusSeeOther(303) for post methods will be used.
- // Except when err != nil.
- Path string
-
- // if not empty then fire a 400 bad request error
- // unless the Status is > 200, then fire that error code
- // with the Err.Error() string as its content.
- //
- // if Err.Error() is empty then it fires the custom error handler
- // if any otherwise the framework sends the default http error text based on the status.
- Err error
- Try func() int
-
- // if true then it skips everything else and it throws a 404 not found error.
- // Can be named as Failure but NotFound is more precise name in order
- // to be visible that it's different than the `Err`
- // because it throws a 404 not found instead of a 400 bad request.
- // NotFound bool
- // let's don't add this yet, it has its dangerous of missuse.
-}
-
-var _ methodfunc.Result = Response{}
-
-// Dispatch writes the response result to the context's response writer.
-func (r Response) Dispatch(ctx context.Context) {
- if r.Path != "" && r.Err == nil {
- // it's not a redirect valid status
- if r.Code < 300 || r.Code >= 400 {
- if ctx.Method() == "POST" {
- r.Code = 303 // StatusSeeOther
- }
- r.Code = 302 // StatusFound
- }
- ctx.Redirect(r.Path, r.Code)
- return
- }
-
- if s := r.Text; s != "" {
- r.Content = []byte(s)
- }
-
- methodfunc.DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err, true)
-}
diff --git a/mvc/method_result_test.go b/mvc/method_result_test.go
deleted file mode 100644
index b6e2c28e..00000000
--- a/mvc/method_result_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package mvc_test
-
-import (
- "errors"
- "testing"
-
- "github.com/kataras/iris"
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/httptest"
- "github.com/kataras/iris/mvc"
-)
-
-// activator/methodfunc/func_caller.go.
-// and activator/methodfunc/func_result_dispatcher.go
-
-type testControllerMethodResult struct {
- mvc.C
-}
-
-func (c *testControllerMethodResult) Get() mvc.Result {
- return mvc.Response{
- Text: "Hello World!",
- }
-}
-
-func (c *testControllerMethodResult) GetWithStatus() mvc.Response { // or mvc.Result again, no problem.
- return mvc.Response{
- Text: "This page doesn't exist",
- Code: iris.StatusNotFound,
- }
-}
-
-type testCustomStruct struct {
- Name string `json:"name" xml:"name"`
- Age int `json:"age" xml:"age"`
-}
-
-func (c *testControllerMethodResult) GetJson() mvc.Result {
- var err error
- if c.Ctx.URLParamExists("err") {
- err = errors.New("error here")
- }
- return mvc.Response{
- Err: err, // if err != nil then it will fire the error's text with a BadRequest.
- Object: testCustomStruct{Name: "Iris", Age: 2},
- }
-}
-
-var things = []string{"thing 0", "thing 1", "thing 2"}
-
-func (c *testControllerMethodResult) GetThingWithTryBy(index int) mvc.Result {
- failure := mvc.Response{
- Text: "thing does not exist",
- Code: iris.StatusNotFound,
- }
-
- return mvc.Try(func() mvc.Result {
- // if panic because of index exceed the slice
- // then the "failure" response will be returned instead.
- return mvc.Response{Text: things[index]}
- }, failure)
-}
-
-func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) mvc.Result {
- return mvc.Try(func() mvc.Result {
- // if panic because of index exceed the slice
- // then the default failure response will be returned instead (400 bad request).
- return mvc.Response{Text: things[index]}
- })
-}
-
-func TestControllerMethodResult(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testControllerMethodResult))
-
- e := httptest.New(t, app)
-
- e.GET("/").Expect().Status(iris.StatusOK).
- Body().Equal("Hello World!")
-
- e.GET("/with/status").Expect().Status(iris.StatusNotFound).
- Body().Equal("This page doesn't exist")
-
- e.GET("/json").Expect().Status(iris.StatusOK).
- JSON().Equal(iris.Map{
- "name": "Iris",
- "age": 2,
- })
-
- e.GET("/json").WithQuery("err", true).Expect().
- Status(iris.StatusBadRequest).
- Body().Equal("error here")
-
- e.GET("/thing/with/try/1").Expect().
- Status(iris.StatusOK).
- Body().Equal("thing 1")
- // failure because of index exceed the slice
- e.GET("/thing/with/try/3").Expect().
- Status(iris.StatusNotFound).
- Body().Equal("thing does not exist")
-
- e.GET("/thing/with/try/default/3").Expect().
- Status(iris.StatusBadRequest).
- Body().Equal("Bad Request")
-}
-
-type testControllerMethodResultTypes struct {
- mvc.Controller
-}
-
-func (c *testControllerMethodResultTypes) GetText() string {
- return "text"
-}
-
-func (c *testControllerMethodResultTypes) GetStatus() int {
- return iris.StatusBadGateway
-}
-
-func (c *testControllerMethodResultTypes) GetTextWithStatusOk() (string, int) {
- return "OK", iris.StatusOK
-}
-
-// tests should have output arguments mixed
-func (c *testControllerMethodResultTypes) GetStatusWithTextNotOkBy(first string, second string) (int, string) {
- return iris.StatusForbidden, "NOT_OK_" + first + second
-}
-
-func (c *testControllerMethodResultTypes) GetTextAndContentType() (string, string) {
- return "text", "text/html"
-}
-
-type testControllerMethodCustomResult struct {
- HTML string
-}
-
-// The only one required function to make that a custom Response dispatcher.
-func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) {
- ctx.HTML(r.HTML)
-}
-
-func (c *testControllerMethodResultTypes) GetCustomResponse() testControllerMethodCustomResult {
- return testControllerMethodCustomResult{"text"}
-}
-
-func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
- return testControllerMethodCustomResult{"OK"}, iris.StatusOK
-}
-
-func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
- return testControllerMethodCustomResult{"internal server error"}, iris.StatusInternalServerError
-}
-
-func (c *testControllerMethodResultTypes) GetCustomStruct() testCustomStruct {
- return testCustomStruct{"Iris", 2}
-}
-
-func (c *testControllerMethodResultTypes) GetCustomStructWithStatusNotOk() (testCustomStruct, int) {
- return testCustomStruct{"Iris", 2}, iris.StatusInternalServerError
-}
-
-func (c *testControllerMethodResultTypes) GetCustomStructWithContentType() (testCustomStruct, string) {
- return testCustomStruct{"Iris", 2}, "text/xml"
-}
-
-func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCustomStruct, err error) {
- s = testCustomStruct{"Iris", 2}
- if c.Ctx.URLParamExists("err") {
- err = errors.New("omit return of testCustomStruct and fire error")
- }
-
- // it should send the testCustomStruct as JSON if error is nil
- // otherwise it should fire the default error(BadRequest) with the error's text.
- return
-}
-
-func TestControllerMethodResultTypes(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testControllerMethodResultTypes))
-
- e := httptest.New(t, app)
-
- e.GET("/text").Expect().Status(iris.StatusOK).
- Body().Equal("text")
-
- e.GET("/status").Expect().Status(iris.StatusBadGateway)
-
- e.GET("/text/with/status/ok").Expect().Status(iris.StatusOK).
- Body().Equal("OK")
-
- e.GET("/status/with/text/not/ok/first/second").Expect().Status(iris.StatusForbidden).
- Body().Equal("NOT_OK_firstsecond")
-
- e.GET("/text/and/content/type").Expect().Status(iris.StatusOK).
- ContentType("text/html", "utf-8").
- Body().Equal("text")
-
- e.GET("/custom/response").Expect().Status(iris.StatusOK).
- ContentType("text/html", "utf-8").
- Body().Equal("text")
- e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
- ContentType("text/html", "utf-8").
- Body().Equal("OK")
- e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
- ContentType("text/html", "utf-8").
- Body().Equal("internal server error")
-
- expectedResultFromCustomStruct := map[string]interface{}{
- "name": "Iris",
- "age": 2,
- }
- e.GET("/custom/struct").Expect().Status(iris.StatusOK).
- JSON().Equal(expectedResultFromCustomStruct)
- e.GET("/custom/struct/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
- JSON().Equal(expectedResultFromCustomStruct)
- e.GET("/custom/struct/with/content/type").Expect().Status(iris.StatusOK).
- ContentType("text/xml", "utf-8")
- e.GET("/custom/struct/with/error").Expect().Status(iris.StatusOK).
- JSON().Equal(expectedResultFromCustomStruct)
- e.GET("/custom/struct/with/error").WithQuery("err", true).Expect().
- Status(iris.StatusBadRequest). // the default status code if error is not nil
- // the content should be not JSON it should be the status code's text
- // it will fire the error's text
- Body().Equal("omit return of testCustomStruct and fire error")
-}
-
-type testControllerViewResultRespectCtxViewData struct {
- T *testing.T
- mvc.C
-}
-
-func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) {
- t.C.BeginRequest(ctx)
- ctx.ViewData("name_begin", "iris_begin")
-}
-
-func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) {
- t.C.EndRequest(ctx)
- // check if data is not overridden by return mvc.View {Data: context.Map...}
-
- dataWritten := ctx.GetViewData()
- if dataWritten == nil {
- t.T.Fatalf("view data is nil, both BeginRequest and Get failed to write the data")
- return
- }
-
- if dataWritten["name_begin"] == nil {
- t.T.Fatalf(`view data[name_begin] is nil,
- BeginRequest's ctx.ViewData call have been overridden by Get's return mvc.View {Data: }.
- Total view data: %v`, dataWritten)
- }
-
- if dataWritten["name"] == nil {
- t.T.Fatalf("view data[name] is nil, Get's return mvc.View {Data: } didn't work. Total view data: %v", dataWritten)
- }
-}
-
-func (t *testControllerViewResultRespectCtxViewData) Get() mvc.Result {
- return mvc.View{
- Name: "doesnt_exists.html",
- Data: context.Map{"name": "iris"}, // we care about this only.
- Code: iris.StatusInternalServerError,
- }
-}
-
-func TestControllerViewResultRespectCtxViewData(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testControllerViewResultRespectCtxViewData), t)
- e := httptest.New(t, app)
-
- e.GET("/").Expect().Status(iris.StatusInternalServerError)
-}
diff --git a/mvc/method_result_view.go b/mvc/method_result_view.go
deleted file mode 100644
index 59742fb2..00000000
--- a/mvc/method_result_view.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package mvc
-
-import (
- "strings"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/mvc/activator/methodfunc"
-
- "github.com/fatih/structs"
-)
-
-// View completes the `methodfunc.Result` interface.
-// It's being used as an alternative return value which
-// wraps the template file name, layout, (any) view data, status code and error.
-// It's smart enough to complete the request and send the correct response to the client.
-//
-// Example at: https://github.com/kataras/iris/blob/master/_examples/mvc/overview/web/controllers/hello_controller.go.
-type View struct {
- Name string
- Layout string
- Data interface{} // map or a custom struct.
- Code int
- Err error
-}
-
-var _ methodfunc.Result = View{}
-
-const dotB = byte('.')
-
-// DefaultViewExt is the default extension if `view.Name `is missing,
-// but note that it doesn't care about
-// the app.RegisterView(iris.$VIEW_ENGINE("./$dir", "$ext"))'s $ext.
-// so if you don't use the ".html" as extension for your files
-// you have to append the extension manually into the `view.Name`
-// or change this global variable.
-var DefaultViewExt = ".html"
-
-func ensureExt(s string) string {
- if len(s) == 0 {
- return "index" + DefaultViewExt
- }
-
- if strings.IndexByte(s, dotB) < 1 {
- s += DefaultViewExt
- }
-
- return s
-}
-
-// Dispatch writes the template filename, template layout and (any) data to the client.
-// Completes the `Result` interface.
-func (r View) Dispatch(ctx context.Context) { // r as Response view.
- if r.Err != nil {
- if r.Code < 400 {
- r.Code = methodfunc.DefaultErrStatusCode
- }
- ctx.StatusCode(r.Code)
- ctx.WriteString(r.Err.Error())
- ctx.StopExecution()
- return
- }
-
- if r.Code > 0 {
- ctx.StatusCode(r.Code)
- }
-
- if r.Name != "" {
- r.Name = ensureExt(r.Name)
-
- if r.Layout != "" {
- r.Layout = ensureExt(r.Layout)
- ctx.ViewLayout(r.Layout)
- }
-
- if r.Data != nil {
- // In order to respect any c.Ctx.ViewData that may called manually before;
- dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
- if ctx.Values().Get(dataKey) == nil {
- // if no c.Ctx.ViewData then it's empty do a
- // pure set, it's faster.
- ctx.Values().Set(dataKey, r.Data)
- } else {
- // else check if r.Data is map or struct, if struct convert it to map,
- // do a range loop and set the data one by one.
- // context.Map is actually a map[string]interface{} but we have to make that check;
- if m, ok := r.Data.(map[string]interface{}); ok {
- setViewData(ctx, m)
- } else if m, ok := r.Data.(context.Map); ok {
- setViewData(ctx, m)
- } else if structs.IsStruct(r.Data) {
- setViewData(ctx, structs.Map(r))
- }
- }
- }
-
- ctx.View(r.Name)
- }
-}
-
-func setViewData(ctx context.Context, data map[string]interface{}) {
- for k, v := range data {
- ctx.ViewData(k, v)
- }
-}
diff --git a/mvc/session_controller.go b/mvc/session_controller.go
deleted file mode 100644
index bc403311..00000000
--- a/mvc/session_controller.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package mvc
-
-import (
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/mvc/activator"
- "github.com/kataras/iris/sessions"
-
- "github.com/kataras/golog"
-)
-
-var defaultManager = sessions.New(sessions.Config{})
-
-// SessionController is a simple `Controller` implementation
-// which requires a binded session manager in order to give
-// direct access to the current client's session via its `Session` field.
-type SessionController struct {
- C
-
- Manager *sessions.Sessions
- Session *sessions.Session
-}
-
-// OnActivate called, once per application lifecycle NOT request,
-// 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(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`)
- }
-}
-
-// BeginRequest calls the Controller's BeginRequest
-// and tries to initialize the current user's Session.
-func (s *SessionController) BeginRequest(ctx context.Context) {
- s.C.BeginRequest(ctx)
- if s.Manager == nil {
- ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
-because the SessionController should predict this on its activation state and use a default one automatically`)
- return
- }
-
- s.Session = s.Manager.Start(ctx)
-}
diff --git a/mvc/strutil.go b/mvc/strutil.go
deleted file mode 100644
index 686da607..00000000
--- a/mvc/strutil.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package mvc
-
-import (
- "strings"
- "unicode"
-)
-
-func findCtrlWords(ctrlName string) (w []string) {
- end := len(ctrlName)
- start := -1
- for i, n := 0, end; i < n; i++ {
- c := rune(ctrlName[i])
- if unicode.IsUpper(c) {
- // it doesn't count the last uppercase
- if start != -1 {
- end = i
- w = append(w, strings.ToLower(ctrlName[start:end]))
- }
- start = i
- continue
- }
- end = i + 1
-
- }
-
- // We can't omit the last name, we have to take it.
- // because of controller names like
- // "UserProfile", we need to return "user", "profile"
- // if "UserController", we need to return "user"
- // if "User", we need to return "user".
- last := ctrlName[start:end]
- if last == ctrlSuffix {
- return
- }
-
- w = append(w, strings.ToLower(last))
- return
-}
diff --git a/mvc/strutil_test.go b/mvc/strutil_test.go
deleted file mode 100644
index 34648ed4..00000000
--- a/mvc/strutil_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package mvc
-
-import (
- "testing"
-)
-
-func TestFindCtrlWords(t *testing.T) {
- var tests = map[string][]string{
- "UserController": {"user"},
- "UserPostController": {"user", "post"},
- "ProfileController": {"profile"},
- "UserProfileController": {"user", "profile"},
- "UserProfilePostController": {"user", "profile", "post"},
- "UserProfile": {"user", "profile"},
- "Profile": {"profile"},
- "User": {"user"},
- }
-
- for ctrlName, expected := range tests {
- words := findCtrlWords(ctrlName)
- if len(expected) != len(words) {
- t.Fatalf("expected words and return don't have the same length: [%d] != [%d] | '%s' != '%s'",
- len(expected), len(words), expected, words)
- }
- for i, w := range words {
- if expected[i] != w {
- t.Fatalf("expected word is not equal with the return one: '%s' != '%s'", expected[i], w)
- }
- }
- }
-}
diff --git a/mvc2/bind.go b/mvc2/bind.go
index 5cfb8593..63f29cb1 100644
--- a/mvc2/bind.go
+++ b/mvc2/bind.go
@@ -1,7 +1,7 @@
package mvc2
import (
- "github.com/kataras/di"
+ "github.com/kataras/iris/mvc2/di"
"reflect"
)
diff --git a/mvc2/controller.go b/mvc2/controller.go
index 4865644c..af17b21e 100644
--- a/mvc2/controller.go
+++ b/mvc2/controller.go
@@ -3,8 +3,9 @@ package mvc2
import (
"fmt"
"reflect"
+ "strings"
- "github.com/kataras/di"
+ "github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
@@ -75,9 +76,9 @@ func (c *C) EndRequest(ctx context.Context) {}
// ControllerActivator returns a new controller type info description.
// Its functionality can be overriden by the end-dev.
type ControllerActivator struct {
- // the router is used on the `Activate` and can be used by end-dev on the `OnActivate`
+ // the router is used on the `Activate` and can be used by end-dev on the `BeforeActivate`
// to register any custom controller's functions as handlers but we will need it here
- // in order to not create a new type like `ActivationPayload` for the `OnActivate`.
+ // in order to not create a new type like `ActivationPayload` for the `BeforeActivate`.
Router router.Party
// initRef BaseController // the BaseController as it's passed from the end-dev.
@@ -88,7 +89,7 @@ type ControllerActivator struct {
FullName string
// the methods names that is already binded to a handler,
- // the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
+ // the BeginRequest, EndRequest and BeforeActivate are reserved by the internal implementation.
reservedMethods []string
// the bindings that comes from the Engine and the controller's filled fields if any.
@@ -100,6 +101,16 @@ type ControllerActivator struct {
injector *di.StructInjector
}
+func getNameOf(typ reflect.Type) string {
+ elemTyp := di.IndirectType(typ)
+
+ typName := elemTyp.Name()
+ pkgPath := elemTyp.PkgPath()
+ fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
+
+ return fullname
+}
+
func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator {
var (
val = reflect.ValueOf(controller)
@@ -116,7 +127,7 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
// the end-developer when declaring the controller,
// activate listeners needs them in order to know if something set-ed already or not,
// look `BindTypeExists`.
- d.Values = append(lookupNonZeroFieldsValues(val), d.Values...)
+ d.Values = append(di.LookupNonZeroFieldsValues(val), d.Values...)
c := &ControllerActivator{
// give access to the Router to the end-devs if they need it for some reason,
@@ -142,7 +153,7 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
}
func whatReservedMethods(typ reflect.Type) []string {
- methods := []string{"OnActivate"}
+ methods := []string{"BeforeActivate"}
if isBaseController(typ) {
methods = append(methods, "BeginRequest", "EndRequest")
}
@@ -182,7 +193,6 @@ func (c *ControllerActivator) parseMethods() {
}
func (c *ControllerActivator) activate() {
- c.injector = c.Dependencies.Struct(c.Value)
c.parseMethods()
}
@@ -233,18 +243,40 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
// get the function's input arguments' bindings.
funcDependencies := c.Dependencies.Clone()
- funcDependencies.Add(pathParams...)
+ funcDependencies.AddValue(pathParams...)
funcInjector := funcDependencies.Func(m.Func)
- // we will make use of 'n' to make a slice of reflect.Value
- // to pass into if the function has input arguments that
- // are will being filled by the funcDependencies.
- n := len(funcIn)
// the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request.
- elemTyp := indirectTyp(c.Type)
- implementsBase := isBaseController(c.Type)
+ // Remember:
+ // we cannot simply do that and expect to work:
+ // hasStructInjector = c.injector != nil && c.injector.Valid
+ // hasFuncInjector = funcInjector != nil && funcInjector.Valid
+ // because
+ // the `Handle` can be called from `BeforeActivate` callbacks
+ // and before activation, the c.injector is nil because
+ // we may not have the dependencies binded yet. But if `c.injector.Valid`
+ // inside the Handelr works because it's set on the `activate()` method.
+ // To solve this we can make check on the FIRST `Handle`,
+ // if c.injector is nil, then set it with the current bindings,
+ // so the user should bind the dependencies needed before the `Handle`
+ // this is a logical flow, so we will choose that one ->
+ if c.injector == nil {
+ c.injector = c.Dependencies.Struct(c.Value)
+ }
+ var (
+ hasStructInjector = c.injector != nil && c.injector.Valid
+ hasFuncInjector = funcInjector != nil && funcInjector.Valid
+
+ implementsBase = isBaseController(c.Type)
+ // we will make use of 'n' to make a slice of reflect.Value
+ // to pass into if the function has input arguments that
+ // are will being filled by the funcDependencies.
+ n = len(funcIn)
+
+ elemTyp = di.IndirectType(c.Type)
+ )
handler := func(ctx context.Context) {
ctrl := reflect.New(elemTyp)
@@ -263,11 +295,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
defer b.EndRequest(ctx)
}
- if !c.injector.Valid && !funcInjector.Valid {
+ if !hasStructInjector && !hasFuncInjector {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else {
ctxValue := reflect.ValueOf(ctx)
- if c.injector.Valid {
+ if hasStructInjector {
elem := ctrl.Elem()
c.injector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
@@ -276,13 +308,13 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args.
- if !funcInjector.Valid {
+ if !hasFuncInjector {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
}
}
// otherwise, it has one or more valid input binders,
// make the input and call the func using those.
- if funcInjector.Valid {
+ if hasFuncInjector {
in := make([]reflect.Value, n, n)
in[0] = ctrl
funcInjector.Inject(&in, ctxValue)
diff --git a/mvc2/controller_handle_test.go b/mvc2/controller_handle_test.go
index 26b77f17..4857aeea 100644
--- a/mvc2/controller_handle_test.go
+++ b/mvc2/controller_handle_test.go
@@ -24,12 +24,11 @@ func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.reqField = ctx.URLParam("reqfield")
}
-func (c *testControllerHandle) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
- // t.Handle("GET", "/", "Get")
- 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 *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) {
+ ca.Handle("GET", "/histatic", "HiStatic")
+ ca.Handle("GET", "/hiservice", "HiService")
+ ca.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
+ ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
}
func (c *testControllerHandle) HiStatic() string {
@@ -51,8 +50,10 @@ func (c *testControllerHandle) HiParamEmptyInputBy() string {
func TestControllerHandle(t *testing.T) {
app := iris.New()
- m := New()
- m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testControllerHandle))
+ m := NewEngine()
+ m.Dependencies.Add(&TestServiceImpl{prefix: "service:"})
+ m.Controller(app, new(testControllerHandle))
+
e := httptest.New(t, app)
// test the index, is not part of the current package's implementation but do it.
diff --git a/mvc2/controller_test.go b/mvc2/controller_test.go
index 785cf133..9ab27476 100644
--- a/mvc2/controller_test.go
+++ b/mvc2/controller_test.go
@@ -63,7 +63,7 @@ func (c *testControllerAny) Any() {
func TestControllerMethodFuncs(t *testing.T) {
app := iris.New()
- m := New()
+ m := NewEngine()
m.Controller(app, new(testController))
m.Controller(app.Party("/all"), new(testControllerAll))
m.Controller(app.Party("/any"), new(testControllerAny))
@@ -113,7 +113,7 @@ func (c *testControllerBeginAndEndRequestFunc) Post() {
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New()
- New().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
+ NewEngine().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
usernames := []string{
@@ -156,7 +156,7 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
ctx.Writef("forbidden")
}
- New().Controller(app.Party("/profile/{username}", middlewareCheck),
+ NewEngine().Controller(app.Party("/profile/{username}", middlewareCheck),
new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
@@ -230,7 +230,7 @@ func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) {
func TestControllerEndRequestAwareness(t *testing.T) {
app := iris.New()
- New().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
+ NewEngine().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
e := httptest.New(t, app)
usernames := []string{
@@ -284,8 +284,8 @@ func TestControllerBind(t *testing.T) {
myTitlePtr := &testBindType{title: t1}
// test bind value to value of the correct type
myTitleV := testBindType{title: t2}
- m := New()
- m.Bind(myTitlePtr, myTitleV)
+ m := NewEngine()
+ m.Dependencies.Add(myTitlePtr, myTitleV)
// or just app
m.Controller(app.Party("/"), new(testControllerBindStruct))
m.Controller(app.Party("/deep"), new(testControllerBindDeep))
@@ -345,8 +345,9 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
)
app := iris.New()
- New().Bind(&testBindType{title: title}).
- Controller(app.Party("/user/{username}"), new(testCtrl0))
+ m := NewEngine()
+ m.Dependencies.Add(&testBindType{title: title})
+ m.Controller(app.Party("/user/{username}"), new(testCtrl0))
e := httptest.New(t, app)
e.GET("/user/" + username).Expect().
@@ -378,7 +379,7 @@ func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} /
func TestControllerRelPathFromFunc(t *testing.T) {
app := iris.New()
- New().Controller(app, new(testControllerRelPathFromFunc))
+ NewEngine().Controller(app, new(testControllerRelPathFromFunc))
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusOK).
@@ -420,12 +421,8 @@ type testControllerActivateListener struct {
TitlePointer *testBindType
}
-func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
- if !ca.Dependencies.BindExists(&testBindType{}) {
- ca.Dependencies.Bind(&testBindType{
- title: "default title",
- })
- }
+func (c *testControllerActivateListener) BeforeActivate(ca *ControllerActivator) {
+ ca.Dependencies.AddOnce(&testBindType{title: "default title"})
}
func (c *testControllerActivateListener) Get() string {
@@ -434,12 +431,14 @@ func (c *testControllerActivateListener) Get() string {
func TestControllerActivateListener(t *testing.T) {
app := iris.New()
- New().Controller(app, new(testControllerActivateListener))
- New().Bind(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
+ NewEngine().Controller(app, new(testControllerActivateListener))
+ m := NewEngine()
+ m.Dependencies.Add(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
title: "my title",
- }).Controller(app.Party("/manual"), new(testControllerActivateListener))
+ })
+ m.Controller(app.Party("/manual"), new(testControllerActivateListener))
// or
- New().Controller(app.Party("/manual2"), &testControllerActivateListener{
+ NewEngine().Controller(app.Party("/manual2"), &testControllerActivateListener{
TitlePointer: &testBindType{
title: "my title",
},
diff --git a/mvc2/di/di.go b/mvc2/di/di.go
new file mode 100644
index 00000000..d56bed86
--- /dev/null
+++ b/mvc2/di/di.go
@@ -0,0 +1,92 @@
+package di
+
+import "reflect"
+
+type (
+ // Hijacker is a type which is used to catch fields or function's input argument
+ // to bind a custom object based on their type.
+ Hijacker func(reflect.Type) (*BindObject, bool)
+ // TypeChecker checks if a specific field's or function input argument's
+ // is valid to be binded.
+ TypeChecker func(reflect.Type) bool
+)
+
+// D is the Dependency Injection container,
+// it contains the Values that can be changed before the injectors.
+// `Struct` and the `Func` methods returns an injector for specific
+// struct instance-value or function.
+type D struct {
+ Values
+
+ hijacker Hijacker
+ goodFunc TypeChecker
+}
+
+// New creates and returns a new Dependency Injection container.
+// See `Values` field and `Func` and `Struct` methods for more.
+func New() *D {
+ return &D{}
+}
+
+// Hijack sets a hijacker function, read the `Hijacker` type for more explaination.
+func (d *D) Hijack(fn Hijacker) *D {
+ d.hijacker = fn
+ return d
+}
+
+// GoodFunc sets a type checker for a valid function that can be binded,
+// read the `TypeChecker` type for more explaination.
+func (d *D) GoodFunc(fn TypeChecker) *D {
+ d.goodFunc = fn
+ return d
+}
+
+// Clone returns a new Dependency Injection container, it adopts the
+// parent's (current "D") hijacker, good func type checker and all dependencies values.
+func (d *D) Clone() *D {
+ clone := New()
+ clone.hijacker = d.hijacker
+ clone.goodFunc = d.goodFunc
+
+ // copy the current dynamic bindings (func binders)
+ // and static struct bindings (services) to this new child.
+ if n := len(d.Values); n > 0 {
+ values := make(Values, n, n)
+ copy(values, d.Values)
+ clone.Values = values
+ }
+
+ return clone
+}
+
+// Struct is being used to return a new injector based on
+// a struct value instance, if it contains fields that the types of those
+// are matching with one or more of the `Values` then they are binded
+// with the injector's `Inject` and `InjectElem` methods.
+func (d *D) Struct(s interface{}) *StructInjector {
+ if s == nil {
+ return nil
+ }
+ v := ValueOf(s)
+
+ return MakeStructInjector(
+ v,
+ d.hijacker,
+ d.goodFunc,
+ d.Values...,
+ )
+}
+
+// Func is being used to return a new injector based on
+// a function, if it contains input arguments that the types of those
+// are matching with one or more of the `Values` then they are binded
+// to the function's input argument when called
+// with the injector's `Fill` method.
+func (d *D) Func(fn interface{}) *FuncInjector {
+ return MakeFuncInjector(
+ ValueOf(fn),
+ d.hijacker,
+ d.goodFunc,
+ d.Values...,
+ )
+}
diff --git a/mvc2/di/func.go b/mvc2/di/func.go
new file mode 100644
index 00000000..ef9d76ca
--- /dev/null
+++ b/mvc2/di/func.go
@@ -0,0 +1,108 @@
+package di
+
+import (
+ "reflect"
+)
+
+type (
+ targetFuncInput struct {
+ Object *BindObject
+ InputIndex int
+ }
+
+ FuncInjector struct {
+ // the original function, is being used
+ // only the .Call, which is refering to the same function, always.
+ fn reflect.Value
+
+ inputs []*targetFuncInput
+ // Length is the number of the valid, final binded input arguments.
+ Length int
+ // Valid is True when `Length` is > 0, it's statically set-ed for
+ // performance reasons.
+ Valid bool //
+ }
+)
+
+func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
+ typ := IndirectType(fn.Type())
+ s := &FuncInjector{
+ fn: fn,
+ }
+
+ if !IsFunc(typ) {
+ return s
+ }
+
+ n := typ.NumIn()
+
+ // function input can have many values of the same types,
+ // so keep track of them in order to not set a func input to a next bind value,
+ // i.e (string, string) with two different binder funcs because of the different param's name.
+ consumedValues := make(map[int]bool, n)
+
+ for i := 0; i < n; i++ {
+ inTyp := typ.In(i)
+
+ if hijack != nil {
+ if b, ok := hijack(inTyp); ok && b != nil {
+ s.inputs = append(s.inputs, &targetFuncInput{
+ InputIndex: i,
+ Object: b,
+ })
+
+ continue
+ }
+ }
+
+ for valIdx, val := range values {
+ if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
+ continue
+ }
+ inTyp := typ.In(i)
+
+ // the binded values to the func's inputs.
+ b, err := MakeBindObject(val, goodFunc)
+
+ if err != nil {
+ return s // if error stop here.
+ }
+
+ if b.IsAssignable(inTyp) {
+ // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
+ // i, b.Type.String(), val.String(), val.Pointer())
+ s.inputs = append(s.inputs, &targetFuncInput{
+ InputIndex: i,
+ Object: &b,
+ })
+
+ consumedValues[valIdx] = true
+ break
+ }
+ }
+ }
+
+ s.Length = n
+ s.Valid = len(s.inputs) > 0
+ return s
+}
+
+func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
+ args := *in
+ for _, input := range s.inputs {
+ input.Object.Assign(ctx, func(v reflect.Value) {
+ // fmt.Printf("assign input index: %d for value: %v\n",
+ // input.InputIndex, v.String())
+ args[input.InputIndex] = v
+ })
+
+ }
+
+ *in = args
+}
+
+func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
+ in := make([]reflect.Value, s.Length, s.Length)
+ s.Inject(&in, ctx...)
+ return s.fn.Call(in)
+}
diff --git a/mvc2/di/object.go b/mvc2/di/object.go
new file mode 100644
index 00000000..29de55f5
--- /dev/null
+++ b/mvc2/di/object.go
@@ -0,0 +1,97 @@
+package di
+
+import (
+ "errors"
+ "reflect"
+)
+
+type BindType uint32
+
+const (
+ Static BindType = iota // simple assignable value, a static value.
+ Dynamic // dynamic value, depends on some input arguments from the caller.
+)
+
+type BindObject struct {
+ Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
+ Value reflect.Value
+
+ BindType BindType
+ ReturnValue func([]reflect.Value) reflect.Value
+}
+
+func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) {
+ if IsFunc(v) {
+ b.BindType = Dynamic
+ b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc)
+ } else {
+ b.BindType = Static
+ b.Type = v.Type()
+ b.Value = v
+ }
+
+ return
+}
+
+var errBad = errors.New("bad")
+
+// MakeReturnValue takes any function
+// that accept custom values and returns something,
+// it returns a binder function, which accepts a slice of reflect.Value
+// and returns a single one reflect.Value for that.
+// It's being used to resolve the input parameters on a "x" consumer faster.
+//
+// The "fn" can have the following form:
+// `func(myService) MyViewModel`.
+//
+// The return type of the "fn" should be a value instance, not a pointer, for your own protection.
+// The binder function should return only one value.
+func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
+ typ := IndirectType(fn.Type())
+
+ // invalid if not a func.
+ if typ.Kind() != reflect.Func {
+ return nil, typ, errBad
+ }
+
+ // invalid if not returns one single value.
+ if typ.NumOut() != 1 {
+ return nil, typ, errBad
+ }
+
+ if goodFunc != nil {
+ if !goodFunc(typ) {
+ return nil, typ, errBad
+ }
+ }
+
+ outTyp := typ.Out(0)
+ zeroOutVal := reflect.New(outTyp).Elem()
+
+ bf := func(ctxValue []reflect.Value) reflect.Value {
+ results := fn.Call(ctxValue)
+ if len(results) == 0 {
+ return zeroOutVal
+ }
+
+ v := results[0]
+ if !v.IsValid() {
+ return zeroOutVal
+ }
+ return v
+ }
+
+ return bf, outTyp, nil
+}
+
+func (b *BindObject) IsAssignable(to reflect.Type) bool {
+ return equalTypes(b.Type, to)
+}
+
+func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
+ if b.BindType == Dynamic {
+ toSetter(b.ReturnValue(ctx))
+ return
+ }
+ toSetter(b.Value)
+}
diff --git a/mvc2/di/reflect.go b/mvc2/di/reflect.go
new file mode 100644
index 00000000..0c28bf02
--- /dev/null
+++ b/mvc2/di/reflect.go
@@ -0,0 +1,180 @@
+package di
+
+import "reflect"
+
+var emptyIn = []reflect.Value{}
+
+// IsZero returns true if a value is nil, remember boolean's false is zero.
+// Remember; fields to be checked should be exported otherwise it returns false.
+func IsZero(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Struct:
+ zero := true
+ for i := 0; i < v.NumField(); i++ {
+ zero = zero && IsZero(v.Field(i))
+ }
+
+ if typ := v.Type(); typ != nil && v.IsValid() {
+ f, ok := typ.MethodByName("IsZero")
+ // if not found
+ // if has input arguments (1 is for the value receiver, so > 1 for the actual input args)
+ // if output argument is not boolean
+ // then skip this IsZero user-defined function.
+ if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool {
+ return zero
+ }
+
+ method := v.Method(f.Index)
+ // no needed check but:
+ if method.IsValid() && !method.IsNil() {
+ // it shouldn't panic here.
+ zero = method.Call(emptyIn)[0].Interface().(bool)
+ }
+ }
+
+ return zero
+ case reflect.Func, reflect.Map, reflect.Slice:
+ return v.IsNil()
+ case reflect.Array:
+ zero := true
+ for i := 0; i < v.Len(); i++ {
+ zero = zero && IsZero(v.Index(i))
+ }
+ return zero
+ }
+ // if not any special type then use the reflect's .Zero
+ // usually for fields, but remember if it's boolean and it's false
+ // then it's zero, even if set-ed.
+
+ if !v.CanInterface() {
+ // if can't interface, i.e return value from unexported field or method then return false
+ return false
+ }
+ zero := reflect.Zero(v.Type())
+ return v.Interface() == zero.Interface()
+}
+
+func IndirectValue(v reflect.Value) reflect.Value {
+ return reflect.Indirect(v)
+}
+
+func ValueOf(o interface{}) reflect.Value {
+ if v, ok := o.(reflect.Value); ok {
+ return v
+ }
+
+ return reflect.ValueOf(o)
+}
+
+func IndirectType(typ reflect.Type) reflect.Type {
+ switch typ.Kind() {
+ case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+ return typ.Elem()
+ }
+ return typ
+}
+
+func goodVal(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
+ if v.IsNil() {
+ return false
+ }
+ }
+
+ return v.IsValid()
+}
+
+func IsFunc(kindable interface {
+ Kind() reflect.Kind
+}) bool {
+ return kindable.Kind() == reflect.Func
+}
+
+func equalTypes(got reflect.Type, expected reflect.Type) bool {
+ if got == expected {
+ return true
+ }
+ // if accepts an interface, check if the given "got" type does
+ // implement this "expected" user handler's input argument.
+ if expected.Kind() == reflect.Interface {
+ // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String())
+ return got.Implements(expected)
+ }
+ return false
+}
+
+// for controller's fields only.
+func structFieldIgnored(f reflect.StructField) bool {
+ if !f.Anonymous {
+ return true // if not anonymous(embedded), ignore it.
+ }
+
+ s := f.Tag.Get("ignore")
+ return s == "true" // if has an ignore tag then ignore it.
+}
+
+type field struct {
+ Type reflect.Type
+ Index []int // the index of the field, slice if it's part of a embedded struct
+ Name string // the actual name
+
+ // this could be empty, but in our cases it's not,
+ // it's filled with the bind object (as service which means as static value)
+ // and it's filled from the lookupFields' caller.
+ AnyValue reflect.Value
+}
+
+func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
+ if elemTyp.Kind() != reflect.Struct {
+ return
+ }
+
+ for i, n := 0, elemTyp.NumField(); i < n; i++ {
+ f := elemTyp.Field(i)
+
+ if IndirectType(f.Type).Kind() == reflect.Struct &&
+ !structFieldIgnored(f) {
+ fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
+ continue
+ }
+
+ // skip unexported fields here,
+ // after the check for embedded structs, these can be binded if their
+ // fields are exported.
+ if f.PkgPath != "" {
+ continue
+ }
+
+ index := []int{i}
+ if len(parentIndex) > 0 {
+ index = append(parentIndex, i)
+ }
+
+ field := field{
+ Type: f.Type,
+ Name: f.Name,
+ Index: index,
+ }
+
+ fields = append(fields, field)
+ }
+
+ return
+}
+
+// LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance.
+// It returns a slice of reflect.Value (same type as `Values`) that can be binded,
+// like the end-developer's custom values.
+func LookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
+ elem := IndirectValue(v)
+ fields := lookupFields(IndirectType(v.Type()), nil)
+ for _, f := range fields {
+
+ if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !IsZero(fieldVal) {
+ bindValues = append(bindValues, fieldVal)
+ }
+ }
+
+ return
+}
diff --git a/mvc2/di/struct.go b/mvc2/di/struct.go
new file mode 100644
index 00000000..76e177b0
--- /dev/null
+++ b/mvc2/di/struct.go
@@ -0,0 +1,84 @@
+package di
+
+import "reflect"
+
+type (
+ targetStructField struct {
+ Object *BindObject
+ FieldIndex []int
+ }
+
+ StructInjector struct {
+ elemType reflect.Type
+ //
+ fields []*targetStructField
+ Valid bool // is True when contains fields and it's a valid target struct.
+ }
+)
+
+func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
+ s := &StructInjector{
+ elemType: IndirectType(v.Type()),
+ }
+
+ fields := lookupFields(s.elemType, nil)
+ for _, f := range fields {
+
+ if hijack != nil {
+ if b, ok := hijack(f.Type); ok && b != nil {
+ s.fields = append(s.fields, &targetStructField{
+ FieldIndex: f.Index,
+ Object: b,
+ })
+
+ continue
+ }
+ }
+
+ for _, val := range values {
+ // the binded values to the struct's fields.
+ b, err := MakeBindObject(val, goodFunc)
+
+ if err != nil {
+ return s // if error stop here.
+ }
+
+ if b.IsAssignable(f.Type) {
+ // fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
+ s.fields = append(s.fields, &targetStructField{
+ FieldIndex: f.Index,
+ Object: &b,
+ })
+ break
+ }
+
+ }
+ }
+
+ s.Valid = len(s.fields) > 0
+ return s
+}
+
+func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
+ if dest == nil {
+ return
+ }
+
+ v := IndirectValue(ValueOf(dest))
+ s.InjectElem(v, ctx...)
+}
+
+func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) {
+ for _, f := range s.fields {
+ f.Object.Assign(ctx, func(v reflect.Value) {
+ // fmt.Printf("%s for %s at index: %d\n", destElem.Type().String(), f.Object.Type.String(), f.FieldIndex)
+ destElem.FieldByIndex(f.FieldIndex).Set(v)
+ })
+ }
+}
+
+func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value {
+ dest := reflect.New(s.elemType)
+ s.InjectElem(dest, ctx...)
+ return dest
+}
diff --git a/mvc2/di/values.go b/mvc2/di/values.go
new file mode 100644
index 00000000..129fed0c
--- /dev/null
+++ b/mvc2/di/values.go
@@ -0,0 +1,100 @@
+package di
+
+import (
+ "reflect"
+)
+
+type Values []reflect.Value
+
+func NewValues() Values {
+ return Values{}
+}
+
+// Add binds values to this controller, if you want to share
+// binding values between controllers use the Engine's `Bind` function instead.
+func (bv *Values) Add(values ...interface{}) {
+ for _, val := range values {
+ bv.AddValue(reflect.ValueOf(val))
+ }
+}
+
+// AddValue same as `Add` but accepts reflect.Value
+// instead.
+func (bv *Values) AddValue(values ...reflect.Value) {
+ for _, v := range values {
+ if !goodVal(v) {
+ continue
+ }
+ *bv = append(*bv, v)
+ }
+}
+
+// Remove unbinds a binding value based on the type,
+// it returns true if at least one field is not binded anymore.
+//
+// The "n" indicates the number of elements to remove, if <=0 then it's 1,
+// this is useful because you may have bind more than one value to two or more fields
+// with the same type.
+func (bv *Values) Remove(value interface{}, n int) bool {
+ return bv.remove(reflect.TypeOf(value), n)
+}
+
+func (bv *Values) remove(typ reflect.Type, n int) (ok bool) {
+ input := *bv
+ for i, in := range input {
+ if equalTypes(in.Type(), typ) {
+ ok = true
+ input = input[:i+copy(input[i:], input[i+1:])]
+ if n > 1 {
+ continue
+ }
+ break
+ }
+ }
+
+ *bv = input
+
+ return
+}
+
+// Has returns true if a binder responsible to
+// bind and return a type of "typ" is already registered to this controller.
+func (bv *Values) Has(value interface{}) bool {
+ return bv.valueTypeExists(reflect.TypeOf(value))
+}
+
+func (bv *Values) valueTypeExists(typ reflect.Type) bool {
+ input := *bv
+ for _, in := range input {
+ if equalTypes(in.Type(), typ) {
+ return true
+ }
+ }
+ return false
+}
+
+// AddOnce binds a value to the controller's field with the same type,
+// if it's not binded already.
+//
+// Returns false if binded already or the value is not the proper one for binding,
+// otherwise true.
+func (bv *Values) AddOnce(value interface{}) bool {
+ return bv.addIfNotExists(reflect.ValueOf(value))
+}
+
+func (bv *Values) addIfNotExists(v reflect.Value) bool {
+ var (
+ typ = v.Type() // no element, raw things here.
+ )
+
+ if !goodVal(v) {
+ return false
+ }
+
+ if bv.valueTypeExists(typ) {
+ return false
+ }
+
+ bv.AddValue(v)
+ return true
+}
diff --git a/mvc2/engine.go b/mvc2/engine.go
index 59454752..0dc900d6 100644
--- a/mvc2/engine.go
+++ b/mvc2/engine.go
@@ -1,65 +1,52 @@
package mvc2
import (
- "errors"
-
- "github.com/kataras/di"
+ "github.com/kataras/iris/mvc2/di"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
-var (
- errNil = errors.New("nil")
- errBad = errors.New("bad")
- errAlreadyExists = errors.New("already exists")
-)
-
type Engine struct {
- dependencies *di.D
+ Dependencies *di.D
}
-func New() *Engine {
+func NewEngine() *Engine {
return &Engine{
- dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
+ Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
}
-func (e *Engine) Bind(values ...interface{}) *Engine {
- e.dependencies.Bind(values...)
- return e
-}
-
-func (e *Engine) Child() *Engine {
- child := New()
- child.dependencies = e.dependencies.Clone()
+func (e *Engine) Clone() *Engine {
+ child := NewEngine()
+ child.Dependencies = e.Dependencies.Clone()
return child
}
func (e *Engine) Handler(handler interface{}) context.Handler {
- h, err := MakeHandler(handler, e.dependencies.Values...)
+ h, err := MakeHandler(handler, e.Dependencies.Values...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
return h
}
-func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) {
- ca := newControllerActivator(router, controller, e.dependencies)
+func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) {
+ ca := newControllerActivator(router, controller, e.Dependencies)
- // give a priority to the "onActivate"
+ // give a priority to the "beforeActivate"
// callbacks, if any.
- for _, cb := range onActivate {
+ for _, cb := range beforeActivate {
cb(ca)
}
- // check if controller has an "OnActivate" function
+ // check if controller has an "BeforeActivate" function
// which accepts the controller activator and call it.
if activateListener, ok := controller.(interface {
- OnActivate(*ControllerActivator)
+ BeforeActivate(*ControllerActivator)
}); ok {
- activateListener.OnActivate(ca)
+ activateListener.BeforeActivate(ca)
}
ca.activate()
diff --git a/mvc2/engine_handler_test.go b/mvc2/engine_handler_test.go
index 547de0c9..9b4cb417 100644
--- a/mvc2/engine_handler_test.go
+++ b/mvc2/engine_handler_test.go
@@ -9,7 +9,8 @@ import (
)
func TestMvcEngineInAndHandler(t *testing.T) {
- m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
+ m := NewEngine()
+ m.Dependencies.Add(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
var (
h1 = m.Handler(testConsumeUserHandler)
diff --git a/mvc2/func_result.go b/mvc2/func_result.go
index d0331949..47a7030d 100644
--- a/mvc2/func_result.go
+++ b/mvc2/func_result.go
@@ -5,6 +5,8 @@ import (
"strings"
"github.com/fatih/structs"
+ "github.com/kataras/iris/mvc2/di"
+
"github.com/kataras/iris/context"
)
@@ -405,7 +407,7 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view.
setViewData(ctx, m)
} else if m, ok := r.Data.(context.Map); ok {
setViewData(ctx, m)
- } else if indirectVal(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
+ } else if di.IndirectValue(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
setViewData(ctx, structs.Map(r))
}
}
diff --git a/mvc2/func_result_test.go b/mvc2/func_result_test.go
index 240a044b..92be4d55 100644
--- a/mvc2/func_result_test.go
+++ b/mvc2/func_result_test.go
@@ -71,7 +71,7 @@ func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) Result
func TestControllerMethodResult(t *testing.T) {
app := iris.New()
- New().Controller(app, new(testControllerMethodResult))
+ NewEngine().Controller(app, new(testControllerMethodResult))
e := httptest.New(t, app)
@@ -175,7 +175,7 @@ func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCust
func TestControllerMethodResultTypes(t *testing.T) {
app := iris.New()
- New().Controller(app, new(testControllerMethodResultTypes))
+ NewEngine().Controller(app, new(testControllerMethodResultTypes))
e := httptest.New(t, app, httptest.LogLevel("debug"))
@@ -266,8 +266,8 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New()
- New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
- ca.Dependencies.Bind(t)
+ NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
+ ca.Dependencies.Add(t)
})
e := httptest.New(t, app)
diff --git a/mvc2/handler.go b/mvc2/handler.go
index 99418677..1213732b 100644
--- a/mvc2/handler.go
+++ b/mvc2/handler.go
@@ -2,7 +2,7 @@ package mvc2
import (
"fmt"
- "github.com/kataras/di"
+ "github.com/kataras/iris/mvc2/di"
"reflect"
"runtime"
@@ -23,7 +23,7 @@ func isContextHandler(handler interface{}) (context.Handler, bool) {
}
func validateHandler(handler interface{}) error {
- if typ := reflect.TypeOf(handler); !isFunc(typ) {
+ if typ := reflect.TypeOf(handler); !di.IsFunc(typ) {
return fmt.Errorf("handler expected to be a kind of func but got typeof(%s)", typ.String())
}
return nil
diff --git a/mvc2/ideas/1/main.go b/mvc2/ideas/1/main.go
new file mode 100644
index 00000000..a63aeda6
--- /dev/null
+++ b/mvc2/ideas/1/main.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/sessions"
+
+ mvc "github.com/kataras/iris/mvc2"
+)
+
+func main() {
+ app := iris.New()
+ mvc.New(app.Party("/todo")).Configure(TodoApp)
+ // no let's have a clear "mvc" package without any conversions and type aliases,
+ // it's one extra import path for a whole new world, it worths it.
+ //
+ // app.UseMVC(app.Party("/todo")).Configure(func(app *iris.MVCApplication))
+
+ app.Run(iris.Addr(":8080"))
+}
+
+func TodoApp(app *mvc.Application) {
+ // You can use normal middlewares at MVC apps of course.
+ app.Router.Use(func(ctx iris.Context) {
+ ctx.Application().Logger().Infof("Path: %s", ctx.Path())
+ ctx.Next()
+ })
+
+ // Add dependencies which will be binding to the controller(s),
+ // can be either a function which accepts an iris.Context and returns a single value (dynamic binding)
+ // or a static struct value (service).
+ app.AddDependencies(
+ mvc.Session(sessions.New(sessions.Config{})),
+ &prefixedLogger{prefix: "DEV"},
+ )
+
+ app.Register(new(TodoController))
+
+ // All dependencies of the parent *mvc.Application
+ // are cloned to that new child, thefore it has access to the same session as well.
+ app.NewChild(app.Router.Party("/sub")).
+ Register(new(TodoSubController))
+}
+
+// If controller's fields (or even its functions) expecting an interface
+// but a struct value is binded then it will check if that struct value implements
+// the interface and if true then it will bind it as expected.
+
+type LoggerService interface {
+ Log(string)
+}
+
+type prefixedLogger struct {
+ prefix string
+}
+
+func (s *prefixedLogger) Log(msg string) {
+ fmt.Printf("%s: %s\n", s.prefix, msg)
+}
+
+type TodoController struct {
+ Logger LoggerService
+
+ Session *sessions.Session
+}
+
+func (c *TodoController) Get() string {
+ count := c.Session.Increment("count", 1)
+
+ body := fmt.Sprintf("Hello from TodoController\nTotal visits from you: %d", count)
+ c.Logger.Log(body)
+ return body
+}
+
+type TodoSubController struct {
+ Session *sessions.Session
+}
+
+func (c *TodoSubController) Get() string {
+ count, _ := c.Session.GetIntDefault("count", 1)
+ return fmt.Sprintf("Hello from TodoSubController.\nRead-only visits count: %d", count)
+}
diff --git a/mvc2/mvc.go b/mvc2/mvc.go
new file mode 100644
index 00000000..c1a7e04b
--- /dev/null
+++ b/mvc2/mvc.go
@@ -0,0 +1,90 @@
+package mvc2
+
+import "github.com/kataras/iris/core/router"
+
+// Application is the high-level compoment of the "mvc" package.
+// It's the API that you will be using to register controllers among wih their
+// dependencies that your controllers may expecting.
+// It contains the Router(iris.Party) in order to be able to register
+// template layout, middleware, done handlers as you used with the
+// standard Iris APIBuilder.
+//
+// The Engine is created by the `New` method and it's the dependencies holder
+// and controllers factory.
+//
+// See `mvc#New` for more.
+type Application struct {
+ Engine *Engine
+ Router router.Party
+}
+
+func newApp(engine *Engine, subRouter router.Party) *Application {
+ return &Application{
+ Engine: engine,
+ Router: subRouter,
+ }
+}
+
+// New returns a new mvc Application based on a "subRouter".
+// Application creates a new engine which is responsible for binding the dependencies
+// and creating and activating the app's controller(s).
+//
+// Example: `New(app.Party("/todo"))`.
+func New(subRouter router.Party) *Application {
+ return newApp(NewEngine(), subRouter)
+}
+
+// Configure can be used to pass one or more functions that accept this
+// Application, use this to add dependencies and controller(s).
+//
+// Example: `New(app.Party("/todo")).Configure(func(mvcApp *mvc.Application){...})`.
+func (app *Application) Configure(configurators ...func(*Application)) *Application {
+ for _, c := range configurators {
+ c(app)
+ }
+ return app
+}
+
+// AddDependencies adds one or more values as dependencies.
+// The value can be a single struct value-instance or a function
+// which has one input and one output, the input should be
+// an `iris.Context` and the output can be any type, that output type
+// will be binded to the controller's field, if matching or to the
+// controller's methods, if matching.
+//
+// The dependencies can be changed per-controller as well via a `beforeActivate`
+// on the `Register` method or when the controller has the `BeforeActivate(c *ControllerActivator)`
+// method defined.
+//
+// It returns this Application.
+//
+// Example: `.AddDependencies(loggerService{prefix: "dev"}, func(ctx iris.Context) User {...})`.
+func (app *Application) AddDependencies(values ...interface{}) *Application {
+ app.Engine.Dependencies.Add(values...)
+ return app
+}
+
+// Register adds a controller for the current Router.
+// It accept any custom struct which its functions will be transformed
+// to routes.
+//
+// The second, optional and variadic argument is the "beforeActive",
+// use that when you want to modify the controller before the activation
+// and registration to the main Iris Application.
+//
+// It returns this Application.
+//
+// Example: `.Register(new(TodoController))`.
+func (app *Application) Register(controller interface{}, beforeActivate ...func(*ControllerActivator)) *Application {
+ app.Engine.Controller(app.Router, controller, beforeActivate...)
+ return app
+}
+
+// NewChild creates and returns a new Application which will be adapted
+// to the "subRouter", it adopts
+// the dependencies bindings from the parent(current) one.
+//
+// Example: `.NewChild(irisApp.Party("/sub")).Register(new(TodoSubController))`.
+func (app *Application) NewChild(subRouter router.Party) *Application {
+ return newApp(app.Engine.Clone(), subRouter)
+}
diff --git a/mvc2/path_param_binder_test.go b/mvc2/path_param_binder_test.go
index 582a1ee2..e03555b2 100644
--- a/mvc2/path_param_binder_test.go
+++ b/mvc2/path_param_binder_test.go
@@ -7,7 +7,8 @@ import (
)
func TestPathParamsBinder(t *testing.T) {
- m := New().Bind(PathParamsBinder)
+ m := NewEngine()
+ m.Dependencies.Add(PathParamsBinder)
got := ""
@@ -25,7 +26,8 @@ func TestPathParamsBinder(t *testing.T) {
}
}
func TestPathParamBinder(t *testing.T) {
- m := New().Bind(PathParamBinder("username"))
+ m := NewEngine()
+ m.Dependencies.Add(PathParamBinder("username"))
got := ""
executed := false
diff --git a/mvc2/reflect.go b/mvc2/reflect.go
index 09d4b47d..e18ece73 100644
--- a/mvc2/reflect.go
+++ b/mvc2/reflect.go
@@ -2,10 +2,8 @@ package mvc2
import (
"reflect"
- "strings"
"github.com/kataras/iris/context"
- "github.com/kataras/pkg/zerocheck"
)
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
@@ -20,58 +18,6 @@ func isContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp)
}
-func indirectVal(v reflect.Value) reflect.Value {
- return reflect.Indirect(v)
-}
-
-func indirectTyp(typ reflect.Type) reflect.Type {
- switch typ.Kind() {
- case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
- return typ.Elem()
- }
- return typ
-}
-
-func goodVal(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
- if v.IsNil() {
- return false
- }
- }
-
- return v.IsValid()
-}
-
-func isFunc(kindable interface {
- Kind() reflect.Kind
-}) bool {
- return kindable.Kind() == reflect.Func
-}
-
-func equalTypes(got reflect.Type, expected reflect.Type) bool {
- if got == expected {
- return true
- }
- // if accepts an interface, check if the given "got" type does
- // implement this "expected" user handler's input argument.
- if expected.Kind() == reflect.Interface {
- // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String())
- return got.Implements(expected)
- }
- return false
-}
-
-func getNameOf(typ reflect.Type) string {
- elemTyp := indirectTyp(typ)
-
- typName := elemTyp.Name()
- pkgPath := elemTyp.PkgPath()
- fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
-
- return fullname
-}
-
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
@@ -80,72 +26,3 @@ func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
}
return funcIn
}
-
-// for controller's fields only.
-func structFieldIgnored(f reflect.StructField) bool {
- if !f.Anonymous {
- return true // if not anonymous(embedded), ignore it.
- }
-
- s := f.Tag.Get("ignore")
- return s == "true" // if has an ignore tag then ignore it.
-}
-
-type field struct {
- Type reflect.Type
- Index []int // the index of the field, slice if it's part of a embedded struct
- Name string // the actual name
-
- // this could be empty, but in our cases it's not,
- // it's filled with the bind object (as service which means as static value)
- // and it's filled from the lookupFields' caller.
- AnyValue reflect.Value
-}
-
-func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
- if elemTyp.Kind() != reflect.Struct {
- return
- }
-
- for i, n := 0, elemTyp.NumField(); i < n; i++ {
- f := elemTyp.Field(i)
-
- if f.PkgPath != "" {
- continue // skip unexported.
- }
-
- if indirectTyp(f.Type).Kind() == reflect.Struct &&
- !structFieldIgnored(f) {
- fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
- continue
- }
-
- index := []int{i}
- if len(parentIndex) > 0 {
- index = append(parentIndex, i)
- }
-
- field := field{
- Type: f.Type,
- Name: f.Name,
- Index: index,
- }
-
- fields = append(fields, field)
- }
-
- return
-}
-
-func lookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
- elem := indirectVal(v)
- fields := lookupFields(indirectTyp(v.Type()), nil)
- for _, f := range fields {
-
- if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !zerocheck.IsZero(fieldVal) {
- bindValues = append(bindValues, fieldVal)
- }
- }
-
- return
-}
diff --git a/mvc2/session_binder.go b/mvc2/session.go
similarity index 100%
rename from mvc2/session_binder.go
rename to mvc2/session.go
diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go
index 8b7e815a..ab6dd394 100644
--- a/mvc2/session_controller.go
+++ b/mvc2/session_controller.go
@@ -17,12 +17,12 @@ type SessionController struct {
Session *sessions.Session
}
-// OnActivate called, once per application lifecycle NOT request,
+// BeforeActivate called, once per application lifecycle NOT request,
// 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(ca *ControllerActivator) {
- if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually {
+func (s *SessionController) BeforeActivate(ca *ControllerActivator) {
+ if didntBindManually := ca.Dependencies.AddOnce(defaultSessionManager); didntBindManually {
ca.Router.GetReporter().Add(
`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.
diff --git a/sessions/session.go b/sessions/session.go
index 3d9d46c8..266f7881 100644
--- a/sessions/session.go
+++ b/sessions/session.go
@@ -167,6 +167,26 @@ func (s *Session) GetIntDefault(key string, defaultValue int) (int, error) {
return defaultValue, errFindParse.Format("int", key, v)
}
+// Increment increments the stored int value saved as "key" by +"n".
+// If value doesn't exist on that "key" then it creates one with the "n" as its value.
+// It returns the new, incremented, value.
+func (s *Session) Increment(key string, n int) (newValue int) {
+ newValue, _ = s.GetIntDefault(key, 0)
+ newValue += n
+ s.Set(key, newValue)
+ return
+}
+
+// Decrement decrements the stored int value saved as "key" by -"n".
+// If value doesn't exist on that "key" then it creates one with the "n" as its value.
+// It returns the new, decremented, value even if it's less than zero.
+func (s *Session) Decrement(key string, n int) (newValue int) {
+ newValue, _ = s.GetIntDefault(key, 0)
+ newValue -= n
+ s.Set(key, newValue)
+ return
+}
+
// GetInt64 same as `Get` but returns its int64 representation,
// if key doesn't exist then it returns -1.
func (s *Session) GetInt64(key string) (int64, error) {