diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index d86dac6c..33ace9c1 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -28,6 +28,8 @@ func newApp() *iris.Application { }) // Different path parameters types in the same path. + // Note that: fallback should registered first e.g. {path} {string}, + // because the handler on this case is executing from last to top. app.Get("/u/{p:path}", func(ctx iris.Context) { ctx.Writef(":string, :int, :uint, :alphabetical and :path in the same path pattern.") }) @@ -39,6 +41,13 @@ func newApp() *iris.Application { ctx.Writef("username (string): %s", ctx.Params().Get("username")) }) + app.Get("/u/{firstname:alphabetical}", func(ctx iris.Context) { + ctx.Writef("before firstname (alphabetical), current route name: %s\n", ctx.RouteName()) + ctx.Next() + }, func(ctx iris.Context) { + ctx.Writef("firstname (alphabetical): %s", ctx.Params().Get("firstname")) + }) + app.Get("/u/{id:int}", func(ctx iris.Context) { ctx.Writef("before id (int), current route name: %s\n", ctx.RouteName()) ctx.Next() @@ -53,13 +62,6 @@ func newApp() *iris.Application { ctx.Writef("uid (uint): %d", ctx.Params().GetUintDefault("uid", 0)) }) - app.Get("/u/{firstname:alphabetical}", func(ctx iris.Context) { - ctx.Writef("before firstname (alphabetical), current route name: %s\n", ctx.RouteName()) - ctx.Next() - }, func(ctx iris.Context) { - ctx.Writef("firstname (alphabetical): %s", ctx.Params().Get("firstname")) - }) - /* /u/some/path/here maps to :path /u/abcd maps to :alphabetical (if :alphabetical registered otherwise :string) diff --git a/context/context.go b/context/context.go index 9800fd30..529ad1ae 100644 --- a/context/context.go +++ b/context/context.go @@ -499,17 +499,17 @@ type Context interface { // // If not found or parse errors returns the "def". PostValueInt64Default(name string, def int64) int64 - // PostValueInt64Default returns the parsed form data from POST, PATCH, + // PostValueFloat64 returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as float64. // // If not found returns -1 and a non-nil error. PostValueFloat64(name string) (float64, error) - // PostValueInt64Default returns the parsed form data from POST, PATCH, + // PostValueFloat64Default returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as float64. // // If not found or parse errors returns the "def". PostValueFloat64Default(name string, def float64) float64 - // PostValueInt64Default returns the parsed form data from POST, PATCH, + // PostValueBool returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as bool. // // If not found or value is false, then it returns false, otherwise true. @@ -2290,7 +2290,7 @@ func (ctx *context) PostValueInt64Default(name string, def int64) int64 { return def } -// PostValueInt64Default returns the parsed form data from POST, PATCH, +// PostValueFloat64 returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as float64. // // If not found returns -1 and a non-nil error. @@ -2302,7 +2302,7 @@ func (ctx *context) PostValueFloat64(name string) (float64, error) { return strconv.ParseFloat(v, 64) } -// PostValueInt64Default returns the parsed form data from POST, PATCH, +// PostValueFloat64Default returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as float64. // // If not found or parse errors returns the "def". @@ -2314,7 +2314,7 @@ func (ctx *context) PostValueFloat64Default(name string, def float64) float64 { return def } -// PostValueInt64Default returns the parsed form data from POST, PATCH, +// PostValueBool returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as bool. // // If not found or value is false, then it returns false, otherwise true. diff --git a/core/router/handler.go b/core/router/handler.go index 8c0c992a..5ca72a87 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -105,16 +105,21 @@ func (h *routerHandler) Build(provider RoutesProvider) error { // fixes order when wildcard root is registered before other wildcard paths return true } + if secondSlashLen == firstSlashLen { // fixes order when static path with the same prefix with a wildcard path // is registered after the wildcard path, although this is managed // by the low-level node but it couldn't work if we registered a root level wildcard, this fixes it. - if len(first.Tmpl().Params) == 0 { + if len(first.tmpl.Params) == 0 { return false } - if len(second.Tmpl().Params) == 0 { + if len(second.tmpl.Params) == 0 { return true } + + // No don't fix the order by framework's suggestion, + // let it as it is today; {string} and {path} should be registered before {id} {uint} and e.t.c. + // see `bindMultiParamTypesHandler` for the reason. Order of registration matters. } } @@ -151,6 +156,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { func bindMultiParamTypesHandler(top *Route, r *Route) { r.BuildHandlers() + // println("here for top: " + top.Name + " and current route: " + r.Name) h := r.Handlers[1:] // remove the macro evaluator handler as we manually check below. f := macroHandler.MakeFilter(r.tmpl) if f == nil { @@ -158,8 +164,13 @@ func bindMultiParamTypesHandler(top *Route, r *Route) { } decisionHandler := func(ctx context.Context) { + // println("core/router/handler.go: decision handler; " + ctx.Path() + " route.Name: " + r.Name + " vs context's " + ctx.GetCurrentRoute().Name()) currentRouteName := ctx.RouteName() + + // Different path parameters types in the same path, fallback should registered first e.g. {path} {string}, + // because the handler on this case is executing from last to top. if f(ctx) { + // println("core/router/handler.go: filter for : " + r.Name + " passed") ctx.SetCurrentRouteName(r.Name) ctx.HandlerIndex(0) ctx.Do(h) diff --git a/macro/handler/handler.go b/macro/handler/handler.go index 70375eb8..ed2013b0 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -4,6 +4,7 @@ package handler import ( "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/core/memstore" "github.com/kataras/iris/v12/macro" ) @@ -76,10 +77,33 @@ func MakeFilter(tmpl macro.Template) context.Filter { return false } - if !p.Eval(entry.String(), &ctx.Params().Store) { + value := p.Eval(entry.String()) + if value == nil { ctx.StatusCode(p.ErrCode) return false } + + // Fixes binding different path parameters names, + // + // app.Get("/{fullname:string}", strHandler) + // app.Get("/{id:int}", idHandler) + // + // before that user didn't see anything + // but under the hoods the set-ed value was a type of string instead of type of int, + // because store contained both "fullname" (which set-ed by the router itself on its string representation) + // and "id" by the param evaluator (see core/router/handler.go and bindMultiParamTypesHandler->MakeFilter) + // and the MVC get by index (e.g. 0) therefore + // it got the "fullname" of type string instead of "id" int if /{int} requested. + // which is critical for faster type assertion in the upcoming, new iris dependency injection (20 Feb 2020). + ctx.Params().Store[p.Index] = memstore.Entry{ + Key: p.Name, + ValueRaw: value, + } + + // for i, v := range ctx.Params().Store { + // fmt.Printf("[%d:%s] macro/handler/handler.go: param passed: %s(%v of type: %T)\n", i, v.Key, + // p.Src, v.ValueRaw, v.ValueRaw) + // } } return true diff --git a/macro/template.go b/macro/template.go index d388bfb7..c97cbf26 100644 --- a/macro/template.go +++ b/macro/template.go @@ -3,7 +3,6 @@ package macro import ( "reflect" - "github.com/kataras/iris/v12/core/memstore" "github.com/kataras/iris/v12/macro/interpreter/ast" "github.com/kataras/iris/v12/macro/interpreter/parser" ) @@ -65,29 +64,28 @@ func (p *TemplateParam) CanEval() bool { return p.canEval } -// Eval is the most critical part of the TEmplateParam. -// It is responsible to return "passed:true" or "not passed:false" -// if the "paramValue" is the correct type of the registered parameter type +// Eval is the most critical part of the TemplateParam. +// It is responsible to return the type-based value if passed otherwise nil. +// If the "paramValue" is the correct type of the registered parameter type // and all functions, if any, are passed. -// "paramChanger" is the same form of context's Params().Set -// we could accept a memstore.Store or even context.RequestParams -// but this form has been chosed in order to test easier and fully decoupled from a request when necessary. // // It is called from the converted macro handler (middleware) // from the higher-level component of "kataras/iris/macro/handler#MakeHandler". -func (p *TemplateParam) Eval(paramValue string, paramSetter memstore.ValueSetter) bool { +func (p *TemplateParam) Eval(paramValue string) interface{} { if p.TypeEvaluator == nil { for _, fn := range p.stringInFuncs { if !fn(paramValue) { - return false + return nil } } - return true + return paramValue } + // fmt.Printf("macro/template.go#L88: Eval for param value: %s and p.Src: %s\n", paramValue, p.Src) + newValue, passed := p.TypeEvaluator(paramValue) if !passed { - return false + return nil } if len(p.Funcs) > 0 { @@ -96,13 +94,14 @@ func (p *TemplateParam) Eval(paramValue string, paramSetter memstore.ValueSetter // 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 + return nil } } } - paramSetter.Set(p.Name, newValue) - return true + // fmt.Printf("macro/template.go: passed with value: %v and type: %T\n", newValue, newValue) + + return newValue } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions)