From a675e8191a2ea31c67e6f3619365526370cd3ab1 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 30 Sep 2018 17:26:40 +0300 Subject: [PATCH] fix macro registration issue and match by kind for MVC and hero instead of its kind, so custom types like structs can be used without any issues. Add an example on how to register a custom macro it is just few lines and all in one place in this version. Former-commit-id: 93c439560fcfad820f9f3e39c1d9557c83cef0ee --- _examples/routing/macros/main.go | 124 +++++++++++++------------------ context/request_params.go | 34 ++++----- hero/param.go | 2 +- macro/AUTHORS | 4 + macro/LICENSE | 27 +++++++ macro/macros.go | 15 ++-- mvc/param.go | 2 +- 7 files changed, 111 insertions(+), 97 deletions(-) create mode 100644 macro/AUTHORS create mode 100644 macro/LICENSE diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 9361ff5f..3de4e29a 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -1,12 +1,14 @@ +// Package main shows how you can register a custom parameter type and macro functions that belongs to it. package main import ( "fmt" "reflect" + "sort" + "strings" "github.com/kataras/iris" "github.com/kataras/iris/context" - // "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero" ) @@ -14,80 +16,60 @@ func main() { app := iris.New() 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("slice", "", false, true, func(paramValue string) (interface{}, bool) { + return strings.Split(paramValue, "/"), true + }).RegisterFunc("contains", func(expectedItems []string) func(paramValue []string) bool { + sort.Strings(expectedItems) + return func(paramValue []string) bool { + if len(paramValue) != len(expectedItems) { + return false + } - // return uint32(n) >= min - // } - // }) + sort.Strings(paramValue) + for i := 0; i < len(paramValue); i++ { + if paramValue[i] != expectedItems[i] { + return false + } + } - /* 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 - // } - // }) - - // // optionally, only when mvc or hero features are used for this custom macro/parameter type. - // context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // /* 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 { - // 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{} */ - // } - // // - - app.Get("/test_uint32/{myparam1:string}/{myparam2:uint32 min(10)}", hero.Handler(func(myparam1 string, myparam2 uint32) string { - 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) - }, func(ctx context.Context) {}) - - 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) + return true + } }) - 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: - println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) - ctx.Writef("Value of the parameters are: %s:%d\n", ctx.Params().Get("myparam1"), ctx.Params().GetUint64Default("myparam2", 0)) + // In order to use your new param type inside MVC controller's function input argument or a hero function input argument + // you have to tell the Iris what type it is, the `ValueRaw` of the parameter is the same type + // as you defined it above with the func(paramValue string) (interface{}, bool). + // The new value and its type(from string to your new custom type) it is stored only once now, + // you don't have to do any conversions for simple cases like this. + context.ParamResolvers[reflect.TypeOf([]string{})] = func(paramIndex int) interface{} { + return func(ctx context.Context) []string { + // When you want to retrieve a parameter with a value type that it is not supported by-default, such as ctx.Params().GetInt + // then you can use the `GetEntry` or `GetEntryAt` and cast its underline `ValueRaw` to the desired type. + // The type should be the same as the macro's evaluator function (last argument on the Macros#Register) return value. + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.([]string) + } + } + + /* + http://localhost:8080/test_slice_hero/myvaluei1/myavlue2 -> + myparam's value (a trailing path parameter type) is: []string{"myvaluei1", "myavlue2"} + */ + app.Get("/test_slice_hero/{myparam:slice}", hero.Handler(func(myparam []string) string { + return fmt.Sprintf("myparam's value (a trailing path parameter type) is: %#v\n", myparam) + })) + + /* + http://localhost:8080/test_slice_contains/notcontains1/value2 -> + (404) Not Found + + http://localhost:8080/test_slice_contains/value1/value2 -> + myparam's value (a trailing path parameter type) is: []string{"value1", "value2"} + */ + app.Get("/test_slice_contains/{myparam:slice contains([value1,value2])}", func(ctx context.Context) { + // When it is not a built'n function available to retrieve your value with the type you want, such as ctx.Params().GetInt + // then you can use the `GetEntry.ValueRaw` to get the real value, which is set-ed by your macro above. + myparam := ctx.Params().GetEntry("myparam").ValueRaw.([]string) + ctx.Writef("myparam's value (a trailing path parameter type) is: %#v\n", myparam) }) app.Run(iris.Addr(":8080")) diff --git a/context/request_params.go b/context/request_params.go index 9e45c363..860ccf90 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -83,65 +83,65 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { } var ( - ParamResolvers = map[reflect.Kind]func(paramIndex int) interface{}{ - reflect.String: func(paramIndex int) interface{} { + ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ + reflect.TypeOf(""): func(paramIndex int) interface{} { return func(ctx Context) string { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) } }, - reflect.Int: func(paramIndex int) interface{} { + reflect.TypeOf(int(1)): func(paramIndex int) interface{} { return func(ctx Context) int { // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) // return v return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) } }, - reflect.Int8: func(paramIndex int) interface{} { + reflect.TypeOf(int8(1)): func(paramIndex int) interface{} { return func(ctx Context) int8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) } }, - reflect.Int16: func(paramIndex int) interface{} { + reflect.TypeOf(int16(1)): func(paramIndex int) interface{} { return func(ctx Context) int16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) } }, - reflect.Int32: func(paramIndex int) interface{} { + reflect.TypeOf(int32(1)): func(paramIndex int) interface{} { return func(ctx Context) int32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) } }, - reflect.Int64: func(paramIndex int) interface{} { + reflect.TypeOf(int64(1)): func(paramIndex int) interface{} { return func(ctx Context) int64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) } }, - reflect.Uint: func(paramIndex int) interface{} { + reflect.TypeOf(uint(1)): func(paramIndex int) interface{} { return func(ctx Context) uint { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) } }, - reflect.Uint8: func(paramIndex int) interface{} { + reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} { return func(ctx Context) uint8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) } }, - reflect.Uint16: func(paramIndex int) interface{} { + reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} { return func(ctx Context) uint16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) } }, - reflect.Uint32: func(paramIndex int) interface{} { + reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} { return func(ctx Context) uint32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) } }, - reflect.Uint64: func(paramIndex int) interface{} { + reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} { return func(ctx Context) uint64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) } }, - reflect.Bool: func(paramIndex int) interface{} { + reflect.TypeOf(true): func(paramIndex int) interface{} { return func(ctx Context) bool { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) } @@ -149,16 +149,16 @@ var ( } ) -// ParamResolverByKindAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// ParamResolverByTypeAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type // and the parameter's index based on the registered path. -// Usage: nameResolver := ParamResolverByKindAndKey(reflect.String, 0) +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.TypeOf(""), 0) // Inside a Handler: nameResolver.Call(ctx)[0] // it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). // It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified // only when Macros are modified in such way that the default selections for the available go std types are not enough. // // Returns empty value and false if "k" does not match any valid parameter resolver. -func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, bool) { +func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Value, bool) { /* NO: // This could work but its result is not exact type, so direct binding is not possible. resolver := m.ParamResolver @@ -178,7 +178,7 @@ func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, // */ - r, ok := ParamResolvers[k] + r, ok := ParamResolvers[typ] if !ok || r == nil { return reflect.Value{}, false } diff --git a/hero/param.go b/hero/param.go index dc34b10e..6941d554 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,7 +19,7 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := context.ParamResolverByKindAndIndex(typ.Kind(), currentParamIndex) + v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) p.next = p.next + 1 return v, ok diff --git a/macro/AUTHORS b/macro/AUTHORS new file mode 100644 index 00000000..04764750 --- /dev/null +++ b/macro/AUTHORS @@ -0,0 +1,4 @@ +# This is the official list of Iris Macro and Route path interpreter authors for copyright +# purposes. + +Gerasimos Maropoulos diff --git a/macro/LICENSE b/macro/LICENSE new file mode 100644 index 00000000..c73df4ce --- /dev/null +++ b/macro/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017-2018 The Iris Macro and Route path interpreter. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Iris nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/macro/macros.go b/macro/macros.go index c92c0202..12a5cf21 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -10,10 +10,10 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). + // Its functions can be used by the rest of the macros and param types whenever not available function by name is used. + // Because of its "master" boolean value to true (third parameter). String = NewMacro("string", "", true, false, nil). - RegisterFunc("regexp", func(expr string) func(string) bool { - return MustRegexp(expr) - }). + RegisterFunc("regexp", MustRegexp). // checks if param value starts with the 'prefix' arg RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { @@ -431,21 +431,22 @@ func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, eval } func (ms *Macros) register(macro *Macro) bool { - if macro.Indent() == "" || macro.Evaluator == nil { + if macro.Indent() == "" { return false } cp := *ms for _, m := range cp { - // can't add more than one with the same ast characteristics. if macro.Indent() == m.Indent() { return false } - if macro.Alias() == m.Alias() || macro.Alias() == m.Indent() { - return false + if alias := macro.Alias(); alias != "" { + if alias == m.Alias() || alias == m.Indent() { + return false + } } if macro.Master() && m.Master() { diff --git a/mvc/param.go b/mvc/param.go index 72301787..faa68396 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -39,7 +39,7 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) if len(funcIn) <= i { return } - funcDep, ok := context.ParamResolverByKindAndIndex(funcIn[i].Kind(), param.Index) + funcDep, ok := context.ParamResolverByTypeAndIndex(funcIn[i], param.Index) if !ok { continue }