From ef5f383227b45fc7c3a9ae1400ad5d242250b444 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 17:29:39 +0300 Subject: [PATCH] support more than string and int at macro functions route path input arguments: int,uint8,uint16,uint32,int8,int32,int64,slice of strings and string Former-commit-id: d29c4fbe5926bac590151322a585f68b394ff72d --- _examples/routing/dynamic-path/main.go | 14 ++--- core/router/macro/interpreter/ast/ast.go | 24 +------- .../router/macro/interpreter/parser/parser.go | 29 ++++----- .../macro/interpreter/parser/parser_test.go | 16 ++--- core/router/macro/macro.go | 60 +++++++++++++++++-- core/router/macro/macro_test.go | 36 ++++++++++- 6 files changed, 119 insertions(+), 60 deletions(-) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index de0f84d1..42ce2f0a 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -112,16 +112,16 @@ func main() { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to number macro type. + // Let's register our first macro attached to uint64 macro type. // "min" = the function // "minValue" = the argument of the function // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :number macro type with the min(...) macro parameter function. - app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { + // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. + app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) + n, err := strconv.ParseUint(paramValue, 10, 64) if err != nil { return false } @@ -129,10 +129,10 @@ func main() { } }) - // http://localhost:8080/profile/id>=1 + // http://localhost:8080/profile/id>=20 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:number min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. id, _ := ctx.Params().GetInt("id") @@ -140,7 +140,7 @@ func main() { }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:number min(1)}/friends/{friendid:number min(1) else 504}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { id, _ := ctx.Params().GetInt("id") // or GetUint64. friendid, _ := ctx.Params().GetInt("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 213d6b33..4a4103a4 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -1,9 +1,7 @@ package ast import ( - "fmt" "reflect" - "strconv" ) // ParamType is a specific uint8 type @@ -206,24 +204,6 @@ type ParamStatement struct { ErrorCode int // 404 } -// ParamFuncArg represents a single parameter function's argument -type ParamFuncArg interface{} - -// ParamFuncArgToInt converts and returns -// any type of "a", to an integer. -func ParamFuncArgToInt(a ParamFuncArg) (int, error) { - switch a.(type) { - case int: - return a.(int), nil - case string: - return strconv.Atoi(a.(string)) - case int64: - return int(a.(int64)), nil - default: - return -1, fmt.Errorf("unexpected function argument type: %q", a) - } -} - // ParamFunc holds the name of a parameter's function // and its arguments (values) // A param func is declared with: @@ -233,6 +213,6 @@ func ParamFuncArgToInt(a ParamFuncArg) (int, error) { // the 1 and 5 are the two param function arguments // range(1,5) type ParamFunc struct { - Name string // range - Args []ParamFuncArg // [1,5] + Name string // range + Args []string // ["1","5"] } diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 84b15770..97920d5b 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -82,10 +82,16 @@ const ( DefaultParamType = ast.ParamTypeString ) -func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { - if t.Type == token.INT { - return ast.ParamFuncArgToInt(t.Literal) - } +// func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { +// if t.Type == token.INT { +// return ast.ParamFuncArgToInt(t.Literal) +// } +// // act all as strings here, because of int vs int64 vs uint64 and etc. +// return t.Literal, nil +// } + +func parseParamFuncArg(t token.Token) (a string, err error) { + // act all as strings here, because of int vs int64 vs uint64 and etc. return t.Literal, nil } @@ -143,25 +149,14 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to // be able to use regex expression as a macro type's func argument too. - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number but got %s", t.Start, t.End, argValTok.Literal) - continue - } // fmt.Printf("argValTok: %#v\n", argValTok) // fmt.Printf("argVal: %#v\n", argVal) - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.COMMA: argValTok := l.NextToken() - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number type but got %s", t.Start, t.End, argValTok.Literal) - continue - } - - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.RPAREN: stmt.Funcs = append(stmt.Funcs, lastParamFunc) lastParamFunc = ast.ParamFunc{} // reset diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index 2ccd5f2d..25fb3400 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -53,10 +53,10 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }}, // 0 @@ -69,7 +69,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }}, // 1 @@ -81,7 +81,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }}, // 2 @@ -189,10 +189,10 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }, @@ -205,7 +205,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }, @@ -218,7 +218,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }, diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 26af655f..23a45d14 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -5,6 +5,7 @@ import ( "reflect" "regexp" "strconv" + "strings" "unicode" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -95,7 +96,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []ast.ParamFuncArg) EvaluatorFunc { + return func(args []string) EvaluatorFunc { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -105,11 +106,60 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { field := typFn.In(i) arg := args[i] - if field.Kind() != reflect.TypeOf(arg).Kind() { - panic("fields should have the same type") + // try to convert the string literal as we get it from the parser. + var ( + v interface{} + err error + ) + + // try to get the value based on the expected type. + switch field.Kind() { + case reflect.Int: + v, err = strconv.Atoi(arg) + case reflect.Int8: + v, err = strconv.ParseInt(arg, 10, 8) + case reflect.Int16: + v, err = strconv.ParseInt(arg, 10, 16) + case reflect.Int32: + v, err = strconv.ParseInt(arg, 10, 32) + case reflect.Int64: + v, err = strconv.ParseInt(arg, 10, 64) + case reflect.Uint8: + v, err = strconv.ParseUint(arg, 10, 8) + case reflect.Uint16: + v, err = strconv.ParseUint(arg, 10, 16) + case reflect.Uint32: + v, err = strconv.ParseUint(arg, 10, 32) + case reflect.Uint64: + v, err = strconv.ParseUint(arg, 10, 64) + case reflect.Float32: + v, err = strconv.ParseFloat(arg, 32) + case reflect.Float64: + v, err = strconv.ParseFloat(arg, 64) + case reflect.Bool: + v, err = strconv.ParseBool(arg) + case reflect.Slice: + if len(arg) > 1 { + if arg[0] == '[' && arg[len(arg)-1] == ']' { + // it is a single argument but as slice. + v = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + } + } + + default: + v = arg } - argValues = append(argValues, reflect.ValueOf(arg)) + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + + argValue := reflect.ValueOf(v) + if expected, got := field.Kind(), argValue.Kind(); expected != got { + panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) + } + + argValues = append(argValues, argValue) } evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() @@ -149,7 +199,7 @@ type ( // and returns an EvaluatorFunc, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc + ParamEvaluatorBuilder func([]string) EvaluatorFunc // ParamFunc represents the parsed // parameter function, it holds diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 834c21e4..beab57f1 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -16,7 +16,7 @@ func TestGoodParamFunc(t *testing.T) { } } - good2 := func(min int, max int) func(string) bool { + good2 := func(min uint64, max uint64) func(string) bool { return func(paramValue string) bool { return true } @@ -244,3 +244,37 @@ func TestPathEvaluatorRaw(t *testing.T) { // testEvaluatorRaw(t, m.String, p.Src, false, 0) // } + +func TestConvertBuilderFunc(t *testing.T) { + fn := func(min uint64, slice []string) func(string) bool { + return func(paramValue string) bool { + if expected, got := "ok", paramValue; expected != got { + t.Fatalf("paramValue is not the expected one: %s vs %s", expected, got) + } + + if expected, got := uint64(1), min; expected != got { + t.Fatalf("min argument is not the expected one: %d vs %d", expected, got) + } + + if expected, got := []string{"name1", "name2"}, slice; len(expected) == len(got) { + if expected, got := "name1", slice[0]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 0, expected, got) + } + + if expected, got := "name2", slice[1]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 1, expected, got) + } + } else { + t.Fatalf("slice argument is not the expected one, the length is difference: %d vs %d", len(expected), len(got)) + } + + return true + } + } + + evalFunc := convertBuilderFunc(fn) + + if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + t.Fatalf("failed, it should fail already") + } +}