diff --git a/mvc2/binder.go b/mvc2/binder.go index 24665766..1e0b9c00 100644 --- a/mvc2/binder.go +++ b/mvc2/binder.go @@ -2,8 +2,6 @@ package mvc2 import ( "reflect" - - "github.com/kataras/iris/context" ) // InputBinder is the result of `MakeBinder`. @@ -12,20 +10,50 @@ import ( // and a function which will accept a context and returns a value of something. type InputBinder struct { BindType reflect.Type - BindFunc func(context.Context) reflect.Value + BindFunc func(ctx []reflect.Value) reflect.Value } -// MustMakeBinder calls the `MakeBinder` and returns its first result, see its docs. +// getBindersForInput returns a map of the responsible binders for the "expected" types, +// which are the expected input parameters' types, +// based on the available "binders" collection. +// +// It returns a map which its key is the index of the "expected" which +// a valid binder for that in's type found, +// the value is the pointer of the responsible `InputBinder`. +// +// Check of "a nothing responsible for those expected types" +// should be done using the `len(m) == 0`. +func getBindersForInput(binders []*InputBinder, expected ...reflect.Type) map[int]*InputBinder { + var m map[int]*InputBinder + + for idx, in := range expected { + for _, b := range binders { + // if same type or the result of binder implements the expected in's type. + if equalTypes(b.BindType, in) { + if m == nil { + m = make(map[int]*InputBinder) + } + // fmt.Printf("set index: %d to type: %s where input type is: %s\n", idx, b.BindType.String(), in.String()) + m[idx] = b + break + } + } + } + + return m +} + +// MustMakeFuncInputBinder calls the `MakeFuncInputBinder` and returns its first result, see its docs. // It panics on error. -func MustMakeBinder(binder interface{}) *InputBinder { - b, err := MakeBinder(binder) +func MustMakeFuncInputBinder(binder interface{}) *InputBinder { + b, err := MakeFuncInputBinder(binder) if err != nil { panic(err) } return b } -// MakeBinder takes a binder function or a struct which contains a "Bind" +// 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. // @@ -37,7 +65,7 @@ func MustMakeBinder(binder interface{}) *InputBinder { // 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 MakeBinder(binder interface{}) (*InputBinder, error) { +func MakeFuncInputBinder(binder interface{}) (*InputBinder, error) { v := reflect.ValueOf(binder) // check if it's a struct or a pointer to a struct @@ -48,10 +76,10 @@ func MakeBinder(binder interface{}) (*InputBinder, error) { } } - return makeBinder(v) + return makeFuncInputBinder(v) } -func makeBinder(fn reflect.Value) (*InputBinder, error) { +func makeFuncInputBinder(fn reflect.Value) (*InputBinder, error) { typ := indirectTyp(fn.Type()) // invalid if not a func. @@ -77,8 +105,9 @@ func makeBinder(fn reflect.Value) (*InputBinder, error) { outTyp := typ.Out(0) zeroOutVal := reflect.New(outTyp).Elem() - bf := func(ctx context.Context) reflect.Value { - results := fn.Call([]reflect.Value{reflect.ValueOf(ctx)}) + bf := func(ctxValue []reflect.Value) reflect.Value { + // []reflect.Value{reflect.ValueOf(ctx)} + results := fn.Call(ctxValue) if len(results) == 0 { return zeroOutVal } @@ -95,33 +124,3 @@ func makeBinder(fn reflect.Value) (*InputBinder, error) { BindFunc: bf, }, nil } - -// searchBinders returns a map of the responsible binders for the "expected" types, -// which are the expected input parameters' types, -// based on the available "binders" collection. -// -// It returns a map which its key is the index of the "expected" which -// a valid binder for that in's type found, -// the value is the pointer of the responsible `InputBinder`. -// -// Check of "a nothing responsible for those expected types" -// should be done using the `len(m) == 0`. -func searchBinders(binders []*InputBinder, expected ...reflect.Type) map[int]*InputBinder { - var m map[int]*InputBinder - - for idx, in := range expected { - for _, b := range binders { - // if same type or the result of binder implements the expected in's type. - if b.BindType == in || (in.Kind() == reflect.Interface && b.BindType.Implements(in)) { - if m == nil { - m = make(map[int]*InputBinder) - } - // fmt.Printf("set index: %d to type: %s where input type is: %s\n", idx, b.BindType.String(), in.String()) - m[idx] = b - break - } - } - } - - return m -} diff --git a/mvc2/binder_test.go b/mvc2/binder_test.go index a26a97e3..daabda8f 100644 --- a/mvc2/binder_test.go +++ b/mvc2/binder_test.go @@ -28,13 +28,13 @@ func (t *testBinderStruct) Bind(ctx context.Context) testUserStruct { return testBinderFunc(ctx) } -func TestMakeBinder(t *testing.T) { - testMakeBinder(t, testBinderFunc) - testMakeBinder(t, new(testBinderStruct)) +func TestMakeFuncInputBinder(t *testing.T) { + testMakeFuncInputBinder(t, testBinderFunc) + testMakeFuncInputBinder(t, new(testBinderStruct)) } -func testMakeBinder(t *testing.T, binder interface{}) { - b, err := MakeBinder(binder) +func testMakeFuncInputBinder(t *testing.T, binder interface{}) { + b, err := MakeFuncInputBinder(binder) if err != nil { t.Fatalf("failed to make binder: %v", err) } @@ -54,8 +54,8 @@ func testMakeBinder(t *testing.T, binder interface{}) { ctx := context.NewContext(nil) ctx.Params().Set("id", fmt.Sprintf("%v", expected.ID)) ctx.Params().Set("username", expected.Username) - - v := b.BindFunc(ctx) + ctxValue := []reflect.Value{reflect.ValueOf(ctx)} + v := b.BindFunc(ctxValue) if !v.CanInterface() { t.Fatalf("result of binder func cannot be interfaced: %#+v", v) } @@ -70,7 +70,16 @@ func testMakeBinder(t *testing.T, binder interface{}) { } } -// TestSearchBinders will test two available binders, one for int +func testCheck(t *testing.T, testName string, shouldPass bool, errString string) { + if shouldPass && errString != "" { + t.Fatalf("[%s] %s", testName, errString) + } + if !shouldPass && errString == "" { + t.Fatalf("[%s] expected not to pass", testName) + } +} + +// TestGetBindersForInput will test two available binders, one for int // and other for a string, // the first input will contains both of them in the same order, // the second will contain both of them as well but with a different order, @@ -80,13 +89,13 @@ func testMakeBinder(t *testing.T, binder interface{}) { // the last one will contain a struct and should fail, // that no of othe available binders will support it, // so no len of the result should be zero there. -func TestSearchBinders(t *testing.T) { +func TestGetBindersForInput(t *testing.T) { // binders var ( - stringBinder = MustMakeBinder(func(ctx context.Context) string { + stringBinder = MustMakeFuncInputBinder(func(ctx context.Context) string { return "a string" }) - intBinder = MustMakeBinder(func(ctx context.Context) int { + intBinder = MustMakeFuncInputBinder(func(ctx context.Context) int { return 42 }) ) @@ -96,48 +105,39 @@ func TestSearchBinders(t *testing.T) { intType = reflect.TypeOf(1) ) - check := func(testName string, shouldPass bool, errString string) { - if shouldPass && errString != "" { - t.Fatalf("[%s] %s", testName, errString) - } - if !shouldPass && errString == "" { - t.Fatalf("[%s] expected not to pass", testName) - } - } - // 1 - check("test1", true, testSearchBinders(t, []*InputBinder{intBinder, stringBinder}, + testCheck(t, "test1", true, testGetBindersForInput(t, []*InputBinder{intBinder, stringBinder}, []interface{}{"a string", 42}, stringType, intType)) availableBinders := []*InputBinder{stringBinder, intBinder} // different order than the fist test. // 2 - check("test2", true, testSearchBinders(t, availableBinders, + testCheck(t, "test2", true, testGetBindersForInput(t, availableBinders, []interface{}{"a string", 42}, stringType, intType)) // 3 - check("test-3-fail", false, testSearchBinders(t, availableBinders, + testCheck(t, "test-3-fail", false, testGetBindersForInput(t, availableBinders, []interface{}{42}, stringType, intType)) // 4 - check("test-4-fail", false, testSearchBinders(t, availableBinders, + testCheck(t, "test-4-fail", false, testGetBindersForInput(t, availableBinders, []interface{}{"a string"}, stringType, intType)) // 5 - check("test-5-fail", false, testSearchBinders(t, availableBinders, + testCheck(t, "test-5-fail", false, testGetBindersForInput(t, availableBinders, []interface{}{42, 42}, stringType, intType)) // 6 - check("test-6-fail", false, testSearchBinders(t, availableBinders, + testCheck(t, "test-6-fail", false, testGetBindersForInput(t, availableBinders, []interface{}{testUserStruct{}}, stringType, intType)) } -func testSearchBinders(t *testing.T, binders []*InputBinder, expectingResults []interface{}, in ...reflect.Type) (errString string) { - m := searchBinders(binders, in...) +func testGetBindersForInput(t *testing.T, binders []*InputBinder, expectingResults []interface{}, in ...reflect.Type) (errString string) { + m := getBindersForInput(binders, in...) - if len(m) != len(expectingResults) { - return "expected results length and valid binders to be equal, so each input has one binder" + if expected, got := len(expectingResults), len(m); expected != got { + return fmt.Sprintf("expected results length(%d) and valid binders length(%d) to be equal, so each input has one binder", expected, got) } - ctx := context.NewContext(nil) + ctxValue := []reflect.Value{reflect.ValueOf(context.NewContext(nil))} for idx, expected := range expectingResults { if m[idx] != nil { - v := m[idx].BindFunc(ctx) + v := m[idx].BindFunc(ctxValue) if got := v.Interface(); got != expected { return fmt.Sprintf("expected result[%d] to be: %v but got: %v", idx, expected, got) } diff --git a/mvc2/handler.go b/mvc2/handler.go new file mode 100644 index 00000000..a40293c1 --- /dev/null +++ b/mvc2/handler.go @@ -0,0 +1,21 @@ +package mvc2 + +import ( + "fmt" + "reflect" + + "github.com/kataras/iris/context" +) + +// checks if "handler" is context.Handler; func(context.Context). +func isContextHandler(handler interface{}) bool { + _, is := handler.(context.Handler) + return is +} + +func validateHandler(handler interface{}) error { + if typ := reflect.TypeOf(handler); !isFunc(typ) { + return fmt.Errorf("handler expected to be a kind of func but got typeof(%s)", typ.String()) + } + return nil +} diff --git a/mvc2/reflect.go b/mvc2/reflect.go index ff293d76..0661464d 100644 --- a/mvc2/reflect.go +++ b/mvc2/reflect.go @@ -32,3 +32,15 @@ func goodVal(v reflect.Value) bool { func isFunc(typ reflect.Type) bool { return typ.Kind() == reflect.Func } + +func equalTypes(in reflect.Type, v reflect.Type) bool { + if in == v { + return true + } + // if accepts an interface, check if the given "v" type does + // implement this. + if in.Kind() == reflect.Interface { + return v.Implements(in) + } + return false +} diff --git a/mvc2/service.go b/mvc2/service.go new file mode 100644 index 00000000..f3155b8f --- /dev/null +++ b/mvc2/service.go @@ -0,0 +1,92 @@ +package mvc2 + +import ( + "reflect" +) + +// // Service is a `reflect.Value` value. +// // We keep that type here, +// // if we ever need to change this type we will not have +// // to refactor the whole mvc's codebase. +// type Service struct { +// reflect.Value +// typ reflect.Type +// } + +// // Valid checks if the service's Value's Value is valid for set or get. +// func (s Service) Valid() bool { +// return goodVal(s.Value) +// } + +// // Equal returns if the +// func (s Service) Equal(other Service) bool { +// return equalTypes(s.typ, other.typ) +// } + +// func (s Service) String() string { +// return s.Type().String() +// } + +// func wrapService(service interface{}) Service { +// if s, ok := service.(Service); ok { +// return s // if it's a Service already. +// } +// return Service{ +// Value: reflect.ValueOf(service), +// typ: reflect.TypeOf(service), +// } +// } + +// // WrapServices wrap a generic services into structured Service slice. +// func WrapServices(services ...interface{}) []Service { +// if l := len(services); l > 0 { +// out := make([]Service, l, l) +// for i, s := range services { +// out[i] = wrapService(s) +// } +// return out +// } +// return nil +// } + +// MustMakeServiceInputBinder calls the `MakeServiceInputBinder` and returns its first result, see its docs. +// It panics on error. +func MustMakeServiceInputBinder(service interface{}) *InputBinder { + s, err := MakeServiceInputBinder(service) + if err != nil { + panic(err) + } + return s +} + +// MakeServiceInputBinder uses a difference/or strange approach, +// we make the services as bind functions +// in order to keep the rest of the code simpler, however we have +// a performance penalty when calling the function instead +// of just put the responsible service to the certain handler's input argument. +func MakeServiceInputBinder(service interface{}) (*InputBinder, error) { + if service == nil { + return nil, errNil + } + + var ( + val = reflect.ValueOf(service) + typ = val.Type() + ) + + if !goodVal(val) { + return nil, errBad + } + + if indirectTyp(typ).Kind() != reflect.Struct { + // if the pointer's struct is not a struct then return err bad. + return nil, errBad + } + + return &InputBinder{ + BindType: typ, + BindFunc: func(_ []reflect.Value) reflect.Value { + return val + }, + }, nil +} diff --git a/mvc2/service_test.go b/mvc2/service_test.go new file mode 100644 index 00000000..88779dce --- /dev/null +++ b/mvc2/service_test.go @@ -0,0 +1,46 @@ +package mvc2 + +import ( + "reflect" + "testing" +) + +type ( + testService interface { + say(string) + } + testServiceImpl struct { + prefix string + } +) + +func (s *testServiceImpl) say(message string) string { + return s.prefix + ": " + message +} + +func TestMakeServiceInputBinder(t *testing.T) { + expectedService := &testServiceImpl{"say"} + b := MustMakeServiceInputBinder(expectedService) + // in + var ( + intType = reflect.TypeOf(1) + availableBinders = []*InputBinder{b} + ) + + // 1 + testCheck(t, "test1", true, testGetBindersForInput(t, availableBinders, + []interface{}{expectedService}, reflect.TypeOf(expectedService))) + // 2 + testCheck(t, "test2-fail", false, testGetBindersForInput(t, availableBinders, + []interface{}{42})) + // 3 + testCheck(t, "test3-fail", false, testGetBindersForInput(t, availableBinders, + []interface{}{42}, intType)) + // 4 + testCheck(t, "test4-fail", false, testGetBindersForInput(t, availableBinders, + []interface{}{42})) + // 5 - check if nothing passed, so no valid binders at all. + testCheck(t, "test5", true, testGetBindersForInput(t, availableBinders, + []interface{}{})) + +}