mirror of
https://github.com/kataras/iris.git
synced 2025-03-15 15:06:26 +01:00
remove the old 'mvc' folder - examples are not changed yet - add the 'di' package inside the mvc2 package - which will be renamed to 'mvc' on the next commit - new mvc.Application and some dublications removed - The new version will be version 9 because it will contain breaking changes (not to the end-developer's controllers but to the API they register them) - get ready for 'Christmas Edition' for believers
Former-commit-id: c7114233dee90ee308c0a3e77ec2ad0c361094b8
This commit is contained in:
parent
4e15f4ea88
commit
55dfd195e0
|
@ -10,17 +10,19 @@ import (
|
||||||
|
|
||||||
// TodoController is our TODO app's web controller.
|
// TodoController is our TODO app's web controller.
|
||||||
type TodoController struct {
|
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
|
// BeforeActivate called once before the server ran, and before
|
||||||
// things to the controller.
|
// the routes and dependency binder builded.
|
||||||
func (c *TodoController) OnActivate(ca *mvc.ControllerActivator) {
|
// 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
|
// this could be binded to a controller's function input argument
|
||||||
// if any, or struct field if any:
|
// 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)
|
// ctx.ReadForm(&item)
|
||||||
var (
|
var (
|
||||||
owner = ctx.PostValue("owner")
|
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.
|
// Get handles the GET: /todo route.
|
||||||
func (c *TodoController) Get() []todo.Item {
|
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.
|
// PutCompleteBy handles the PUT: /todo/complete/{id:long} route.
|
||||||
func (c *TodoController) PutCompleteBy(id int64) int {
|
func (c *TodoController) PutCompleteBy(id int64) int {
|
||||||
item, found := c.service.GetByID(id)
|
item, found := c.Service.GetByID(id)
|
||||||
if !found {
|
if !found {
|
||||||
return iris.StatusNotFound
|
return iris.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.OwnerID != c.session.ID() {
|
if item.OwnerID != c.Session.ID() {
|
||||||
return iris.StatusForbidden
|
return iris.StatusForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.service.Complete(item) {
|
if !c.Service.Complete(item) {
|
||||||
return iris.StatusBadRequest
|
return iris.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +69,11 @@ func (c *TodoController) PutCompleteBy(id int64) int {
|
||||||
|
|
||||||
// Post handles the POST: /todo route.
|
// Post handles the POST: /todo route.
|
||||||
func (c *TodoController) Post(newItem todo.Item) int {
|
func (c *TodoController) Post(newItem todo.Item) int {
|
||||||
if newItem.OwnerID != c.session.ID() {
|
if newItem.OwnerID != c.Session.ID() {
|
||||||
return iris.StatusForbidden
|
return iris.StatusForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.service.Save(newItem); err != nil {
|
if err := c.Service.Save(newItem); err != nil {
|
||||||
return iris.StatusBadRequest
|
return iris.StatusBadRequest
|
||||||
}
|
}
|
||||||
return iris.StatusOK
|
return iris.StatusOK
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/errors"
|
"github.com/kataras/iris/core/errors"
|
||||||
"github.com/kataras/iris/core/router/macro"
|
"github.com/kataras/iris/core/router/macro"
|
||||||
"github.com/kataras/iris/mvc/activator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -478,85 +477,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
|
||||||
return
|
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
|
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
|
||||||
// which can be changed.
|
// which can be changed.
|
||||||
var StaticCacheDuration = 20 * time.Second
|
var StaticCacheDuration = 20 * time.Second
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/errors"
|
"github.com/kataras/iris/core/errors"
|
||||||
"github.com/kataras/iris/core/router/macro"
|
"github.com/kataras/iris/core/router/macro"
|
||||||
"github.com/kataras/iris/mvc/activator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Party is here to separate the concept of
|
// Party is here to separate the concept of
|
||||||
|
@ -130,63 +129,6 @@ type Party interface {
|
||||||
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
|
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
|
||||||
Any(registeredPath string, handlers ...context.Handler) []*Route
|
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
|
// StaticHandler returns a new Handler which is ready
|
||||||
// to serve all kind of static files.
|
// to serve all kind of static files.
|
||||||
//
|
//
|
||||||
|
|
129
go19.go
129
go19.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/host"
|
"github.com/kataras/iris/core/host"
|
||||||
"github.com/kataras/iris/core/router"
|
"github.com/kataras/iris/core/router"
|
||||||
"github.com/kataras/iris/mvc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -48,132 +47,4 @@ type (
|
||||||
//
|
//
|
||||||
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
|
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
|
||||||
Party = router.Party
|
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
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
26
mvc/go19.go
26
mvc/go19.go
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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 "<b>text</b>", "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{"<b>text</b>"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
|
|
||||||
return testControllerMethodCustomResult{"<b>OK</b>"}, iris.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
|
|
||||||
return testControllerMethodCustomResult{"<b>internal server error</b>"}, 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("<b>text</b>")
|
|
||||||
|
|
||||||
e.GET("/custom/response").Expect().Status(iris.StatusOK).
|
|
||||||
ContentType("text/html", "utf-8").
|
|
||||||
Body().Equal("<b>text</b>")
|
|
||||||
e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
|
|
||||||
ContentType("text/html", "utf-8").
|
|
||||||
Body().Equal("<b>OK</b>")
|
|
||||||
e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
|
|
||||||
ContentType("text/html", "utf-8").
|
|
||||||
Body().Equal("<b>internal server error</b>")
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package mvc2
|
package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/di"
|
"github.com/kataras/iris/mvc2/di"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ package mvc2
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/di"
|
"github.com/kataras/iris/mvc2/di"
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/router"
|
"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.
|
// ControllerActivator returns a new controller type info description.
|
||||||
// Its functionality can be overriden by the end-dev.
|
// Its functionality can be overriden by the end-dev.
|
||||||
type ControllerActivator struct {
|
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
|
// 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
|
Router router.Party
|
||||||
|
|
||||||
// initRef BaseController // the BaseController as it's passed from the end-dev.
|
// initRef BaseController // the BaseController as it's passed from the end-dev.
|
||||||
|
@ -88,7 +89,7 @@ type ControllerActivator struct {
|
||||||
FullName string
|
FullName string
|
||||||
|
|
||||||
// the methods names that is already binded to a handler,
|
// 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
|
reservedMethods []string
|
||||||
|
|
||||||
// the bindings that comes from the Engine and the controller's filled fields if any.
|
// 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
|
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 {
|
func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator {
|
||||||
var (
|
var (
|
||||||
val = reflect.ValueOf(controller)
|
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,
|
// the end-developer when declaring the controller,
|
||||||
// activate listeners needs them in order to know if something set-ed already or not,
|
// activate listeners needs them in order to know if something set-ed already or not,
|
||||||
// look `BindTypeExists`.
|
// look `BindTypeExists`.
|
||||||
d.Values = append(lookupNonZeroFieldsValues(val), d.Values...)
|
d.Values = append(di.LookupNonZeroFieldsValues(val), d.Values...)
|
||||||
|
|
||||||
c := &ControllerActivator{
|
c := &ControllerActivator{
|
||||||
// give access to the Router to the end-devs if they need it for some reason,
|
// 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 {
|
func whatReservedMethods(typ reflect.Type) []string {
|
||||||
methods := []string{"OnActivate"}
|
methods := []string{"BeforeActivate"}
|
||||||
if isBaseController(typ) {
|
if isBaseController(typ) {
|
||||||
methods = append(methods, "BeginRequest", "EndRequest")
|
methods = append(methods, "BeginRequest", "EndRequest")
|
||||||
}
|
}
|
||||||
|
@ -182,7 +193,6 @@ func (c *ControllerActivator) parseMethods() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ControllerActivator) activate() {
|
func (c *ControllerActivator) activate() {
|
||||||
c.injector = c.Dependencies.Struct(c.Value)
|
|
||||||
c.parseMethods()
|
c.parseMethods()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,18 +243,40 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
|
||||||
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
|
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
|
||||||
// get the function's input arguments' bindings.
|
// get the function's input arguments' bindings.
|
||||||
funcDependencies := c.Dependencies.Clone()
|
funcDependencies := c.Dependencies.Clone()
|
||||||
funcDependencies.Add(pathParams...)
|
funcDependencies.AddValue(pathParams...)
|
||||||
funcInjector := funcDependencies.Func(m.Func)
|
funcInjector := funcDependencies.Func(m.Func)
|
||||||
|
|
||||||
|
// the element value, not the pointer, wil lbe used to create a
|
||||||
|
// new controller on each incoming request.
|
||||||
|
|
||||||
|
// 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
|
// we will make use of 'n' to make a slice of reflect.Value
|
||||||
// to pass into if the function has input arguments that
|
// to pass into if the function has input arguments that
|
||||||
// are will being filled by the funcDependencies.
|
// are will being filled by the funcDependencies.
|
||||||
n := len(funcIn)
|
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)
|
elemTyp = di.IndirectType(c.Type)
|
||||||
|
)
|
||||||
|
|
||||||
handler := func(ctx context.Context) {
|
handler := func(ctx context.Context) {
|
||||||
ctrl := reflect.New(elemTyp)
|
ctrl := reflect.New(elemTyp)
|
||||||
|
@ -263,11 +295,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
|
||||||
defer b.EndRequest(ctx)
|
defer b.EndRequest(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.injector.Valid && !funcInjector.Valid {
|
if !hasStructInjector && !hasFuncInjector {
|
||||||
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
||||||
} else {
|
} else {
|
||||||
ctxValue := reflect.ValueOf(ctx)
|
ctxValue := reflect.ValueOf(ctx)
|
||||||
if c.injector.Valid {
|
if hasStructInjector {
|
||||||
elem := ctrl.Elem()
|
elem := ctrl.Elem()
|
||||||
c.injector.InjectElem(elem, ctxValue)
|
c.injector.InjectElem(elem, ctxValue)
|
||||||
if ctx.IsStopped() {
|
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...
|
// we do this in order to reduce in := make...
|
||||||
// if not func input binders, we execute the handler with empty input args.
|
// 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))
|
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// otherwise, it has one or more valid input binders,
|
// otherwise, it has one or more valid input binders,
|
||||||
// make the input and call the func using those.
|
// make the input and call the func using those.
|
||||||
if funcInjector.Valid {
|
if hasFuncInjector {
|
||||||
in := make([]reflect.Value, n, n)
|
in := make([]reflect.Value, n, n)
|
||||||
in[0] = ctrl
|
in[0] = ctrl
|
||||||
funcInjector.Inject(&in, ctxValue)
|
funcInjector.Inject(&in, ctxValue)
|
||||||
|
|
|
@ -24,12 +24,11 @@ func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
|
||||||
c.reqField = ctx.URLParam("reqfield")
|
c.reqField = ctx.URLParam("reqfield")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testControllerHandle) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
|
func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) {
|
||||||
// t.Handle("GET", "/", "Get")
|
ca.Handle("GET", "/histatic", "HiStatic")
|
||||||
t.Handle("GET", "/histatic", "HiStatic")
|
ca.Handle("GET", "/hiservice", "HiService")
|
||||||
t.Handle("GET", "/hiservice", "HiService")
|
ca.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
|
||||||
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
|
ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
|
||||||
t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testControllerHandle) HiStatic() string {
|
func (c *testControllerHandle) HiStatic() string {
|
||||||
|
@ -51,8 +50,10 @@ func (c *testControllerHandle) HiParamEmptyInputBy() string {
|
||||||
func TestControllerHandle(t *testing.T) {
|
func TestControllerHandle(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
m := New()
|
m := NewEngine()
|
||||||
m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testControllerHandle))
|
m.Dependencies.Add(&TestServiceImpl{prefix: "service:"})
|
||||||
|
m.Controller(app, new(testControllerHandle))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
// test the index, is not part of the current package's implementation but do it.
|
// test the index, is not part of the current package's implementation but do it.
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (c *testControllerAny) Any() {
|
||||||
func TestControllerMethodFuncs(t *testing.T) {
|
func TestControllerMethodFuncs(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
m := New()
|
m := NewEngine()
|
||||||
m.Controller(app, new(testController))
|
m.Controller(app, new(testController))
|
||||||
m.Controller(app.Party("/all"), new(testControllerAll))
|
m.Controller(app.Party("/all"), new(testControllerAll))
|
||||||
m.Controller(app.Party("/any"), new(testControllerAny))
|
m.Controller(app.Party("/any"), new(testControllerAny))
|
||||||
|
@ -113,7 +113,7 @@ func (c *testControllerBeginAndEndRequestFunc) Post() {
|
||||||
|
|
||||||
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
|
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
|
NewEngine().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
usernames := []string{
|
usernames := []string{
|
||||||
|
@ -156,7 +156,7 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
|
||||||
ctx.Writef("forbidden")
|
ctx.Writef("forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
New().Controller(app.Party("/profile/{username}", middlewareCheck),
|
NewEngine().Controller(app.Party("/profile/{username}", middlewareCheck),
|
||||||
new(testControllerBeginAndEndRequestFunc))
|
new(testControllerBeginAndEndRequestFunc))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
@ -230,7 +230,7 @@ func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) {
|
||||||
|
|
||||||
func TestControllerEndRequestAwareness(t *testing.T) {
|
func TestControllerEndRequestAwareness(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
|
NewEngine().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
usernames := []string{
|
usernames := []string{
|
||||||
|
@ -284,8 +284,8 @@ func TestControllerBind(t *testing.T) {
|
||||||
myTitlePtr := &testBindType{title: t1}
|
myTitlePtr := &testBindType{title: t1}
|
||||||
// test bind value to value of the correct type
|
// test bind value to value of the correct type
|
||||||
myTitleV := testBindType{title: t2}
|
myTitleV := testBindType{title: t2}
|
||||||
m := New()
|
m := NewEngine()
|
||||||
m.Bind(myTitlePtr, myTitleV)
|
m.Dependencies.Add(myTitlePtr, myTitleV)
|
||||||
// or just app
|
// or just app
|
||||||
m.Controller(app.Party("/"), new(testControllerBindStruct))
|
m.Controller(app.Party("/"), new(testControllerBindStruct))
|
||||||
m.Controller(app.Party("/deep"), new(testControllerBindDeep))
|
m.Controller(app.Party("/deep"), new(testControllerBindDeep))
|
||||||
|
@ -345,8 +345,9 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Bind(&testBindType{title: title}).
|
m := NewEngine()
|
||||||
Controller(app.Party("/user/{username}"), new(testCtrl0))
|
m.Dependencies.Add(&testBindType{title: title})
|
||||||
|
m.Controller(app.Party("/user/{username}"), new(testCtrl0))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
e.GET("/user/" + username).Expect().
|
e.GET("/user/" + username).Expect().
|
||||||
|
@ -378,7 +379,7 @@ func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} /
|
||||||
|
|
||||||
func TestControllerRelPathFromFunc(t *testing.T) {
|
func TestControllerRelPathFromFunc(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app, new(testControllerRelPathFromFunc))
|
NewEngine().Controller(app, new(testControllerRelPathFromFunc))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
e.GET("/").Expect().Status(iris.StatusOK).
|
e.GET("/").Expect().Status(iris.StatusOK).
|
||||||
|
@ -420,12 +421,8 @@ type testControllerActivateListener struct {
|
||||||
TitlePointer *testBindType
|
TitlePointer *testBindType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
|
func (c *testControllerActivateListener) BeforeActivate(ca *ControllerActivator) {
|
||||||
if !ca.Dependencies.BindExists(&testBindType{}) {
|
ca.Dependencies.AddOnce(&testBindType{title: "default title"})
|
||||||
ca.Dependencies.Bind(&testBindType{
|
|
||||||
title: "default title",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testControllerActivateListener) Get() string {
|
func (c *testControllerActivateListener) Get() string {
|
||||||
|
@ -434,12 +431,14 @@ func (c *testControllerActivateListener) Get() string {
|
||||||
|
|
||||||
func TestControllerActivateListener(t *testing.T) {
|
func TestControllerActivateListener(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app, new(testControllerActivateListener))
|
NewEngine().Controller(app, new(testControllerActivateListener))
|
||||||
New().Bind(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
|
m := NewEngine()
|
||||||
|
m.Dependencies.Add(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
|
||||||
title: "my title",
|
title: "my title",
|
||||||
}).Controller(app.Party("/manual"), new(testControllerActivateListener))
|
})
|
||||||
|
m.Controller(app.Party("/manual"), new(testControllerActivateListener))
|
||||||
// or
|
// or
|
||||||
New().Controller(app.Party("/manual2"), &testControllerActivateListener{
|
NewEngine().Controller(app.Party("/manual2"), &testControllerActivateListener{
|
||||||
TitlePointer: &testBindType{
|
TitlePointer: &testBindType{
|
||||||
title: "my title",
|
title: "my title",
|
||||||
},
|
},
|
||||||
|
|
92
mvc2/di/di.go
Normal file
92
mvc2/di/di.go
Normal file
|
@ -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...,
|
||||||
|
)
|
||||||
|
}
|
108
mvc2/di/func.go
Normal file
108
mvc2/di/func.go
Normal file
|
@ -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)
|
||||||
|
}
|
97
mvc2/di/object.go
Normal file
97
mvc2/di/object.go
Normal file
|
@ -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)
|
||||||
|
}
|
180
mvc2/di/reflect.go
Normal file
180
mvc2/di/reflect.go
Normal file
|
@ -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
|
||||||
|
}
|
84
mvc2/di/struct.go
Normal file
84
mvc2/di/struct.go
Normal file
|
@ -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
|
||||||
|
}
|
100
mvc2/di/values.go
Normal file
100
mvc2/di/values.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,65 +1,52 @@
|
||||||
package mvc2
|
package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"github.com/kataras/iris/mvc2/di"
|
||||||
|
|
||||||
"github.com/kataras/di"
|
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/router"
|
"github.com/kataras/iris/core/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
errNil = errors.New("nil")
|
|
||||||
errBad = errors.New("bad")
|
|
||||||
errAlreadyExists = errors.New("already exists")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
dependencies *di.D
|
Dependencies *di.D
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Engine {
|
func NewEngine() *Engine {
|
||||||
return &Engine{
|
return &Engine{
|
||||||
dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
|
Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Bind(values ...interface{}) *Engine {
|
func (e *Engine) Clone() *Engine {
|
||||||
e.dependencies.Bind(values...)
|
child := NewEngine()
|
||||||
return e
|
child.Dependencies = e.Dependencies.Clone()
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine) Child() *Engine {
|
|
||||||
child := New()
|
|
||||||
child.dependencies = e.dependencies.Clone()
|
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Handler(handler interface{}) context.Handler {
|
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 {
|
if err != nil {
|
||||||
golog.Errorf("mvc handler: %v", err)
|
golog.Errorf("mvc handler: %v", err)
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) {
|
func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) {
|
||||||
ca := newControllerActivator(router, controller, e.dependencies)
|
ca := newControllerActivator(router, controller, e.Dependencies)
|
||||||
|
|
||||||
// give a priority to the "onActivate"
|
// give a priority to the "beforeActivate"
|
||||||
// callbacks, if any.
|
// callbacks, if any.
|
||||||
for _, cb := range onActivate {
|
for _, cb := range beforeActivate {
|
||||||
cb(ca)
|
cb(ca)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if controller has an "OnActivate" function
|
// check if controller has an "BeforeActivate" function
|
||||||
// which accepts the controller activator and call it.
|
// which accepts the controller activator and call it.
|
||||||
if activateListener, ok := controller.(interface {
|
if activateListener, ok := controller.(interface {
|
||||||
OnActivate(*ControllerActivator)
|
BeforeActivate(*ControllerActivator)
|
||||||
}); ok {
|
}); ok {
|
||||||
activateListener.OnActivate(ca)
|
activateListener.BeforeActivate(ca)
|
||||||
}
|
}
|
||||||
|
|
||||||
ca.activate()
|
ca.activate()
|
||||||
|
|
|
@ -9,7 +9,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMvcEngineInAndHandler(t *testing.T) {
|
func TestMvcEngineInAndHandler(t *testing.T) {
|
||||||
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
m := NewEngine()
|
||||||
|
m.Dependencies.Add(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
h1 = m.Handler(testConsumeUserHandler)
|
h1 = m.Handler(testConsumeUserHandler)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
|
"github.com/kataras/iris/mvc2/di"
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -405,7 +407,7 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view.
|
||||||
setViewData(ctx, m)
|
setViewData(ctx, m)
|
||||||
} else if m, ok := r.Data.(context.Map); ok {
|
} else if m, ok := r.Data.(context.Map); ok {
|
||||||
setViewData(ctx, m)
|
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))
|
setViewData(ctx, structs.Map(r))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) Result
|
||||||
|
|
||||||
func TestControllerMethodResult(t *testing.T) {
|
func TestControllerMethodResult(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app, new(testControllerMethodResult))
|
NewEngine().Controller(app, new(testControllerMethodResult))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCust
|
||||||
|
|
||||||
func TestControllerMethodResultTypes(t *testing.T) {
|
func TestControllerMethodResultTypes(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app, new(testControllerMethodResultTypes))
|
NewEngine().Controller(app, new(testControllerMethodResultTypes))
|
||||||
|
|
||||||
e := httptest.New(t, app, httptest.LogLevel("debug"))
|
e := httptest.New(t, app, httptest.LogLevel("debug"))
|
||||||
|
|
||||||
|
@ -266,8 +266,8 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
|
||||||
|
|
||||||
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
|
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
|
NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
|
||||||
ca.Dependencies.Bind(t)
|
ca.Dependencies.Add(t)
|
||||||
})
|
})
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kataras/di"
|
"github.com/kataras/iris/mvc2/di"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ func isContextHandler(handler interface{}) (context.Handler, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateHandler(handler interface{}) error {
|
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 fmt.Errorf("handler expected to be a kind of func but got typeof(%s)", typ.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
83
mvc2/ideas/1/main.go
Normal file
83
mvc2/ideas/1/main.go
Normal file
|
@ -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)
|
||||||
|
}
|
90
mvc2/mvc.go
Normal file
90
mvc2/mvc.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -7,7 +7,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPathParamsBinder(t *testing.T) {
|
func TestPathParamsBinder(t *testing.T) {
|
||||||
m := New().Bind(PathParamsBinder)
|
m := NewEngine()
|
||||||
|
m.Dependencies.Add(PathParamsBinder)
|
||||||
|
|
||||||
got := ""
|
got := ""
|
||||||
|
|
||||||
|
@ -25,7 +26,8 @@ func TestPathParamsBinder(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestPathParamBinder(t *testing.T) {
|
func TestPathParamBinder(t *testing.T) {
|
||||||
m := New().Bind(PathParamBinder("username"))
|
m := NewEngine()
|
||||||
|
m.Dependencies.Add(PathParamBinder("username"))
|
||||||
|
|
||||||
got := ""
|
got := ""
|
||||||
executed := false
|
executed := false
|
||||||
|
|
123
mvc2/reflect.go
123
mvc2/reflect.go
|
@ -2,10 +2,8 @@ package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/pkg/zerocheck"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
|
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
|
||||||
|
@ -20,58 +18,6 @@ func isContext(inTyp reflect.Type) bool {
|
||||||
return inTyp.Implements(contextTyp)
|
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 {
|
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
|
||||||
n := funcTyp.NumIn()
|
n := funcTyp.NumIn()
|
||||||
funcIn := make([]reflect.Type, n, n)
|
funcIn := make([]reflect.Type, n, n)
|
||||||
|
@ -80,72 +26,3 @@ func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
|
||||||
}
|
}
|
||||||
return funcIn
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,12 +17,12 @@ type SessionController struct {
|
||||||
Session *sessions.Session
|
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.
|
// every single time the dev registers a specific SessionController-based controller.
|
||||||
// It makes sure that its "Manager" field is filled
|
// It makes sure that its "Manager" field is filled
|
||||||
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
||||||
func (s *SessionController) OnActivate(ca *ControllerActivator) {
|
func (s *SessionController) BeforeActivate(ca *ControllerActivator) {
|
||||||
if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually {
|
if didntBindManually := ca.Dependencies.AddOnce(defaultSessionManager); didntBindManually {
|
||||||
ca.Router.GetReporter().Add(
|
ca.Router.GetReporter().Add(
|
||||||
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
||||||
therefore this controller is using the default sessions manager instead.
|
therefore this controller is using the default sessions manager instead.
|
||||||
|
|
|
@ -167,6 +167,26 @@ func (s *Session) GetIntDefault(key string, defaultValue int) (int, error) {
|
||||||
return defaultValue, errFindParse.Format("int", key, v)
|
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,
|
// GetInt64 same as `Get` but returns its int64 representation,
|
||||||
// if key doesn't exist then it returns -1.
|
// if key doesn't exist then it returns -1.
|
||||||
func (s *Session) GetInt64(key string) (int64, error) {
|
func (s *Session) GetInt64(key string) (int64, error) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user