implement a simple path param binder

Former-commit-id: 2edc7f115332b7afe42d6b0b1b7b6edd4a44a121
This commit is contained in:
kataras 2017-11-25 14:04:35 +02:00
parent 2448a60e04
commit 9d63e3194f
5 changed files with 177 additions and 8 deletions

View File

@ -42,8 +42,19 @@ func getBindersForInput(binders []*InputBinder, expected ...reflect.Type) map[in
}
continue
}
for _, b := range binders {
// if same type or the result of binder implements the expected in's type.
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
// if has slice we can't know the returning len from now
// so the expected input length and the len(m) are impossible to guess.
if isSliceAndExpectedItem(b.BindType, expected, idx) {
hasSlice = true
m[idx] = b
continue
}
*/
if equalTypes(b.BindType, in) {
if m == nil {
m = make(map[int]*InputBinder)
@ -134,7 +145,7 @@ func makeFuncInputBinder(fn reflect.Value) (*InputBinder, error) {
bf := func(ctxValue []reflect.Value) reflect.Value {
// []reflect.Value{reflect.ValueOf(ctx)}
results := fn.Call(ctxValue)
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
}

View File

@ -67,6 +67,12 @@ func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler,
}
m := getBindersForInput(binders, typIn...)
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
// the only case that these are not equal is when
// binder returns a slice and input contains one or more inputs.
*/
if len(m) != n {
err := fmt.Errorf("input arguments length(%d) of types(%s) and valid binders length(%d) are not equal", n, typIn, len(m))
golog.Errorf("mvc handler: %v", err)
@ -76,12 +82,15 @@ func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler,
hasIn := len(m) > 0
fn := reflect.ValueOf(handler)
resultHandler := func(ctx context.Context) {
if !hasIn {
// if has no input to bind then execute the "handler" using the mvc style
// for any output parameters.
if !hasIn {
return func(ctx context.Context) {
methodfunc.DispatchFuncResult(ctx, fn.Call(emptyIn))
return
}
}, nil
}
return func(ctx context.Context) {
// we could use other tricks for "in"
// here but let's stick to that which is clearly
// that it doesn't keep any previous state
@ -94,9 +103,35 @@ func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler,
ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
for k, v := range m {
in[k] = v.BindFunc(ctxValues)
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
// now an additional check if it's array and has more inputs of the same type
// and all these results to the expected inputs.
// n-1: if has more to set.
result := v.BindFunc(ctxValues)
if isSliceAndExpectedItem(result.Type(), in, k) {
// if kind := result.Kind(); (kind == reflect.Slice || kind == reflect.Array) && n-1 > k {
prev := 0
for j, nn := 1, result.Len(); j < nn; j++ {
item := result.Slice(prev, j)
prev++
// remember; we already set the inputs type, so we know
// what the function expected to have.
if !equalTypes(item.Type(), in[k+1].Type()) {
break
}
in[k+1] = item
}
} else {
in[k] = result
}
*/
if ctx.IsStopped() {
return
}
}
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
}
return resultHandler, nil
}, nil
}

44
mvc2/path_param.go Normal file
View File

@ -0,0 +1,44 @@
package mvc2
import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/memstore"
)
// 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}
}
}

66
mvc2/path_param_test.go Normal file
View File

@ -0,0 +1,66 @@
package mvc2
import (
"testing"
"github.com/kataras/iris/context"
)
func TestPathParamsBinder(t *testing.T) {
m := New()
m.In(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 := New()
m.In(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)
}
// test the non executed if param not found.
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")
}
}

View File

@ -33,6 +33,19 @@ func isFunc(typ reflect.Type) bool {
return typ.Kind() == reflect.Func
}
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
func isSliceAndExpectedItem(got reflect.Type, in []reflect.Type, currentBindersIdx int) bool {
kind := got.Kind()
// if got result is slice or array.
return (kind == reflect.Slice || kind == reflect.Array) &&
// if has expected next input.
len(in)-1 > currentBindersIdx &&
// if the current input's type is not the same as got (if it's not a slice of that types or anything else).
equalTypes(got, in[currentBindersIdx])
}
*/
func equalTypes(got reflect.Type, expected reflect.Type) bool {
if got == expected {
return true