From 5a3be2ab58bdd5d759a777d9d50e55e509aa6d15 Mon Sep 17 00:00:00 2001 From: kataras Date: Fri, 24 Nov 2017 17:34:35 +0200 Subject: [PATCH] simplify by join the bind registration(ctx-transformer-to-something-func-binder and service one, which just sets the struct as it's) to one named 'In' and create a 'Child' which will return a new mvc instance with binders inheritanced from the parent one and add a simple test to the mvc_test.go - will have more later on Former-commit-id: 81ae99390c683a61e1b0bac58725a04b9a3eebbb --- mvc2/binder.go | 36 ++++++++++++++++++--------- mvc2/binder_test.go | 7 ------ mvc2/handler_test.go | 58 ++++++++++++++++++++++++++------------------ mvc2/mvc.go | 44 ++++++++++++++++++++++----------- mvc2/mvc_test.go | 22 +++++++++++++++++ 5 files changed, 111 insertions(+), 56 deletions(-) create mode 100644 mvc2/mvc_test.go diff --git a/mvc2/binder.go b/mvc2/binder.go index c4b20648..1b87e141 100644 --- a/mvc2/binder.go +++ b/mvc2/binder.go @@ -68,29 +68,41 @@ func MustMakeFuncInputBinder(binder interface{}) *InputBinder { return b } +type binderType uint32 + +const ( + functionType binderType = iota + serviceType + invalidType +) + +func resolveBinderType(binder interface{}) binderType { + if binder == nil { + return invalidType + } + + switch indirectTyp(reflect.TypeOf(binder)).Kind() { + case reflect.Func: + return functionType + case reflect.Struct: + return serviceType + } + + return invalidType +} + // MakeFuncInputBinder takes a binder function or a struct which contains a "Bind" // function and returns an `InputBinder`, which Iris uses to // resolve and set the input parameters when a handler is executed. // // The "binder" can have the following form: -// `func(iris.Context) UserViewModel` -// and a struct which contains a "Bind" method -// of the same binder form that was described above. +// `func(iris.Context) UserViewModel`. // // The return type of the "binder" should be a value instance, not a pointer, for your own protection. // The binder function should return only one value and // it can accept only one input argument, the Iris' Context (`context.Context` or `iris.Context`). func MakeFuncInputBinder(binder interface{}) (*InputBinder, error) { v := reflect.ValueOf(binder) - - // check if it's a struct or a pointer to a struct - // and contains a "Bind" method, if yes use that as the binder func. - if indirectTyp(v.Type()).Kind() == reflect.Struct { - if m := v.MethodByName("Bind"); m.IsValid() && m.CanInterface() { - v = m - } - } - return makeFuncInputBinder(v) } diff --git a/mvc2/binder_test.go b/mvc2/binder_test.go index daabda8f..099a3578 100644 --- a/mvc2/binder_test.go +++ b/mvc2/binder_test.go @@ -22,15 +22,8 @@ func testBinderFunc(ctx context.Context) testUserStruct { } } -type testBinderStruct struct{} - -func (t *testBinderStruct) Bind(ctx context.Context) testUserStruct { - return testBinderFunc(ctx) -} - func TestMakeFuncInputBinder(t *testing.T) { testMakeFuncInputBinder(t, testBinderFunc) - testMakeFuncInputBinder(t, new(testBinderStruct)) } func testMakeFuncInputBinder(t *testing.T, binder interface{}) { diff --git a/mvc2/handler_test.go b/mvc2/handler_test.go index 046c0754..3a806728 100644 --- a/mvc2/handler_test.go +++ b/mvc2/handler_test.go @@ -40,39 +40,51 @@ 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) { binders := []*InputBinder{ // #1 - MustMakeFuncInputBinder(testBinderFunc), + MustMakeFuncInputBinder(testBinderFuncUserStruct), // #2 - MustMakeServiceInputBinder(&testServiceImpl{prefix: "say"}), + MustMakeServiceInputBinder(testBinderService), // #3 - MustMakeFuncInputBinder(func(ctx iris.Context) string { - return ctx.Params().Get("param") - }), + MustMakeFuncInputBinder(testBinderFuncParam), } var ( - // 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. - consumeUserHandler = func(ctx iris.Context, user testUserStruct) { - ctx.JSON(user) - } - h1 = MustMakeHandler(consumeUserHandler, binders) - - // just one input arg, the service which is binded by the #2 service binder. - consumeServiceHandler = func(service testService) string { - return service.Say("something") - } - h2 = MustMakeHandler(consumeServiceHandler, binders) - - // just one input arg, a standar string which is binded by the #3 func(ctx) any binder. - consumeParamHandler = func(myParam string) string { - return "param is: " + myParam - } - h3 = MustMakeHandler(consumeParamHandler, binders) + h1 = MustMakeHandler(testConsumeUserHandler, binders) + h2 = MustMakeHandler(testConsumeServiceHandler, binders) + h3 = MustMakeHandler(testConsumeParamHandler, binders) ) + 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) diff --git a/mvc2/mvc.go b/mvc2/mvc.go index a737aa34..9ae84450 100644 --- a/mvc2/mvc.go +++ b/mvc2/mvc.go @@ -20,28 +20,44 @@ func New() *Mvc { return new(Mvc) } -func (m *Mvc) RegisterBinder(binders ...interface{}) error { - for _, binder := range binders { - b, err := MakeFuncInputBinder(binder) - if err != nil { - return err +func (m *Mvc) Child() *Mvc { + child := New() + + // copy the current parent's ctx func binders and services to this new child. + if len(m.binders) > 0 { + binders := make([]*InputBinder, len(m.binders), len(m.binders)) + for i, v := range m.binders { + binders[i] = v } - m.binders = append(m.binders, b) + child.binders = binders } - return nil + return child } -func (m *Mvc) RegisterService(services ...interface{}) error { - for _, service := range services { - b, err := MakeServiceInputBinder(service) - if err != nil { - return err +func (m *Mvc) In(binders ...interface{}) { + for _, binder := range binders { + typ := resolveBinderType(binder) + + var ( + b *InputBinder + err error + ) + + if typ == functionType { + b, err = MakeFuncInputBinder(binder) + } else if typ == serviceType { + b, err = MakeServiceInputBinder(binder) + } else { + err = errBad } + + if err != nil { + continue + } + m.binders = append(m.binders, b) } - - return nil } func (m *Mvc) Handler(handler interface{}) context.Handler { diff --git a/mvc2/mvc_test.go b/mvc2/mvc_test.go new file mode 100644 index 00000000..e8ab81b7 --- /dev/null +++ b/mvc2/mvc_test.go @@ -0,0 +1,22 @@ +package mvc2_test + +// black-box in combination with the handler_test + +import ( + "testing" + + . "github.com/kataras/iris/mvc2" +) + +func TestMvcInAndHandler(t *testing.T) { + m := New() + m.In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam) + + var ( + h1 = m.Handler(testConsumeUserHandler) + h2 = m.Handler(testConsumeServiceHandler) + h3 = m.Handler(testConsumeParamHandler) + ) + + testAppWithMvcHandlers(t, h1, h2, h3) +}