From d6d27b26050ad5dba0f6ec25c09fdfdd0709fa07 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 03:17:45 +0300 Subject: [PATCH] Conversion once at macros and their functions, internal changes required Former-commit-id: 7b778cccfb7c0e30ca5e8106017ada065993aba5 --- README.md | 6 +- _examples/routing/dynamic-path/main.go | 28 ++- _examples/routing/macros/main.go | 66 ++++--- context/request_params.go | 3 +- core/memstore/memstore.go | 33 ++++ core/router/macro.go | 32 +++- core/router/macro/macro.go | 118 +++++++----- core/router/macro/macro_test.go | 68 ++++--- core/router/macro/macros.go | 243 +++++++++++-------------- core/router/macro/template.go | 8 +- hero/handler.go | 12 +- 11 files changed, 338 insertions(+), 279 deletions(-) diff --git a/README.md b/README.md index 9981c0b2..49125deb 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| | `:string` | string | anything | `Params().Get` | -| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| +| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...| | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | @@ -127,7 +127,7 @@ func main() { ```go app.Get("/users/{id:uint64}", func(ctx iris.Context){ - id, _ := ctx.Params().GetUint64("id") + id := ctx.Params().GetUint64Default("id", 0) // [...] }) ``` @@ -226,7 +226,7 @@ func main() { // but will not match /users/-1 because uint should be bigger than zero // neither /users or /users/. app.Get("/users/{id:uint64}", func(ctx iris.Context) { - id, _ := ctx.Params().GetUint64("id") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("User with ID: %d", id) }) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 926b4af1..6d11a878 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -2,7 +2,6 @@ package main import ( "regexp" - "strconv" "github.com/kataras/iris" ) @@ -122,17 +121,12 @@ func main() { // 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 + // func(uint64) bool = our func's evaluator, this executes in serve time when // 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.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= minValue + app.Macros().Get("uint64").RegisterFunc("min", func(minValue uint64) func(uint64) bool { + // type of "paramValue" should match the type of the internal macro's evaluator function, which in this case is "uint64". + return func(paramValue uint64) bool { + return paramValue >= minValue } }) @@ -142,14 +136,14 @@ func main() { 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") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: 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") + id := ctx.Params().GetUint64Default("id", 0) + friendid := ctx.Params().GetUint64Default("friendid", 0) ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. @@ -169,7 +163,7 @@ func main() { } // MatchString is a type of func(string) bool, so we use it as it is. - app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) + app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -178,7 +172,7 @@ func main() { // // Another one is by using a custom body. - app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -193,7 +187,7 @@ func main() { // // Register your custom macro function which accepts a slice of strings `[...,...]`. - app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index f45d4031..8548da88 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -16,37 +16,49 @@ func main() { app.Logger().SetLevel("debug") // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. - app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { - _, err := strconv.ParseUint(paramValue, 10, 32) - return err == nil - }). - RegisterFunc("min", func(min uint32) func(string) bool { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 32) - if err != nil { - return false - } + // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { + // _, err := strconv.ParseUint(paramValue, 10, 32) + // return err == nil + // }). + // RegisterFunc("min", func(min uint32) func(string) bool { + // return func(paramValue string) bool { + // n, err := strconv.ParseUint(paramValue, 10, 32) + // if err != nil { + // return false + // } - return uint32(n) >= min + // return uint32(n) >= min + // } + // }) + + /* TODO: + somehow define one-time how the parameter should be parsed to a particular type (go std or custom) + tip: we can change the original value from string to X using the entry's.ValueRaw + ^ Done 27 sep 2018. + */ + + app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + return uint32(v), err == nil + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min } }) - /* TODO: - somehow define one-time how the parameter should be parsed to a particular type (go std or custom) - tip: we can change the original value from string to X using the entry's.ValueRaw - */ - + // optionally, only when mvc or hero features are used for this custom macro/parameter type. context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // return func(store memstore.Store) uint32 { - // param, _ := store.GetEntryAt(paramIndex) + /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ + // return func(ctx context.Context) uint32 { + // param := ctx.Params().GetEntryAt(paramIndex) // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) // return uint32(paramValueAsUint32) // } return func(ctx context.Context) uint32 { - param := ctx.Params().GetEntryAt(paramIndex) - paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - return uint32(paramValueAsUint32) - } + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, + we must return a value i.e 0 for int for its interface{} */ } // @@ -54,11 +66,11 @@ func main() { return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) })) - app.Get("test_uint64/{myparam:uint64}", handler) + app.Get("test_uint64/{myparam:uint64 min(5)}", func(ctx context.Context) { + // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) + // but better and faster because the macro converts the string to uint64 automatically: + ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0)) + }) app.Run(iris.Addr(":8080")) } - -func handler(ctx context.Context) { - ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) -} diff --git a/context/request_params.go b/context/request_params.go index ebb20afb..b8426856 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -1,6 +1,7 @@ package context import ( + "fmt" "reflect" "strconv" "strings" @@ -33,7 +34,7 @@ func (r *RequestParams) GetEntry(key string) memstore.Entry { // by the key-value params. func (r *RequestParams) Visit(visitor func(key string, value string)) { r.Store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. + visitor(k, fmt.Sprintf("%v", v)) // always string here. }) } diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 52c3b340..4eda5113 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -6,6 +6,7 @@ package memstore import ( + "fmt" "reflect" "strconv" "strings" @@ -67,11 +68,19 @@ func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} { // If not found returns "def". func (e Entry) StringDefault(def string) string { v := e.ValueRaw + if v == nil { + return def + } if vString, ok := v.(string); ok { return vString } + val := fmt.Sprintf("%v", v) + if val != "" { + return val + } + return def } @@ -105,6 +114,20 @@ func (e Entry) IntDefault(def int) (int, error) { return val, nil case int: return vv, nil + case int8: + return int(vv), nil + case int32: + return int(vv), nil + case int64: + return int(vv), nil + case uint: + return int(vv), nil + case uint8: + return int(vv), nil + case uint32: + return int(vv), nil + case uint64: + return int(vv), nil } return def, errFindParse.Format("int", e.Key) @@ -123,6 +146,10 @@ func (e Entry) Int64Default(def int64) (int64, error) { return strconv.ParseInt(vv, 10, 64) case int64: return vv, nil + case int32: + return int64(vv), nil + case int8: + return int64(vv), nil case int: return int64(vv), nil } @@ -151,6 +178,12 @@ func (e Entry) Float64Default(def float64) (float64, error) { return vv, nil case int: return float64(vv), nil + case int64: + return float64(vv), nil + case uint: + return float64(vv), nil + case uint64: + return float64(vv), nil } return def, errFindParse.Format("float64", e.Key) diff --git a/core/router/macro.go b/core/router/macro.go index 02c0ea95..2af58d34 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -3,6 +3,7 @@ package router import ( "fmt" "net/http" + "reflect" "strings" "github.com/kataras/iris/context" @@ -83,24 +84,37 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { paramValue := ctx.Params().Get(p.Name) - // first, check for type evaluator - if !p.TypeEvaluator(paramValue) { + if p.TypeEvaluator == nil { + // allow. + ctx.Next() + return + } + + // first, check for type evaluator. + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return } - // then check for all of its functions - for _, evalFunc := range p.Funcs { - if !evalFunc(paramValue) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + // then check for all of its functions + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } } } + ctx.Params().Store.Set(p.Name, newValue) } - // if all passed, just continue + // if all passed, just continue. ctx.Next() } }(*tmpl) diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 5cb304e0..a589e60a 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -12,14 +12,51 @@ import ( // EvaluatorFunc is the signature for both param types and param funcs. // It should accepts the param's value as string // and return true if validated otherwise false. -type EvaluatorFunc func(paramValue string) bool +// type EvaluatorFunc func(paramValue string) bool +// type BinderFunc func(paramValue string) interface{} -// NewEvaluatorFromRegexp accepts a regexp "expr" expression -// and returns an EvaluatorFunc based on that regexp. -// the regexp is compiled before return. +type ( + ParamEvaluator func(paramValue string) (interface{}, bool) + // FuncEvaluator interface{} // i.e func(paramValue int) bool +) + +var goodEvaluatorFuncs = []reflect.Type{ + reflect.TypeOf(func(string) (interface{}, bool) { return nil, false }), + reflect.TypeOf(ParamEvaluator(func(string) (interface{}, bool) { return nil, false })), +} + +func goodParamFunc(typ reflect.Type) bool { + if typ.Kind() == reflect.Func { // it should be a func which returns a func (see below check). + if typ.NumOut() == 1 { + typOut := typ.Out(0) + if typOut.Kind() != reflect.Func { + return false + } + + if typOut.NumOut() == 2 { // if it's a type of EvaluatorFunc, used for param evaluator. + for _, fType := range goodEvaluatorFuncs { + if typOut == fType { + return true + } + } + return false + } + + if typOut.NumIn() == 1 && typOut.NumOut() == 1 { // if it's a type of func(paramValue [int,string...]) bool, used for param funcs. + return typOut.Out(0).Kind() == reflect.Bool + } + } + } + + return false +} + +// Regexp accepts a regexp "expr" expression +// and returns its MatchString. +// The regexp is compiled before return. // // Returns a not-nil error on regexp compile failure. -func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { +func Regexp(expr string) (func(string) bool, error) { if expr == "" { return nil, fmt.Errorf("empty regex expression") } @@ -37,36 +74,16 @@ func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { return r.MatchString, nil } -// MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp +// MustRegexp same as Regexp // but it panics on the "expr" parse failure. -func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { - r, err := NewEvaluatorFromRegexp(expr) +func MustRegexp(expr string) func(string) bool { + r, err := Regexp(expr) if err != nil { panic(err) } return r } -var ( - goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false }) - goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false })) -) - -func goodParamFunc(typ reflect.Type) bool { - // should be a func - // which returns a func(string) bool - if typ.Kind() == reflect.Func { - if typ.NumOut() == 1 { - typOut := typ.Out(0) - if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 { - return true - } - } - } - - return false -} - // goodParamFuncName reports whether the function name is a valid identifier. func goodParamFuncName(name string) bool { if name == "" { @@ -85,7 +102,7 @@ func goodParamFuncName(name string) bool { // the convertBuilderFunc return value is generating at boot time. // convertFunc converts an interface to a valid full param function. -func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { +func convertBuilderFunc(fn interface{}) ParamFuncBuilder { typFn := reflect.TypeOf(fn) if !goodParamFunc(typFn) { @@ -94,7 +111,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []string) EvaluatorFunc { + return func(args []string) reflect.Value { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -179,24 +196,25 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { argValue := reflect.ValueOf(val) 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)) + panic(fmt.Sprintf("func's input arguments 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() + evalFn := reflect.ValueOf(fn).Call(argValues)[0] - var evaluator EvaluatorFunc - // check for typed and not typed - if _v, ok := evalFn.(EvaluatorFunc); ok { - evaluator = _v - } else if _v, ok = evalFn.(func(string) bool); ok { - evaluator = _v - } - return func(paramValue string) bool { - return evaluator(paramValue) - } + // var evaluator EvaluatorFunc + // // check for typed and not typed + // if _v, ok := evalFn.(EvaluatorFunc); ok { + // evaluator = _v + // } else if _v, ok = evalFn.(func(string) bool); ok { + // evaluator = _v + // } + // return func(paramValue interface{}) bool { + // return evaluator(paramValue) + // } + return evalFn } } @@ -218,16 +236,16 @@ type ( master bool trailing bool - Evaluator EvaluatorFunc + Evaluator ParamEvaluator funcs []ParamFunc } - // ParamEvaluatorBuilder is a func + // ParamFuncBuilder is a func // which accepts a param function's arguments (values) - // and returns an EvaluatorFunc, its job + // and returns a function as value, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]string) EvaluatorFunc + ParamFuncBuilder func([]string) reflect.Value // the func // ParamFunc represents the parsed // parameter function, it holds @@ -236,13 +254,13 @@ type ( // the evaluator func. ParamFunc struct { Name string - Func ParamEvaluatorBuilder + Func ParamFuncBuilder } ) // NewMacro creates and returns a Macro that can be used as a registry for // a new customized parameter type and its functions. -func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { +func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvaluator) *Macro { return &Macro{ indent: indent, alias: alias, @@ -287,7 +305,7 @@ func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { return m } -func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { +func (m *Macro) registerFunc(funcName string, fullFn ParamFuncBuilder) { if !goodParamFuncName(funcName) { return } @@ -305,7 +323,7 @@ func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { }) } -func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { +func (m *Macro) getFunc(funcName string) ParamFuncBuilder { for _, fn := range m.funcs { if fn.Name == funcName { if fn.Func == nil { diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 20a29732..020f4d74 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -2,6 +2,7 @@ package macro import ( "reflect" + "strconv" "testing" ) @@ -64,9 +65,25 @@ func TestGoodParamFuncName(t *testing.T) { } } -func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bool, i int) { - if got := macroEvaluator.Evaluator(input); pass != got { - t.Fatalf("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got) +func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) { + if macroEvaluator.Evaluator == nil && pass { + return // if not evaluator defined then it should allow everything. + } + value, passed := macroEvaluator.Evaluator(input) + if pass != passed { + t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed) + } + + if !passed { + return + } + + if value == nil && expectedType != reflect.Invalid { + t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i) + } + + if v := reflect.ValueOf(value); v.Kind() != expectedType { + t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind()) } } @@ -84,30 +101,32 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(t, String, tt.input, tt.pass, i) + testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i) } } -func TestNumberEvaluatorRaw(t *testing.T) { +func TestIntEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + tests := []struct { pass bool input string }{ - {false, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {true, "18446744073709551615"}, // 3 - {true, "-18446744073709551615"}, // 4 - {true, "-18446744073709553213213213213213121615"}, // 5 - {false, "42 18446744073709551615"}, // 6 - {false, "--42"}, // 7 - {false, "+42"}, // 8 - {false, "main.css"}, // 9 - {false, "/assets/main.css"}, // 10 + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {x64, "9223372036854775807" /*max int64*/}, // 3 + {x64, "-9223372036854775808" /*min int64 */}, // 4 + {false, "-18446744073709553213213213213213121615"}, // 5 + {false, "42 18446744073709551615"}, // 6 + {false, "--42"}, // 7 + {false, "+42"}, // 8 + {false, "main.css"}, // 9 + {false, "/assets/main.css"}, // 10 } for i, tt := range tests { - testEvaluatorRaw(t, Number, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i) } } @@ -132,7 +151,7 @@ func TestInt64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i) } } @@ -161,7 +180,7 @@ func TestUint8EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i) } } @@ -186,7 +205,7 @@ func TestUint64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i) } } @@ -203,7 +222,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) + testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i) } } @@ -220,7 +239,7 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, File, tt.input, tt.pass, i) + testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i) } } @@ -238,7 +257,7 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(t, Path, tt.input, tt.pass, i) + testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i) } } @@ -270,8 +289,7 @@ func TestConvertBuilderFunc(t *testing.T) { } evalFunc := convertBuilderFunc(fn) - - if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) { t.Fatalf("failed, it should fail already") } } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index 1a520af2..756ef9dd 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,233 +10,205 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, func(string) bool { return true }). - RegisterFunc("regexp", func(expr string) EvaluatorFunc { - return MustNewEvaluatorFromRegexp(expr) + String = NewMacro("string", "", true, false, nil). // if nil allows everything. + RegisterFunc("regexp", func(expr string) func(string) bool { + return MustRegexp(expr) }). // checks if param value starts with the 'prefix' arg - RegisterFunc("prefix", func(prefix string) EvaluatorFunc { + RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { return strings.HasPrefix(paramValue, prefix) } }). // checks if param value ends with the 'suffix' arg - RegisterFunc("suffix", func(suffix string) EvaluatorFunc { + RegisterFunc("suffix", func(suffix string) func(string) bool { return func(paramValue string) bool { return strings.HasSuffix(paramValue, suffix) } }). // checks if param value contains the 's' arg - RegisterFunc("contains", func(s string) EvaluatorFunc { + RegisterFunc("contains", func(s string) func(string) bool { return func(paramValue string) bool { return strings.Contains(paramValue, s) } }). // checks if param value's length is at least 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { + RegisterFunc("min", func(min int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= min } }). // checks if param value's length is not bigger than 'max' - RegisterFunc("max", func(max int) EvaluatorFunc { + RegisterFunc("max", func(max int) func(string) bool { return func(paramValue string) bool { return max >= len(paramValue) } }) - simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") - // Number or int type - // both positive and negative numbers, any number of digits. - Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). + simpleNumberEval = MustRegexp("^-?[0-9]+$") + // Int or int type + // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + v, err := strconv.Atoi(paramValue) + if err != nil { + return nil, false + } + + return v, true + }). // checks if the param value's int representation is // bigger or equal than 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int) func(int) bool { + return func(paramValue int) bool { + return paramValue >= min } }). // checks if the param value's int representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int) func(int) bool { + return func(paramValue int) bool { + return paramValue <= max } }). // checks if the param value's int representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max int) func(int) bool { + return func(paramValue int) bool { + return !(paramValue < min || paramValue > max) } }) // Int64 as int64 type // -9223372036854775808 to 9223372036854775807. - Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false } - _, err := strconv.ParseInt(paramValue, 10, 64) - // if err == strconv.ErrRange... - return err == nil + v, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { // if err == strconv.ErrRange... + return nil, false + } + return v, true }). // checks if the param value's int64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue >= min } }). // checks if the param value's int64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue <= max } }). // checks if the param value's int64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max int64) func(int64) bool { + return func(paramValue int64) bool { + return !(paramValue < min || paramValue > max) } }) + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") // Uint8 as uint8 type // 0 to 255. - Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). + Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) { + if !uint8Eval(paramValue) { + return nil, false + } + + v, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return nil, false + } + return uint8(v), true + }). // checks if the param value's uint8 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - return uint8(n) >= min + RegisterFunc("min", func(min uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue >= min } }). // checks if the param value's uint8 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - return uint8(n) <= max + RegisterFunc("max", func(max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue <= max } }). // checks if the param value's uint8 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - if v := uint8(n); v < min || v > max { - return false - } - return true + RegisterFunc("range", func(min, max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return !(paramValue < min || paramValue > max) } }) // Uint64 as uint64 type // 0 to 18446744073709551615. - Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false } - _, err := strconv.ParseUint(paramValue, 10, 64) - return err == nil + v, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return nil, false + } + return v, true }). // checks if the param value's uint64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue >= min } }). // checks if the param value's uint64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue <= max } }). // checks if the param value's uint64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return !(paramValue < min || paramValue > max) } }) // Bool or boolean as bool type // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False". - Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) { // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ // in this case. - _, err := strconv.ParseBool(paramValue) - return err == nil + v, err := strconv.ParseBool(paramValue) + if err != nil { + return nil, false + } + return v, true }) + alphabeticalEval = MustRegexp("^[a-zA-Z ]+$") // Alphabetical letter type // letters only (upper or lowercase) - Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) + Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) { + if !alphabeticalEval(paramValue) { + return nil, false + } + return paramValue, true + }) + + fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$") // File type // letters (upper or lowercase) // numbers (0-9) @@ -244,7 +216,12 @@ var ( // dash (-) // point (.) // no spaces! or other character - File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) + File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) { + if !fileEval(paramValue) { + return nil, false + } + return paramValue, true + }) // Path type // anything, should be the last part // @@ -252,11 +229,11 @@ var ( // types because I want to give the opportunity to the user // to organise the macro functions based on wildcard or single dynamic named path parameter. // Should be living in the latest path segment of a route path. - Path = NewMacro("path", "", false, true, func(string) bool { return true }) + Path = NewMacro("path", "", false, true, nil) Defaults = &Macros{ String, - Number, + Int, Int64, Uint8, Uint64, @@ -268,7 +245,7 @@ var ( type Macros []*Macro -func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) if ms.register(macro) { return macro diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 3b30d2f7..9491b6df 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,8 @@ package macro import ( + "reflect" + "github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/parser" ) @@ -27,8 +29,8 @@ type TemplateParam struct { Name string `json:"name"` Index int `json:"index"` ErrCode int `json:"errCode"` - TypeEvaluator EvaluatorFunc `json:"-"` - Funcs []EvaluatorFunc `json:"-"` + TypeEvaluator ParamEvaluator `json:"-"` + Funcs []reflect.Value `json:"-"` } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -74,7 +76,7 @@ func Parse(src string, macros Macros) (*Template, error) { } evalFn := tmplFn(paramfn.Args) - if evalFn == nil { + if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func { continue } tmplParam.Funcs = append(tmplParam.Funcs, evalFn) diff --git a/hero/handler.go b/hero/handler.go index 6644dc83..f0b47044 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -6,15 +6,13 @@ import ( "runtime" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" ) var ( - contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() - memstoreTyp = reflect.TypeOf(memstore.Store{}) + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() ) // IsContext returns true if the "inTyp" is a type of Context. @@ -22,14 +20,6 @@ func IsContext(inTyp reflect.Type) bool { return inTyp.Implements(contextTyp) } -// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store. -func IsExpectingStore(inTyp reflect.Type) bool { - print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ") - println(inTyp == memstoreTyp) - - return inTyp == memstoreTyp -} - // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { h, is := handler.(context.Handler)