mirror of
https://github.com/kataras/iris.git
synced 2025-02-09 02:34:55 +01:00
start the new mvc - binder
Former-commit-id: 37e56f409ca136700452fb8fbff740fcca3e98bf
This commit is contained in:
parent
907ba28f84
commit
de69b2fba2
127
mvc2/binder.go
Normal file
127
mvc2/binder.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package mvc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InputBinder is the result of `MakeBinder`.
|
||||||
|
// It contains the binder wrapped information, like the
|
||||||
|
// type that is responsible to bind
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustMakeBinder calls the `MakeBinder` and returns its first result, see its docs.
|
||||||
|
// It panics on error.
|
||||||
|
func MustMakeBinder(binder interface{}) *InputBinder {
|
||||||
|
b, err := MakeBinder(binder)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBinder 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.
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
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 makeBinder(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBinder(fn reflect.Value) (*InputBinder, error) {
|
||||||
|
typ := indirectTyp(fn.Type())
|
||||||
|
|
||||||
|
// invalid if not a func.
|
||||||
|
if typ.Kind() != reflect.Func {
|
||||||
|
return nil, errBad
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid if not returns one single value.
|
||||||
|
if typ.NumOut() != 1 {
|
||||||
|
return nil, errBad
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid if input args length is not one.
|
||||||
|
if typ.NumIn() != 1 {
|
||||||
|
return nil, errBad
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid if that single input arg is not a typeof context.Context.
|
||||||
|
if !isContext(typ.In(0)) {
|
||||||
|
return nil, errBad
|
||||||
|
}
|
||||||
|
|
||||||
|
outTyp := typ.Out(0)
|
||||||
|
zeroOutVal := reflect.New(outTyp).Elem()
|
||||||
|
|
||||||
|
bf := func(ctx context.Context) reflect.Value {
|
||||||
|
results := fn.Call([]reflect.Value{reflect.ValueOf(ctx)})
|
||||||
|
if len(results) == 0 {
|
||||||
|
return zeroOutVal
|
||||||
|
}
|
||||||
|
|
||||||
|
v := results[0]
|
||||||
|
if !v.IsValid() {
|
||||||
|
return zeroOutVal
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return &InputBinder{
|
||||||
|
BindType: outTyp,
|
||||||
|
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
|
||||||
|
}
|
150
mvc2/binder_test.go
Normal file
150
mvc2/binder_test.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package mvc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testUserStruct struct {
|
||||||
|
ID int64
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBinderFunc(ctx context.Context) testUserStruct {
|
||||||
|
id, _ := ctx.Params().GetInt64("id")
|
||||||
|
username := ctx.Params().Get("username")
|
||||||
|
return testUserStruct{
|
||||||
|
ID: id,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testBinderStruct struct{}
|
||||||
|
|
||||||
|
func (t *testBinderStruct) Bind(ctx context.Context) testUserStruct {
|
||||||
|
return testBinderFunc(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeBinder(t *testing.T) {
|
||||||
|
testMakeBinder(t, testBinderFunc)
|
||||||
|
testMakeBinder(t, new(testBinderStruct))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMakeBinder(t *testing.T, binder interface{}) {
|
||||||
|
b, err := MakeBinder(binder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to make binder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == nil {
|
||||||
|
t.Fatalf("excepted non-nil *InputBinder but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, got := reflect.TypeOf(testUserStruct{}), b.BindType; expected != got {
|
||||||
|
t.Fatalf("expected type of the binder's return value to be: %T but got: %T", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := testUserStruct{
|
||||||
|
ID: 42,
|
||||||
|
Username: "kataras",
|
||||||
|
}
|
||||||
|
ctx := context.NewContext(nil)
|
||||||
|
ctx.Params().Set("id", fmt.Sprintf("%v", expected.ID))
|
||||||
|
ctx.Params().Set("username", expected.Username)
|
||||||
|
|
||||||
|
v := b.BindFunc(ctx)
|
||||||
|
if !v.CanInterface() {
|
||||||
|
t.Fatalf("result of binder func cannot be interfaced: %#+v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := v.Interface().(testUserStruct)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("result of binder func should be a type of 'testUserStruct' but got: %#+v", v.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != expected {
|
||||||
|
t.Fatalf("invalid result of binder func, expected: %v but got: %v", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSearchBinders 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,
|
||||||
|
// the third will contain only the int input and should fail,
|
||||||
|
// the forth one will contain only the string input and should fail,
|
||||||
|
// the fifth one will contain two integers and should fail,
|
||||||
|
// 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) {
|
||||||
|
// binders
|
||||||
|
var (
|
||||||
|
stringBinder = MustMakeBinder(func(ctx context.Context) string {
|
||||||
|
return "a string"
|
||||||
|
})
|
||||||
|
intBinder = MustMakeBinder(func(ctx context.Context) int {
|
||||||
|
return 42
|
||||||
|
})
|
||||||
|
)
|
||||||
|
// in
|
||||||
|
var (
|
||||||
|
stringType = reflect.TypeOf("string")
|
||||||
|
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},
|
||||||
|
[]interface{}{"a string", 42}, stringType, intType))
|
||||||
|
availableBinders := []*InputBinder{stringBinder, intBinder} // different order than the fist test.
|
||||||
|
// 2
|
||||||
|
check("test2", true, testSearchBinders(t, availableBinders,
|
||||||
|
[]interface{}{"a string", 42}, stringType, intType))
|
||||||
|
// 3
|
||||||
|
check("test-3-fail", false, testSearchBinders(t, availableBinders,
|
||||||
|
[]interface{}{42}, stringType, intType))
|
||||||
|
// 4
|
||||||
|
check("test-4-fail", false, testSearchBinders(t, availableBinders,
|
||||||
|
[]interface{}{"a string"}, stringType, intType))
|
||||||
|
// 5
|
||||||
|
check("test-5-fail", false, testSearchBinders(t, availableBinders,
|
||||||
|
[]interface{}{42, 42}, stringType, intType))
|
||||||
|
// 6
|
||||||
|
check("test-6-fail", false, testSearchBinders(t, availableBinders,
|
||||||
|
[]interface{}{testUserStruct{}}, stringType, intType))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSearchBinders(t *testing.T, binders []*InputBinder, expectingResults []interface{}, in ...reflect.Type) (errString string) {
|
||||||
|
m := searchBinders(binders, in...)
|
||||||
|
|
||||||
|
if len(m) != len(expectingResults) {
|
||||||
|
return "expected results length and valid binders to be equal, so each input has one binder"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.NewContext(nil)
|
||||||
|
for idx, expected := range expectingResults {
|
||||||
|
if m[idx] != nil {
|
||||||
|
v := m[idx].BindFunc(ctx)
|
||||||
|
if got := v.Interface(); got != expected {
|
||||||
|
return fmt.Sprintf("expected result[%d] to be: %v but got: %v", idx, expected, got)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logf("m[%d] = nil on input = %v\n", idx, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
11
mvc2/mvc.go
Normal file
11
mvc2/mvc.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package mvc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNil = errors.New("nil")
|
||||||
|
errBad = errors.New("bad")
|
||||||
|
errAlreadyExists = errors.New("already exists")
|
||||||
|
)
|
34
mvc2/reflect.go
Normal file
34
mvc2/reflect.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package mvc2
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func isContext(inTyp reflect.Type) bool {
|
||||||
|
return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported.
|
||||||
|
}
|
||||||
|
|
||||||
|
func indirectVal(v reflect.Value) reflect.Value {
|
||||||
|
return reflect.Indirect(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func indirectTyp(typ reflect.Type) reflect.Type {
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
|
return typ.Elem()
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func goodVal(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFunc(typ reflect.Type) bool {
|
||||||
|
return typ.Kind() == reflect.Func
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user