mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
I, think, that binders are done, both dynamic functions with different results every time (based on the context) and static services (interface as input(to give the devs the chance make better and most testable code) and struct or both are structs)
Former-commit-id: eb395b06003ea9eae005a36c9c6be0ef63c4d41d
This commit is contained in:
parent
de69b2fba2
commit
3a46102d4d
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
21
mvc2/handler.go
Normal file
21
mvc2/handler.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
92
mvc2/service.go
Normal file
92
mvc2/service.go
Normal file
|
@ -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
|
||||
}
|
46
mvc2/service_test.go
Normal file
46
mvc2/service_test.go
Normal file
|
@ -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{}{}))
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user