mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
implement a simple path param binder
Former-commit-id: 2edc7f115332b7afe42d6b0b1b7b6edd4a44a121
This commit is contained in:
parent
2448a60e04
commit
9d63e3194f
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 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)
|
||||
}
|
||||
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||
/*
|
||||
// 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
|
||||
}
|
||||
|
||||
return resultHandler, nil
|
||||
in[k+1] = item
|
||||
}
|
||||
} else {
|
||||
in[k] = result
|
||||
}
|
||||
*/
|
||||
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
}
|
||||
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||
}, nil
|
||||
}
|
||||
|
|
44
mvc2/path_param.go
Normal file
44
mvc2/path_param.go
Normal 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
66
mvc2/path_param_test.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user