diff --git a/_examples/README.md b/_examples/README.md index f988d46d..2252441e 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -191,8 +191,8 @@ Optional `EndRequest(ctx)` function to perform any finalization after any method Inheritance, recursively, see for example our `mvc.SessionController`, it has the `Session *sessions.Session` and `Manager *sessions.Sessions` as embedded fields which are filled by its `BeginRequest`, [here](https://github.com/kataras/iris/blob/master/mvc/session_controller.go). -This is just an example, you could use the `mvc.Session(mySessions)` as a dependency to the MVC Application, i.e -`mvcApp.AddDependencies(mvc.Session(sessions.New(sessions.Config{Cookie: "iris_session_id"})))`. +This is just an example, you could use the `sessions.Session` which returned from the manager's `Start` as a dynamic dependency to the MVC Application, i.e +`mvcApp.AddDependencies(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start)`. Access to the dynamic path parameters via the controller's methods' input arguments, no binding is needed. When you use the Iris' default syntax to parse handlers from a controller, you need to suffix the methods diff --git a/_examples/mvc/README.md b/_examples/mvc/README.md index e898b22e..c5f970c5 100644 --- a/_examples/mvc/README.md +++ b/_examples/mvc/README.md @@ -78,8 +78,8 @@ Optional `EndRequest(ctx)` function to perform any finalization after any method Inheritance, recursively, see for example our `mvc.SessionController`, it has the `Session *sessions.Session` and `Manager *sessions.Sessions` as embedded fields which are filled by its `BeginRequest`, [here](https://github.com/kataras/iris/blob/master/mvc/session_controller.go). -This is just an example, you could use the `mvc.Session(mySessions)` as a dependency to the MVC Application, i.e -`mvcApp.AddDependencies(mvc.Session(sessions.New(sessions.Config{Cookie: "iris_session_id"})))`. +This is just an example, you could use the `sessions.Session` as a dependency to the MVC Application, i.e +`mvcApp.AddDependencies(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start)`. Access to the dynamic path parameters via the controller's methods' input arguments, no binding is needed. When you use the Iris' default syntax to parse handlers from a controller, you need to suffix the methods diff --git a/_examples/mvc/login/main.go b/_examples/mvc/login/main.go index 105f9610..6f599996 100644 --- a/_examples/mvc/login/main.go +++ b/_examples/mvc/login/main.go @@ -64,7 +64,7 @@ func main() { user := mvc.New(app.Party("/user")) user.AddDependencies( userService, - mvc.Session(sessManager), + sessManager.Start, ) user.Register(new(controllers.UserController)) diff --git a/_examples/mvc/session-controller/main.go b/_examples/mvc/session-controller/main.go index cdfb8db6..5cf4805a 100644 --- a/_examples/mvc/session-controller/main.go +++ b/_examples/mvc/session-controller/main.go @@ -52,10 +52,7 @@ func newApp() *iris.Application { // If dependencies are registered without field or function's input arguments as // consumers then those dependencies are being ignored before the server ran, // so you can bind many dependecies and use them in different controllers. - // func(ctx iris.Context) *sessions.Session { - // return sess.Start(ctx) - // }, -> same as mvc.Session(sess): - mvc.Session(sess), + sess.Start, time.Now(), ) visitApp.Register(new(VisitController)) diff --git a/_examples/structuring/login-mvc-single-responsibility-package/main.go b/_examples/structuring/login-mvc-single-responsibility-package/main.go index 60b4d36b..2828e81c 100644 --- a/_examples/structuring/login-mvc-single-responsibility-package/main.go +++ b/_examples/structuring/login-mvc-single-responsibility-package/main.go @@ -39,7 +39,7 @@ func configureMVC(app *mvc.Application) { userApp := app.NewChild(app.Router.Party("/user")) userApp.AddDependencies( user.NewDataSource(), - mvc.Session(manager), + manager.Start, ) userApp.Register(new(user.Controller)) } diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go index d125440c..262c7f97 100644 --- a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go +++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go @@ -43,7 +43,7 @@ func main() { // any dependencies bindings here... todosApp.AddDependencies( todo.NewMemoryService(), - mvc.Session(sess), + sess.Start, ws.Upgrade, ) diff --git a/doc.go b/doc.go index 62a699b5..35aa2e6a 100644 --- a/doc.go +++ b/doc.go @@ -1663,9 +1663,9 @@ useful to call middlewares or when many methods use the same collection of data. Optional `EndRequest(ctx)` function to perform any finalization after any method executed. -Session dependency via `mvc.Session(mySessions)` to the MVC Application, i.e +Session dynamic dependency via manager's `Start` to the MVC Application, i.e - mvcApp.AddDependencies(mvc.Session(sessions.New(sessions.Config{Cookie: "iris_session_id"}))) + mvcApp.AddDependencies(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start) Inheritance, recursively. diff --git a/hero/session.go b/hero/session.go index 49887104..8d008653 100644 --- a/hero/session.go +++ b/hero/session.go @@ -1,12 +1,10 @@ package hero -import ( - "github.com/kataras/iris/context" - "github.com/kataras/iris/sessions" -) +// It's so easy, no need to be lived anywhere as built'n.. users should understand +// how easy it's by using it. -// Session is a binder that will fill a *sessions.Session function input argument -// or a Controller struct's field. -func Session(sess *sessions.Sessions) func(context.Context) *sessions.Session { - return sess.Start -} +// // Session is a binder that will fill a *sessions.Session function input argument +// // or a Controller struct's field. +// func Session(sess *sessions.Sessions) func(context.Context) *sessions.Session { +// return sess.Start +// } diff --git a/mvc/README.md b/mvc/README.md deleted file mode 100644 index 06001bef..00000000 --- a/mvc/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# MVC Internals - -* `MakeHandler` - accepts a function which accepts any input and outputs any result, and any optional values that will be used as binders, if needed they will be converted in order to be faster at serve-time. Returns a `context/iris#Handler` and a non-nil error if passed function cannot be wrapped to a raw `context/iris#Handler` - * Struct fields with `Struct Binding` - * Methods with `Dynamic Binding` -* `Engine` - The "manager" of the controllers and handlers, can be grouped and an `Engine` can have any number of children. - * `Engine#Bind` Binds values to be used inside on one or more handlers and controllers - * `Engine#Handler` - Creates and returns a new mvc handler, which accept any input parameters (calculated by the binders) and output any result which will be sent as a response to the HTTP Client. Calls the `MakeHandler` with the Engine's `Dependencies.Values` as the binders - * `Engine#Controller` - Creates and activates a controller based on a struct which has the `C` as an embedded , anonymous, field and defines methods to be used as routes. Can accept any optional activator listeners in order to bind any custom routes or change the bindings, called once at startup. -* The highest level feature of this package is the `Application` which contains -an `iris.Party` as its Router and an `Engine`. A new `Application` is created with `New(iris.Party)` and registers a new `Engine` for itself, `Engines` can be shared via the `Application#NewChild` or by manually creating an `&Application{ Engine: engine, Router: subRouter }`. The `Application` is being used to build complete `mvc applications through controllers`, it doesn't contain any method to convert mvc handlers to raw handlers, although its `Engine` can do that but it's not a common scenario. - -Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/mvc. - -## Binding - -First of all, they can be binded to `func input arguments` (custom handlers) or `struct fields` (controllers). We will use the term `Input` for both of them. - -```go -// consume the user here as func input argument. -func myHandler(user User) {} - -type myController struct { - // consume the user here, as struct field. - user User -} -``` - -If the input is an interface then it will check if the binding is completed this interface -and it will be binded as expected. - -Two types of binders are supported: - -### Dynamic Binding - -`ReturnValue`, should return a single value, no pointer to, if the consumer Input (`struct field` or `func input argument`) expects `User` then it will be binded on each request, this is a dynamic binding based on the `Context`. - -```go -type User struct { - Username string -} - -myBinder := func(ctx iris.Context) User { - return User { - Username: ctx.Params().Get("username"), - } -} - -myHandler := func(user User) { - // ... -} -``` - -### Static Binding - -`Static Value (Service)`, this is used to bind a value instance, like a service or a database connection. - -```go -// optional but we declare interface most of the times to -// be easier to switch from production to testing or local and visa versa. -// If input is an interface then it will check if the binding is completed this interface -// and it will be binded as expected. -type Service interface { - Do() string -} - -type myProductionService struct { - text string -} -func (s *myProductionService) Do() string { - return s.text -} - -myService := &myProductionService{text: "something"} - -myHandler := func(service Service) { - // ... -} -``` - -### Add Dependencies - -#### For Handlers - -MakeHandler is used to create a handler based on a function which can accept any input arguments and export any output arguments, the input arguments can be dynamic path parameters or custom [binders](#binding). - -```go -h, err := MakeHandler(myHandler, reflect.ValueOf(myBinder)) -``` - -Values passed in `Dependencies` are binded to all handlers and controllers that are expected a type of the returned value, in this case the myBinder indicates a dynamic/serve-time function which returns a User, as shown above. - -```go -m := NewEngine() -m.Dependencies.Add(myBinder) - -h := m.Handler(myHandler) -``` - -#### For Controllers - -```go -app := iris.New() -m := NewEngine() -m.Dependencies.Add(myBinder) -m.Controller(app, new(myController)) -// ... -``` - -```go -sub := app.Party("/sub") -m := NewEngine() -m.Controller(sub, &myController{service: myService}) -``` - -```go -NewEngine().Controller(sub.Party("/subsub"), new(myController), func(b mvc.BeforeActivation) { - b.Dependencies().Add(myService) -}) -``` \ No newline at end of file diff --git a/mvc/controller.go b/mvc/controller.go index 1c44b97b..2c0609e2 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -8,7 +8,8 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/mvc/di" + "github.com/kataras/iris/hero" + "github.com/kataras/iris/hero/di" "github.com/kataras/golog" ) @@ -306,30 +307,30 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref // if c.injector is nil, then set it with the current bindings, // these bindings can change after, so first add dependencies and after register routes. if c.injector == nil { - c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...) - if c.injector.HasFields { + c.injector = di.Struct(c.Value, c.dependencies...) + if c.injector.Has { golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String()) } } // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) - funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...) + funcInjector := di.Func(m.Func, funcDependencies...) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) - if funcInjector.Valid { + if funcInjector.Has { golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, m.Name, funcInjector.String()) } var ( implementsBase = isBaseController(c.Type) hasBindableFields = c.injector.CanInject - hasBindableFuncInputs = funcInjector.Valid + hasBindableFuncInputs = funcInjector.Has call = m.Func.Call ) if !implementsBase && !hasBindableFields && !hasBindableFuncInputs { return func(ctx context.Context) { - DispatchFuncResult(ctx, call(c.injector.AcquireSlice())) + hero.DispatchFuncResult(ctx, call(c.injector.AcquireSlice())) } } @@ -371,11 +372,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref in := make([]reflect.Value, n, n) in[0] = ctrl funcInjector.Inject(&in, ctxValue) - DispatchFuncResult(ctx, call(in)) + hero.DispatchFuncResult(ctx, call(in)) return } - DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) + hero.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) } } diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index a373f18c..a5d1123a 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -10,6 +10,23 @@ import ( . "github.com/kataras/iris/mvc" ) +// service +type ( + // these TestService and TestServiceImpl could be in lowercase, unexported + // but the `Say` method should be exported however we have those exported + // because of the controller handler test. + TestService interface { + Say(string) string + } + TestServiceImpl struct { + prefix string + } +) + +func (s *TestServiceImpl) Say(message string) string { + return s.prefix + " " + message +} + type testControllerHandle struct { Ctx context.Context Service TestService @@ -53,9 +70,9 @@ func (c *testControllerHandle) HiParamEmptyInputBy() string { func TestControllerHandle(t *testing.T) { app := iris.New() - m := NewEngine() - m.Dependencies.Add(&TestServiceImpl{prefix: "service:"}) - m.Controller(app, new(testControllerHandle)) + m := New(app) + m.AddDependencies(&TestServiceImpl{prefix: "service:"}) + m.Register(new(testControllerHandle)) e := httptest.New(t, app) diff --git a/mvc/controller_method_result_test.go b/mvc/controller_method_result_test.go index f7cc4c0b..966d5655 100644 --- a/mvc/controller_method_result_test.go +++ b/mvc/controller_method_result_test.go @@ -69,7 +69,7 @@ func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) Result func TestControllerMethodResult(t *testing.T) { app := iris.New() - NewEngine().Controller(app, new(testControllerMethodResult)) + New(app).Register(new(testControllerMethodResult)) e := httptest.New(t, app) @@ -173,7 +173,7 @@ func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCust func TestControllerMethodResultTypes(t *testing.T) { app := iris.New() - NewEngine().Controller(app, new(testControllerMethodResultTypes)) + New(app).Register(new(testControllerMethodResultTypes)) e := httptest.New(t, app) @@ -261,9 +261,9 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result { func TestControllerViewResultRespectCtxViewData(t *testing.T) { app := iris.New() - m := NewEngine() - m.Dependencies.Add(t) - m.Controller(app.Party("/"), new(testControllerViewResultRespectCtxViewData)) + m := New(app.Party("/")) + m.AddDependencies(t) + m.Register(new(testControllerViewResultRespectCtxViewData)) e := httptest.New(t, app) diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 1aa218a1..307e5ac0 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -64,10 +64,9 @@ func (c *testControllerAny) Any() { func TestControllerMethodFuncs(t *testing.T) { app := iris.New() - m := NewEngine() - m.Controller(app, new(testController)) - m.Controller(app.Party("/all"), new(testControllerAll)) - m.Controller(app.Party("/any"), new(testControllerAny)) + New(app).Register(new(testController)) + New(app.Party("/all")).Register(new(testControllerAll)) + New(app.Party("/any")).Register(new(testControllerAny)) e := httptest.New(t, app) for _, method := range router.AllMethods { @@ -112,7 +111,8 @@ func (c *testControllerBeginAndEndRequestFunc) Post() { func TestControllerBeginAndEndRequestFunc(t *testing.T) { app := iris.New() - NewEngine().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc)) + New(app.Party("/profile/{username}")). + Register(new(testControllerBeginAndEndRequestFunc)) e := httptest.New(t, app) usernames := []string{ @@ -155,8 +155,10 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) { ctx.Writef("forbidden") } - NewEngine().Controller(app.Party("/profile/{username}", middlewareCheck), - new(testControllerBeginAndEndRequestFunc)) + app.PartyFunc("/profile/{username}", func(r iris.Party) { + r.Use(middlewareCheck) + New(r).Register(new(testControllerBeginAndEndRequestFunc)) + }) e := httptest.New(t, app) @@ -229,7 +231,7 @@ func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) { func TestControllerEndRequestAwareness(t *testing.T) { app := iris.New() - NewEngine().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness)) + New(app.Party("/era/{username}")).Register(new(testControllerEndRequestAwareness)) e := httptest.New(t, app) usernames := []string{ @@ -284,11 +286,10 @@ func TestControllerDependencies(t *testing.T) { myTitlePtr := &testBindType{title: t1} // test bind value to value of the correct type myTitleV := testBindType{title: t2} - m := NewEngine() - m.Dependencies.Add(myTitlePtr, myTitleV) - // or just app - m.Controller(app.Party("/"), new(testControllerBindStruct)) - m.Controller(app.Party("/deep"), new(testControllerBindDeep)) + m := New(app) + m.AddDependencies(myTitlePtr, myTitleV) + m.Register(new(testControllerBindStruct)) + m.NewChild(app.Party("/deep")).Register(new(testControllerBindDeep)) e := httptest.New(t, app) expected := t1 + t2 @@ -347,9 +348,9 @@ func TestControllerInsideControllerRecursively(t *testing.T) { ) app := iris.New() - m := NewEngine() - m.Dependencies.Add(&testBindType{title: title}) - m.Controller(app.Party("/user/{username}"), new(testCtrl0)) + m := New(app.Party("/user/{username}")) + m.AddDependencies(&testBindType{title: title}) + m.Register(new(testCtrl0)) e := httptest.New(t, app) e.GET("/user/" + username).Expect(). @@ -381,7 +382,7 @@ func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} / func TestControllerRelPathFromFunc(t *testing.T) { app := iris.New() - NewEngine().Controller(app, new(testControllerRelPathFromFunc)) + New(app).Register(new(testControllerRelPathFromFunc)) e := httptest.New(t, app) e.GET("/").Expect().Status(iris.StatusOK). @@ -431,14 +432,14 @@ func (c *testControllerActivateListener) Get() string { func TestControllerActivateListener(t *testing.T) { app := iris.New() - NewEngine().Controller(app, new(testControllerActivateListener)) - m := NewEngine() - m.Dependencies.Add(&testBindType{ + New(app).Register(new(testControllerActivateListener)) + m := New(app) + m.AddDependencies(&testBindType{ title: "my title", }) - m.Controller(app.Party("/manual"), new(testControllerActivateListener)) + m.NewChild(m.Router.Party("/manual")).Register(new(testControllerActivateListener)) // or - NewEngine().Controller(app.Party("/manual2"), &testControllerActivateListener{ + m.NewChild(m.Router.Party("/manual2")).Register(&testControllerActivateListener{ TitlePointer: &testBindType{ title: "my title", }, @@ -481,7 +482,7 @@ func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string { func TestControllerNotCreateNewDueManuallySettingAllFields(t *testing.T) { app := iris.New() - NewEngine().Controller(app, &testControllerNotCreateNewDueManuallySettingAllFields{ + New(app).Register(&testControllerNotCreateNewDueManuallySettingAllFields{ T: t, TitlePointer: &testBindType{ title: "my title", diff --git a/mvc/di/TODO.txt b/mvc/di/TODO.txt deleted file mode 100644 index 569cb392..00000000 --- a/mvc/di/TODO.txt +++ /dev/null @@ -1,11 +0,0 @@ -I can do one of the followings to this "di" folder when I finish the cleanup and document it a bit, -although I'm sick I will try to finish it tomorrow. - -End-users don't need this. -1) So, rename this to "internal". - -I don't know if something similar exist in Go, -it's a dependency injection framework at the end, and a very fast one. - -2) So I'm thinking to push it to a different repo, - like https://github.com/kataras/di or even to my small common https://github.com/kataras/pkg collection. \ No newline at end of file diff --git a/mvc/di/di.go b/mvc/di/di.go deleted file mode 100644 index 469be602..00000000 --- a/mvc/di/di.go +++ /dev/null @@ -1,87 +0,0 @@ -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 explanation. -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 explanation. -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 { - return &D{ - Values: d.Values.Clone(), - hijacker: d.hijacker, - goodFunc: d.goodFunc, - } -} - -// 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 &StructInjector{HasFields: false} - } - - return MakeStructInjector( - ValueOf(s), - d.hijacker, - d.goodFunc, - d.Values.CloneWithFieldsOf(s)..., - ) -} - -// 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 { - if fn == nil { - return &FuncInjector{Valid: false} - } - - return MakeFuncInjector( - ValueOf(fn), - d.hijacker, - d.goodFunc, - d.Values..., - ) -} diff --git a/mvc/di/func.go b/mvc/di/func.go deleted file mode 100644 index e49a55c0..00000000 --- a/mvc/di/func.go +++ /dev/null @@ -1,141 +0,0 @@ -package di - -import ( - "fmt" - "reflect" -) - -type ( - targetFuncInput struct { - Object *BindObject - InputIndex int - } - - // FuncInjector keeps the data that are needed in order to do the binding injection - // as fast as possible and with the best possible and safest way. - FuncInjector struct { - // the original function, is being used - // only the .Call, which is referring 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 - - trace string // for debug info. - } -) - -// MakeFuncInjector returns a new func injector, which will be the object -// that the caller should use to bind input arguments of the "fn" function. -// -// The hijack and the goodFunc are optional, the "values" is the dependencies values. -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) { - // println(inTyp.String() + " is assignable to " + val.Type().String()) - // 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 = len(s.inputs) - s.Valid = s.Length > 0 - - for i, in := range s.inputs { - bindmethodTyp := bindTypeString(in.Object.BindType) - typIn := typ.In(in.InputIndex) - // remember: on methods that are part of a struct (i.e controller) - // the input index = 1 is the begggining instead of the 0, - // because the 0 is the controller receiver pointer of the method. - s.trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n", i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String()) - } - - return s -} - -// String returns a debug trace text. -func (s *FuncInjector) String() string { - return s.trace -} - -// Inject accepts an already created slice of input arguments -// and fills them, the "ctx" is optional and it's used -// on the dependencies that depends on one or more input arguments, these are the "ctx". -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 -} - -// Call calls the "Inject" with a new slice of input arguments -// that are computed by the length of the input argument from the MakeFuncInjector's "fn" function. -// -// If the function needs a receiver, so -// the caller should be able to in[0] = receiver before injection, -// then the `Inject` method should be used instead. -func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value { - in := make([]reflect.Value, s.Length, s.Length) - s.Inject(&in, ctx...) - return s.fn.Call(in) -} diff --git a/mvc/di/object.go b/mvc/di/object.go deleted file mode 100644 index 392abcc5..00000000 --- a/mvc/di/object.go +++ /dev/null @@ -1,123 +0,0 @@ -package di - -import ( - "errors" - "reflect" -) - -// BindType is the type of a binded object/value, it's being used to -// check if the value is accessible after a function call with a "ctx" when needed ( Dynamic type) -// or it's just a struct value (a service | Static type). -type BindType uint32 - -const ( - // Static is the simple assignable value, a static value. - Static BindType = iota - // Dynamic returns a value but it depends on some input arguments from the caller, - // on serve time. - Dynamic -) - -func bindTypeString(typ BindType) string { - switch typ { - case Dynamic: - return "Dynamic" - default: - return "Static" - } -} - -// BindObject contains the dependency value's read-only information. -// FuncInjector and StructInjector keeps information about their -// input arguments/or fields, these properties contain a `BindObject` inside them. -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 -} - -// MakeBindObject accepts any "v" value, struct, pointer or a function -// and a type checker that is used to check if the fields (if "v.elem()" is struct) -// or the input arguments (if "v.elem()" is func) -// are valid to be included as the final object's dependencies, even if the caller added more -// the "di" is smart enough to select what each "v" needs and what not before serve time. -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 -} - -// IsAssignable checks if "to" type can be used as "b.Value/ReturnValue". -func (b *BindObject) IsAssignable(to reflect.Type) bool { - return equalTypes(b.Type, to) -} - -// Assign sets the values to a setter, "toSetter" contains the setter, so the caller -// can use it for multiple and different structs/functions as well. -func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) { - if b.BindType == Dynamic { - toSetter(b.ReturnValue(ctx)) - return - } - toSetter(b.Value) -} diff --git a/mvc/di/reflect.go b/mvc/di/reflect.go deleted file mode 100644 index eb4644b8..00000000 --- a/mvc/di/reflect.go +++ /dev/null @@ -1,200 +0,0 @@ -package di - -import "reflect" - -var emptyIn = []reflect.Value{} - -// IsZero returns true if a value is nil. -// Remember; fields to be checked should be exported otherwise it returns false. -// Notes for users: -// Boolean's zero value is false, even if not set-ed. -// UintXX are not zero on 0 because they are pointers to. -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 ValuesOf(valuesAsInterface []interface{}) (values []reflect.Value) { - for _, v := range valuesAsInterface { - values = append(values, ValueOf(v)) - } - return -} - -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 - Name string // the actual name. - Index []int // the index of the field, slice if it's part of a embedded struct - CanSet bool // is true if it's exported. - - // 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 -} - -// NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported, -// it will check for its exported fields. -func NumFields(elemTyp reflect.Type, skipUnexported bool) int { - return len(lookupFields(elemTyp, skipUnexported, nil)) -} - -func lookupFields(elemTyp reflect.Type, skipUnexported bool, 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, skipUnexported, append(parentIndex, i))...) - continue - } - - // skip unexported fields here, - // after the check for embedded structs, these can be binded if their - // fields are exported. - isExported := f.PkgPath == "" - if skipUnexported && !isExported { - continue - } - - index := []int{i} - if len(parentIndex) > 0 { - index = append(parentIndex, i) - } - - field := field{ - Type: f.Type, - Name: f.Name, - Index: index, - CanSet: isExported, - } - - 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, skipUnexported bool) (bindValues []reflect.Value) { - elem := IndirectValue(v) - fields := lookupFields(IndirectType(v.Type()), skipUnexported, nil) - - for _, f := range fields { - if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/ - !IsZero(fieldVal) { - bindValues = append(bindValues, fieldVal) - } - } - - return -} diff --git a/mvc/di/struct.go b/mvc/di/struct.go deleted file mode 100644 index df20d7c8..00000000 --- a/mvc/di/struct.go +++ /dev/null @@ -1,201 +0,0 @@ -package di - -import ( - "fmt" - "reflect" -) - -type Scope uint8 - -const ( - Stateless Scope = iota - Singleton -) - -type ( - targetStructField struct { - Object *BindObject - FieldIndex []int - } - - // StructInjector keeps the data that are needed in order to do the binding injection - // as fast as possible and with the best possible and safest way. - StructInjector struct { - initRef reflect.Value - initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection. - elemType reflect.Type - // - fields []*targetStructField - // is true when contains bindable fields and it's a valid target struct, - // it maybe 0 but struct may contain unexported fields or exported but no bindable (Stateless) - // see `setState`. - HasFields bool - CanInject bool // if any bindable fields when the state is NOT singleton. - Scope Scope - } -) - -func (s *StructInjector) countBindType(typ BindType) (n int) { - for _, f := range s.fields { - if f.Object.BindType == typ { - n++ - } - } - return -} - -// MakeStructInjector returns a new struct injector, which will be the object -// that the caller should use to bind exported fields or -// embedded unexported fields that contain exported fields -// of the "v" struct value or pointer. -// -// The hijack and the goodFunc are optional, the "values" is the dependencies values. -func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector { - s := &StructInjector{ - initRef: v, - initRefAsSlice: []reflect.Value{v}, - elemType: IndirectType(v.Type()), - } - - fields := lookupFields(s.elemType, true, 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.HasFields = len(s.fields) > 0 - // set the overall state of this injector. - s.fillStruct() - s.setState() - - return s -} - -// set the state, once. -// Here the "initRef" have already the static bindings and the manually-filled fields. -func (s *StructInjector) setState() { - // note for zero length of struct's fields: - // if struct doesn't contain any field - // so both of the below variables will be 0, - // so it's a singleton. - // At the other hand the `s.HasFields` maybe false - // but the struct may contain UNEXPORTED fields or non-bindable fields (request-scoped on both cases) - // so a new controller/struct at the caller side should be initialized on each request, - // we should not depend on the `HasFields` for singleton or no, this is the reason I - // added the `.State` now. - - staticBindingsFieldsLength := s.countBindType(Static) - allStructFieldsLength := NumFields(s.elemType, false) - // check if unexported(and exported) fields are set-ed manually or via binding (at this time we have all fields set-ed inside the "initRef") - // i.e &Controller{unexportedField: "my value"} - // or dependencies values = "my value" and Controller struct {Field string} - // if so then set the temp staticBindingsFieldsLength to that number, so for example: - // if static binding length is 0 - // but an unexported field is set-ed then act that as singleton. - if allStructFieldsLength > staticBindingsFieldsLength { - structFieldsUnexportedNonZero := LookupNonZeroFieldsValues(s.initRef, false) - staticBindingsFieldsLength = len(structFieldsUnexportedNonZero) - } - - // println("staticBindingsFieldsLength: ", staticBindingsFieldsLength) - // println("allStructFieldsLength: ", allStructFieldsLength) - - // if the number of static values binded is equal to the - // total struct's fields(including unexported fields this time) then set as singleton. - if staticBindingsFieldsLength == allStructFieldsLength { - s.Scope = Singleton - // the default is `Stateless`, which means that a new instance should be created - // on each inject action by the caller. - return - } - - s.CanInject = s.Scope == Stateless && s.HasFields -} - -// fill the static bindings values once. -func (s *StructInjector) fillStruct() { - if !s.HasFields { - return - } - // if field is Static then set it to the value that passed by the caller, - // so will have the static bindings already and we can just use that value instead - // of creating new instance. - destElem := IndirectValue(s.initRef) - for _, f := range s.fields { - // if field is Static then set it to the value that passed by the caller, - // so will have the static bindings already and we can just use that value instead - // of creating new instance. - if f.Object.BindType == Static { - destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value) - } - } -} - -// String returns a debug trace message. -func (s *StructInjector) String() (trace string) { - for i, f := range s.fields { - elemField := s.elemType.FieldByIndex(f.FieldIndex) - trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n", - i+1, bindTypeString(f.Object.BindType), f.Object.Type.String(), - elemField.Name, elemField.Type.String()) - } - - return -} - -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) { - destElem.FieldByIndex(f.FieldIndex).Set(v) - }) - } -} - -func (s *StructInjector) Acquire() reflect.Value { - if s.Scope == Singleton { - return s.initRef - } - return reflect.New(s.elemType) -} - -func (s *StructInjector) AcquireSlice() []reflect.Value { - if s.Scope == Singleton { - return s.initRefAsSlice - } - return []reflect.Value{reflect.New(s.elemType)} -} diff --git a/mvc/di/values.go b/mvc/di/values.go deleted file mode 100644 index 1033b957..00000000 --- a/mvc/di/values.go +++ /dev/null @@ -1,126 +0,0 @@ -package di - -import "reflect" - -// Values is a shortcut of []reflect.Value, -// it makes easier to remove and add dependencies. -type Values []reflect.Value - -// NewValues returns new empty (dependencies) values. -func NewValues() Values { - return Values{} -} - -// Clone returns a copy of the current values. -func (bv Values) Clone() Values { - if n := len(bv); n > 0 { - values := make(Values, n, n) - copy(values, bv) - return values - } - - return NewValues() -} - -// CloneWithFieldsOf will return a copy of the current values -// plus the "s" struct's fields that are filled(non-zero) by the caller. -func (bv Values) CloneWithFieldsOf(s interface{}) Values { - values := bv.Clone() - - // add the manual filled fields to the dependencies. - filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true) - values = append(values, filledFieldValues...) - return values -} - -// Len returns the length of the current "bv" values slice. -func (bv Values) Len() int { - return len(bv) -} - -// Add adds values as dependencies, if the struct's fields -// or the function's input arguments needs them, they will be defined as -// bindings (at build-time) and they will be used (at serve-time). -func (bv *Values) Add(values ...interface{}) { - bv.AddValues(ValuesOf(values)...) -} - -// AddValues same as `Add` but accepts reflect.Value dependencies instead of interface{} -// and appends them to the list if they pass some checks. -func (bv *Values) AddValues(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 { - for _, in := range bv { - 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.Add(v) - return true -} diff --git a/mvc/engine.go b/mvc/engine.go deleted file mode 100644 index e27ffc95..00000000 --- a/mvc/engine.go +++ /dev/null @@ -1,107 +0,0 @@ -package mvc - -import ( - "github.com/kataras/golog" - "github.com/kataras/iris/mvc/di" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router" -) - -// Engine contains the Dependencies which will be binded -// to the controller(s) or handler(s) that can be created -// using the Engine's `Handler` and `Controller` methods. -// -// This is not exported for being used by everyone, use it only when you want -// to share engines between multi mvc.go#Application -// or make custom mvc handlers that can be used on the standard -// iris' APIBuilder. The last one reason is the most useful here, -// although end-devs can use the `MakeHandler` as well. -// -// For a more high-level structure please take a look at the "mvc.go#Application". -type Engine struct { - Dependencies di.Values -} - -// NewEngine returns a new engine, a container for dependencies and a factory -// for handlers and controllers, this is used internally by the `mvc#Application` structure. -// Please take a look at the structure's documentation for more information. -func NewEngine() *Engine { - return &Engine{ - Dependencies: di.NewValues(), - } -} - -// Clone creates and returns a new engine with the parent's(current) Dependencies. -// It copies the current "e" dependencies and returns a new engine. -func (e *Engine) Clone() *Engine { - child := NewEngine() - child.Dependencies = e.Dependencies.Clone() - return child -} - -// Handler accepts a "handler" function which can accept any input arguments that match -// with the Engine's `Dependencies` and any output result; like string, int (string,int), -// custom structs, Result(View | Response) and anything you already know that mvc implementation supports. -// It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application, -// as middleware or as simple route handler or subdomain's handler. -func (e *Engine) Handler(handler interface{}) context.Handler { - h, err := MakeHandler(handler, e.Dependencies.Clone()...) - if err != nil { - golog.Errorf("mvc handler: %v", err) - } - return h -} - -// Controller accepts a sub router and registers any custom struct -// as controller, if struct doesn't have any compatible methods -// neither are registered via `ControllerActivator`'s `Handle` method -// then the controller is not registered at all. -// -// A Controller may have one or more methods -// that are wrapped to a handler and registered as routes before the server ran. -// The controller's method can accept any input argument that are previously binded -// via the dependencies or route's path accepts dynamic path parameters. -// The controller's fields are also bindable via the dependencies, either a -// static value (service) or a function (dynamically) which accepts a context -// and returns a single value (this type is being used to find the relative field or method's input argument). -// -// 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. -// -// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc. -func (e *Engine) Controller(router router.Party, controller interface{}) { - // initialize the controller's activator, nothing too magical so far. - c := newControllerActivator(router, controller, e.Dependencies) - - // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate` - // call, which is simply parses the controller's methods, end-dev can register custom controller's methods - // by using the BeforeActivation's (a ControllerActivation) `.Handle` method. - if before, ok := controller.(interface { - BeforeActivation(BeforeActivation) - }); ok { - before.BeforeActivation(c) - } - - c.activate() - - if after, okAfter := controller.(interface { - AfterActivation(AfterActivation) - }); okAfter { - after.AfterActivation(c) - } -} diff --git a/mvc/engine_handler_test.go b/mvc/engine_handler_test.go deleted file mode 100644 index a2a01e30..00000000 --- a/mvc/engine_handler_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package mvc_test - -// black-box in combination with the handler_test - -import ( - "testing" - - . "github.com/kataras/iris/mvc" -) - -func TestMvcEngineInAndHandler(t *testing.T) { - m := NewEngine() - m.Dependencies.Add(testBinderFuncUserStruct, testBinderService, testBinderFuncParam) - - var ( - h1 = m.Handler(testConsumeUserHandler) - h2 = m.Handler(testConsumeServiceHandler) - h3 = m.Handler(testConsumeParamHandler) - ) - - testAppWithMvcHandlers(t, h1, h2, h3) -} diff --git a/mvc/func_result.go b/mvc/func_result.go deleted file mode 100644 index 8875f078..00000000 --- a/mvc/func_result.go +++ /dev/null @@ -1,474 +0,0 @@ -package mvc - -import ( - "reflect" - "strings" - - "github.com/fatih/structs" - "github.com/kataras/iris/mvc/di" - - "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. -// -// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview. -type Result interface { - // Dispatch should sends the response to the context's response writer. - Dispatch(ctx context.Context) -} - -var defaultFailureResponse = Response{Code: 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 -} - -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) { - if len(values) == 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() || !v.CanInterface() { - // continue - // } - 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 - } - - } - - */ - switch value := f.(type) { - case bool: - found = value - if !found { - // skip everything, skip other values, we don't care about other return values, - // this boolean is the higher in order. - break - } - case int: - statusCode = value - case string: - // 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(value, slashB) > 0 { - contentType = value - } else { - // otherwise is content - content = []byte(value) - } - - case []byte: - // it's raw content, get the latest - content = value - case compatibleErr: - if value != nil { // it's always not nil but keep it here. - err = value - 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. - } - default: - // 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) -} - -// 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 _ 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) - } - - DispatchCommon(ctx, r.Code, r.ContentType, r.Content, r.Object, r.Err, true) -} - -// 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 _ 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 = 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 set-ed before (the most common scenario) then do a - // simple 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 modify 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 di.IndirectValue(reflect.ValueOf(r.Data)).Kind() == reflect.Struct { - setViewData(ctx, structs.Map(r)) - } - } - } - - ctx.View(r.Name) - } -} - -func setViewData(ctx context.Context, data map[string]interface{}) { - for k, v := range data { - ctx.ViewData(k, v) - } -} diff --git a/mvc/go19.go b/mvc/go19.go new file mode 100644 index 00000000..ab834727 --- /dev/null +++ b/mvc/go19.go @@ -0,0 +1,21 @@ +// +build go1.9 + +package mvc + +import "github.com/kataras/iris/hero" + +type ( + + // Result is a type alias for the `hero#Result`, useful for output controller's methods. + Result = hero.Result + // Response is a type alias for the `hero#Response`, useful for output controller's methods. + Response = hero.Response + // View is a type alias for the `hero#View`, useful for output controller's methods. + View = hero.View +) + +var ( + // Try is a type alias for the `hero#Try`, + // useful to return a result based on two cases: failure(including panics) and a succeess. + Try = hero.Try +) diff --git a/mvc/handler.go b/mvc/handler.go deleted file mode 100644 index 81819143..00000000 --- a/mvc/handler.go +++ /dev/null @@ -1,89 +0,0 @@ -package mvc - -import ( - "fmt" - "github.com/kataras/iris/mvc/di" - "reflect" - "runtime" - - "github.com/kataras/golog" - "github.com/kataras/iris/context" -) - -// checks if "handler" is context.Handler; func(context.Context). -func isContextHandler(handler interface{}) (context.Handler, bool) { - h, is := handler.(context.Handler) - if !is { - fh, is := handler.(func(context.Context)) - if is { - return fh, is - } - } - return h, is -} - -func validateHandler(handler interface{}) error { - if typ := reflect.TypeOf(handler); !di.IsFunc(typ) { - return fmt.Errorf("handler expected to be a kind of func but got typeof(%s)", typ.String()) - } - return nil -} - -// MustMakeHandler calls the `MakeHandler` and panics on any error. -func MustMakeHandler(handler interface{}, bindValues ...reflect.Value) context.Handler { - h, err := MakeHandler(handler, bindValues...) - if err != nil { - panic(err) - } - - return h -} - -// MakeHandler accepts a "handler" function which can accept any input arguments that match -// with the "bindValues" types and any output result, that matches the mvc types, like string, int (string,int), -// custom structs, Result(View | Response) and anything that you already know that mvc implementation supports, -// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application, -// as middleware or as simple route handler or party handler or subdomain handler-router. -func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Handler, error) { - if err := validateHandler(handler); err != nil { - return nil, err - } - - if h, is := isContextHandler(handler); is { - golog.Warnf("mvc handler: you could just use the low-level API to register a context handler instead") - return h, nil - } - - fn := reflect.ValueOf(handler) - n := fn.Type().NumIn() - - if n == 0 { - h := func(ctx context.Context) { - DispatchFuncResult(ctx, fn.Call(emptyIn)) - } - - return h, nil - } - - funcInjector := di.MakeFuncInjector(fn, hijacker, typeChecker, bindValues...) - if !funcInjector.Valid { - pc := fn.Pointer() - fpc := runtime.FuncForPC(pc) - callerFileName, callerLineNumber := fpc.FileLine(pc) - callerName := fpc.Name() - - err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s", - n, funcInjector.Length, fn.Type().String(), callerFileName, callerLineNumber, callerName) - return nil, err - } - - h := func(ctx context.Context) { - // in := make([]reflect.Value, n, n) - // funcInjector.Inject(&in, reflect.ValueOf(ctx)) - // DispatchFuncResult(ctx, fn.Call(in)) - DispatchFuncResult(ctx, funcInjector.Call(reflect.ValueOf(ctx))) - } - - return h, nil - -} diff --git a/mvc/handler_test.go b/mvc/handler_test.go deleted file mode 100644 index fb22ea24..00000000 --- a/mvc/handler_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package mvc_test - -// black-box - -import ( - "fmt" - "reflect" - "testing" - - "github.com/kataras/iris" - "github.com/kataras/iris/httptest" - - . "github.com/kataras/iris/mvc" -) - -// dynamic func -type testUserStruct struct { - ID int64 - Username string -} - -func testBinderFunc(ctx iris.Context) testUserStruct { - id, _ := ctx.Params().GetInt64("id") - username := ctx.Params().Get("username") - return testUserStruct{ - ID: id, - Username: username, - } -} - -// service -type ( - // these TestService and TestServiceImpl could be in lowercase, unexported - // but the `Say` method should be exported however we have those exported - // because of the controller handler test. - TestService interface { - Say(string) string - } - TestServiceImpl struct { - prefix string - } -) - -func (s *TestServiceImpl) Say(message string) string { - return s.prefix + " " + message -} - -var ( - // binders, as user-defined - testBinderFuncUserStruct = testBinderFunc - testBinderService = &TestServiceImpl{prefix: "say"} - testBinderFuncParam = func(ctx iris.Context) string { - return ctx.Params().Get("param") - } - - // consumers - // a context as first input arg, which is not needed to be binded manually, - // and a user struct which is binded to the input arg by the #1 func(ctx) any binder. - testConsumeUserHandler = func(ctx iris.Context, user testUserStruct) { - ctx.JSON(user) - } - - // just one input arg, the service which is binded by the #2 service binder. - testConsumeServiceHandler = func(service TestService) string { - return service.Say("something") - } - // just one input arg, a standar string which is binded by the #3 func(ctx) any binder. - testConsumeParamHandler = func(myParam string) string { - return "param is: " + myParam - } -) - -func TestMakeHandler(t *testing.T) { - var ( - h1 = MustMakeHandler(testConsumeUserHandler, reflect.ValueOf(testBinderFuncUserStruct)) - h2 = MustMakeHandler(testConsumeServiceHandler, reflect.ValueOf(testBinderService)) - h3 = MustMakeHandler(testConsumeParamHandler, reflect.ValueOf(testBinderFuncParam)) - ) - - testAppWithMvcHandlers(t, h1, h2, h3) -} - -func testAppWithMvcHandlers(t *testing.T, h1, h2, h3 iris.Handler) { - app := iris.New() - app.Get("/{id:long}/{username:string}", h1) - app.Get("/service", h2) - app.Get("/param/{param:string}", h3) - - expectedUser := testUserStruct{ - ID: 42, - Username: "kataras", - } - - e := httptest.New(t, app) - // 1 - e.GET(fmt.Sprintf("/%d/%s", expectedUser.ID, expectedUser.Username)).Expect().Status(httptest.StatusOK). - JSON().Equal(expectedUser) - // 2 - e.GET("/service").Expect().Status(httptest.StatusOK). - Body().Equal("say something") - // 3 - e.GET("/param/the_param_value").Expect().Status(httptest.StatusOK). - Body().Equal("param is: the_param_value") -} - -// TestBindFunctionAsFunctionInputArgument tests to bind -// a whole dynamic function based on the current context -// as an input argument in the mvc-like handler's function. -func TestBindFunctionAsFunctionInputArgument(t *testing.T) { - app := iris.New() - postsBinder := func(ctx iris.Context) func(string) string { - return ctx.PostValue // or FormValue, the same here. - } - - h := MustMakeHandler(func(get func(string) string) string { - // send the `ctx.PostValue/FormValue("username")` value - // to the client. - return get("username") - }, - // bind the function binder. - reflect.ValueOf(postsBinder)) - - app.Post("/", h) - - e := httptest.New(t, app) - - expectedUsername := "kataras" - e.POST("/").WithFormField("username", expectedUsername). - Expect().Status(iris.StatusOK).Body().Equal(expectedUsername) -} diff --git a/mvc/ideas/1/main.go b/mvc/ideas/1/main.go index 2c46f815..bde6019b 100644 --- a/mvc/ideas/1/main.go +++ b/mvc/ideas/1/main.go @@ -36,7 +36,7 @@ func TodoApp(app *mvc.Application) { // 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{})), + sessions.New(sessions.Config{})).Start, &prefixedLogger{prefix: "DEV"}, ) diff --git a/mvc/mvc.go b/mvc/mvc.go index fb56a817..e3adff6b 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -1,6 +1,9 @@ package mvc -import "github.com/kataras/iris/core/router" +import ( + "github.com/kataras/iris/core/router" + "github.com/kataras/iris/hero/di" +) // Application is the high-level compoment of the "mvc" package. // It's the API that you will be using to register controllers among with their @@ -14,14 +17,14 @@ import "github.com/kataras/iris/core/router" // // See `mvc#New` for more. type Application struct { - Engine *Engine - Router router.Party + Dependencies di.Values + Router router.Party } -func newApp(engine *Engine, subRouter router.Party) *Application { +func newApp(subRouter router.Party, values di.Values) *Application { return &Application{ - Engine: engine, - Router: subRouter, + Router: subRouter, + Dependencies: values, } } @@ -31,7 +34,7 @@ func newApp(engine *Engine, subRouter router.Party) *Application { // // Example: `New(app.Party("/todo"))` or `New(app)` as it's the same as `New(app.Party("/"))`. func New(party router.Party) *Application { - return newApp(NewEngine(), party) + return newApp(party, di.NewValues()) } // Configure creates a new controller and configures it, @@ -80,7 +83,7 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat // // Example: `.AddDependencies(loggerService{prefix: "dev"}, func(ctx iris.Context) User {...})`. func (app *Application) AddDependencies(values ...interface{}) *Application { - app.Engine.Dependencies.Add(values...) + app.Dependencies.Add(values...) return app } @@ -95,9 +98,59 @@ func (app *Application) AddDependencies(values ...interface{}) *Application { // // It returns this mvc Application. // -// Example: `.Register(new(TodoController))`. +// Usage: `.Register(new(TodoController))`. +// +// Controller accepts a sub router and registers any custom struct +// as controller, if struct doesn't have any compatible methods +// neither are registered via `ControllerActivator`'s `Handle` method +// then the controller is not registered at all. +// +// A Controller may have one or more methods +// that are wrapped to a handler and registered as routes before the server ran. +// The controller's method can accept any input argument that are previously binded +// via the dependencies or route's path accepts dynamic path parameters. +// The controller's fields are also bindable via the dependencies, either a +// static value (service) or a function (dynamically) which accepts a context +// and returns a single value (this type is being used to find the relative field or method's input argument). +// +// 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. +// +// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc func (app *Application) Register(controller interface{}) *Application { - app.Engine.Controller(app.Router, controller) + // initialize the controller's activator, nothing too magical so far. + c := newControllerActivator(app.Router, controller, app.Dependencies) + + // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate` + // call, which is simply parses the controller's methods, end-dev can register custom controller's methods + // by using the BeforeActivation's (a ControllerActivation) `.Handle` method. + if before, ok := controller.(interface { + BeforeActivation(BeforeActivation) + }); ok { + before.BeforeActivation(c) + } + + c.activate() + + if after, okAfter := controller.(interface { + AfterActivation(AfterActivation) + }); okAfter { + after.AfterActivation(c) + } return app } @@ -108,5 +161,5 @@ func (app *Application) Register(controller interface{}) *Application { // // Example: `.NewChild(irisApp.Party("/path")).Register(new(TodoSubController))`. func (app *Application) NewChild(party router.Party) *Application { - return newApp(app.Engine.Clone(), party) + return newApp(party, app.Dependencies.Clone()) } diff --git a/mvc/param.go b/mvc/param.go index b66ae846..8b680aee 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -4,13 +4,10 @@ import ( "reflect" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro/interpreter/ast" ) -// for methods inside a controller. - func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { if len(funcIn) == 0 || len(params) == 0 { return @@ -64,43 +61,3 @@ func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Valu return reflect.ValueOf(fn) } - -// for raw handlers, independent of a controller. - -// PathParams is the context's named path parameters, see `PathParamsBinder` too. -type PathParams = context.RequestParams - -// PathParamsBinder is the binder which will bind the `PathParams` type value to the specific -// handler's input argument, see `PathParams` as well. -func PathParamsBinder(ctx context.Context) PathParams { - return *ctx.Params() -} - -// PathParam describes a named path parameter, it's the result of the PathParamBinder and the expected -// handler func's input argument's type, see `PathParamBinder` too. -type PathParam struct { - memstore.Entry - Empty bool -} - -// PathParamBinder is the binder which binds a handler func's input argument to a named path parameter -// based on its name, see `PathParam` as well. -func PathParamBinder(name string) func(ctx context.Context) PathParam { - return func(ctx context.Context) PathParam { - e, found := ctx.Params().GetEntry(name) - if !found { - - // useless check here but it doesn't hurt, - // useful only when white-box tests run. - if ctx.Application() != nil { - ctx.Application().Logger().Warnf(ctx.HandlerName()+": expected parameter name '%s' to be described in the route's path in order to be received by the `ParamBinder`, please fix it.\n The main handler will not be executed for your own protection.", name) - } - - ctx.StopExecution() - return PathParam{ - Empty: true, - } - } - return PathParam{e, false} - } -} diff --git a/mvc/param_test.go b/mvc/param_test.go deleted file mode 100644 index a46d69df..00000000 --- a/mvc/param_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package mvc - -import ( - "testing" - - "github.com/kataras/iris/context" -) - -func TestPathParamsBinder(t *testing.T) { - m := NewEngine() - m.Dependencies.Add(PathParamsBinder) - - got := "" - - h := m.Handler(func(params PathParams) { - got = params.Get("firstname") + params.Get("lastname") - }) - - ctx := context.NewContext(nil) - ctx.Params().Set("firstname", "Gerasimos") - ctx.Params().Set("lastname", "Maropoulos") - h(ctx) - expected := "GerasimosMaropoulos" - if got != expected { - t.Fatalf("expected the params 'firstname' + 'lastname' to be '%s' but got '%s'", expected, got) - } -} -func TestPathParamBinder(t *testing.T) { - m := NewEngine() - m.Dependencies.Add(PathParamBinder("username")) - - got := "" - executed := false - h := m.Handler(func(username PathParam) { - // this should not be fired at all if "username" param wasn't found at all. - // although router is responsible for that but the `ParamBinder` makes that check as well because - // the end-developer may put a param as input argument on her/his function but - // on its route's path didn't describe the path parameter, - // the handler fires a warning and stops the execution for the invalid handler to protect the user. - executed = true - got = username.String() - }) - - expectedUsername := "kataras" - ctx := context.NewContext(nil) - ctx.Params().Set("username", expectedUsername) - h(ctx) - - if got != expectedUsername { - t.Fatalf("expected the param 'username' to be '%s' but got '%s'", expectedUsername, got) - } - - /* this is useless, we don't need to check if ctx.Stopped - inside bindings, this is middleware's job. - // test the non executed if param not found -> this is already done in Iris if real context, so ignore it. - executed = false - got = "" - - ctx2 := context.NewContext(nil) - h(ctx2) - - if got != "" { - t.Fatalf("expected the param 'username' to be entirely empty but got '%s'", got) - } - if executed { - t.Fatalf("expected the handler to not be executed") - } - */ -} diff --git a/mvc/reflect.go b/mvc/reflect.go index c28f87fc..1b13bbe6 100644 --- a/mvc/reflect.go +++ b/mvc/reflect.go @@ -1,11 +1,6 @@ package mvc -import ( - "reflect" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/mvc/di" -) +import "reflect" var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() @@ -13,12 +8,6 @@ func isBaseController(ctrlTyp reflect.Type) bool { return ctrlTyp.Implements(baseControllerTyp) } -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() - -func isContext(inTyp reflect.Type) bool { - return inTyp.Implements(contextTyp) -} - func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type { n := funcTyp.NumIn() funcIn := make([]reflect.Type, n, n) @@ -27,28 +16,3 @@ func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type { } return funcIn } - -var ( - typeChecker = func(fn reflect.Type) bool { - // valid if that single input arg is a typeof context.Context. - return fn.NumIn() == 1 && isContext(fn.In(0)) - } - - hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { - if !isContext(fieldOrFuncInput) { - return nil, false - } - - // this is being used on both func injector and struct injector. - // if the func's input argument or the struct's field is a type of Context - // then we can do a fast binding using the ctxValue - // which is used as slice of reflect.Value, because of the final method's `Call`. - return &di.BindObject{ - Type: contextTyp, - BindType: di.Dynamic, - ReturnValue: func(ctxValue []reflect.Value) reflect.Value { - return ctxValue[0] - }, - }, true - } -) diff --git a/mvc/session.go b/mvc/session.go deleted file mode 100644 index 9e20cc7d..00000000 --- a/mvc/session.go +++ /dev/null @@ -1,12 +0,0 @@ -package mvc - -import ( - "github.com/kataras/iris/context" - "github.com/kataras/iris/sessions" -) - -// Session is a binder that will fill a *sessions.Session function input argument -// or a Controller struct's field. -func Session(sess *sessions.Sessions) func(context.Context) *sessions.Session { - return sess.Start -} diff --git a/mvc/session_controller.go b/mvc/session_controller.go index fd24cbf7..72afa789 100644 --- a/mvc/session_controller.go +++ b/mvc/session_controller.go @@ -11,7 +11,9 @@ var defaultSessionManager = sessions.New(sessions.Config{}) // which requires a binded session manager in order to give // direct access to the current client's session via its `Session` field. // -// SessionController is deprecated please use the `mvc.Session(manager)` instead, it's more useful, +// SessionController is deprecated please use the new dependency injection's methods instead, +// i.e `mvcApp.AddDependencies(sessions.New(sessions.Config{}).Start)`. +// It's more controlled by you, // also *sessions.Session type can now `Destroy` itself without the need of the manager, embrace it. type SessionController struct { Manager *sessions.Sessions