diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 1afaf005..a1dd0d77 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -65,6 +65,24 @@ func main() { return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) })) + app.Get("/test_string/{myparam1}/{myparam2 prefix(a)}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + + app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", 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: diff --git a/core/router/macro.go b/core/router/macro.go index dfbf39be..07549640 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -2,8 +2,6 @@ package router import ( "fmt" - "net/http" - "reflect" "strings" "github.com/kataras/iris/context" @@ -61,21 +59,20 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { // Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. func convertTmplToHandler(tmpl *macro.Template) context.Handler { - - needMacroHandler := false - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) // 2. if we don't have any named params then we don't need a handler too. + + needsMacroHandler := false for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound { - } else { - // println("we need handler for: " + tmpl.Src) - needMacroHandler = true + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break } } - if !needMacroHandler { + if !needsMacroHandler { // println("we don't need handler for: " + tmpl.Src) return nil } @@ -83,38 +80,19 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(tmpl macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { - if p.TypeEvaluator == nil { - // allow. - continue + if !p.CanEval() { + // println(p.Src + " no need to evaluate anything") + continue // allow. } - // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(ctx.Params().Get(p.Name)) - if !passed { + if !p.Eval(ctx.Params().Get(p.Name), ctx.Params().Set) { 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. ctx.Next() } }(*tmpl) - } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index a6297553..6832ee92 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,7 +10,7 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, nil). // if nil allows everything. + String = NewMacro("string", "", true, false, nil). RegisterFunc("regexp", func(expr string) func(string) bool { return MustRegexp(expr) }). diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 9491b6df..983427a8 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,7 @@ package macro import ( + "github.com/kataras/iris/core/memstore" "reflect" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -31,6 +32,61 @@ type TemplateParam struct { ErrCode int `json:"errCode"` TypeEvaluator ParamEvaluator `json:"-"` Funcs []reflect.Value `json:"-"` + + stringInFuncs []func(string) bool + canEval bool +} + +func (p TemplateParam) preComputed() TemplateParam { + for _, pfn := range p.Funcs { + if fn, ok := pfn.Interface().(func(string) bool); ok { + p.stringInFuncs = append(p.stringInFuncs, fn) + } + } + + // if true then it should be execute the type parameter or its functions + // else it can be ignored, + // i.e {myparam} or {myparam:string} or {myparam:path} -> + // their type evaluator is nil because they don't do any checks and they don't change + // the default parameter value's type (string) so no need for any work). + p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode + + return p +} + +func (p *TemplateParam) CanEval() bool { + return p.canEval +} + +// paramChanger is the same form of context's Params().Set +func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, newValue interface{}) (memstore.Entry, bool)) bool { + if p.TypeEvaluator == nil { + for _, fn := range p.stringInFuncs { + if !fn(paramValue) { + return false + } + } + return true + } + + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { + return false + } + + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + 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 + return false + } + } + } + + paramChanger(p.Name, newValue) + return true } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -82,7 +138,7 @@ func Parse(src string, macros Macros) (*Template, error) { tmplParam.Funcs = append(tmplParam.Funcs, evalFn) } - t.Params = append(t.Params, tmplParam) + t.Params = append(t.Params, tmplParam.preComputed()) } return t, nil