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 }