From 9d63e3194f8518f32019cf9e18b604c48eb751a5 Mon Sep 17 00:00:00 2001 From: kataras Date: Sat, 25 Nov 2017 14:04:35 +0200 Subject: [PATCH] implement a simple path param binder Former-commit-id: 2edc7f115332b7afe42d6b0b1b7b6edd4a44a121 --- mvc2/binder.go | 13 +++++++- mvc2/handler.go | 49 +++++++++++++++++++++++++----- mvc2/path_param.go | 44 +++++++++++++++++++++++++++ mvc2/path_param_test.go | 66 +++++++++++++++++++++++++++++++++++++++++ mvc2/reflect.go | 13 ++++++++ 5 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 mvc2/path_param.go create mode 100644 mvc2/path_param_test.go diff --git a/mvc2/binder.go b/mvc2/binder.go index 1b87e141..baba2a9d 100644 --- a/mvc2/binder.go +++ b/mvc2/binder.go @@ -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 } diff --git a/mvc2/handler.go b/mvc2/handler.go index a0ea70cc..bda3dc05 100644 --- a/mvc2/handler.go +++ b/mvc2/handler.go @@ -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 } diff --git a/mvc2/path_param.go b/mvc2/path_param.go new file mode 100644 index 00000000..ea0da3f7 --- /dev/null +++ b/mvc2/path_param.go @@ -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} + } +} diff --git a/mvc2/path_param_test.go b/mvc2/path_param_test.go new file mode 100644 index 00000000..c10e47c5 --- /dev/null +++ b/mvc2/path_param_test.go @@ -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") + } +} diff --git a/mvc2/reflect.go b/mvc2/reflect.go index 3e45cc1a..931aa4b8 100644 --- a/mvc2/reflect.go +++ b/mvc2/reflect.go @@ -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