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
This commit is contained in:
Gerasimos (Makis) Maropoulos 2018-09-30 17:26:40 +03:00
parent 21ab51bde7
commit a675e8191a
7 changed files with 111 additions and 97 deletions

View File

@ -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"))

View File

@ -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
}

View File

@ -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

4
macro/AUTHORS Normal file
View File

@ -0,0 +1,4 @@
# This is the official list of Iris Macro and Route path interpreter authors for copyright
# purposes.
Gerasimos Maropoulos <kataras2006@hotmail.com>

27
macro/LICENSE Normal file
View File

@ -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.

View File

@ -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() {

View File

@ -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
}