Conversion once at macros and their functions, internal changes required

Former-commit-id: 7b778cccfb7c0e30ca5e8106017ada065993aba5
This commit is contained in:
Gerasimos (Makis) Maropoulos 2018-09-27 03:17:45 +03:00
parent dc3c38b189
commit d6d27b2605
11 changed files with 338 additions and 279 deletions

View File

@ -114,7 +114,7 @@ func main() {
| Param Type | Go Type | Validation | Retrieve Helper | | Param Type | Go Type | Validation | Retrieve Helper |
| -----------------|------|-------------|------| | -----------------|------|-------------|------|
| `:string` | string | anything | `Params().Get` | | `:string` | string | anything | `Params().Get` |
| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| | `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...|
| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` |
| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` |
| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` |
@ -127,7 +127,7 @@ func main() {
```go ```go
app.Get("/users/{id:uint64}", func(ctx iris.Context){ app.Get("/users/{id:uint64}", func(ctx iris.Context){
id, _ := ctx.Params().GetUint64("id") id := ctx.Params().GetUint64Default("id", 0)
// [...] // [...]
}) })
``` ```
@ -226,7 +226,7 @@ func main() {
// but will not match /users/-1 because uint should be bigger than zero // but will not match /users/-1 because uint should be bigger than zero
// neither /users or /users/. // neither /users or /users/.
app.Get("/users/{id:uint64}", func(ctx iris.Context) { app.Get("/users/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id") id := ctx.Params().GetUint64Default("id", 0)
ctx.Writef("User with ID: %d", id) ctx.Writef("User with ID: %d", id)
}) })

View File

@ -2,7 +2,6 @@ package main
import ( import (
"regexp" "regexp"
"strconv"
"github.com/kataras/iris" "github.com/kataras/iris"
) )
@ -122,17 +121,12 @@ func main() {
// Let's register our first macro attached to uint64 macro type. // Let's register our first macro attached to uint64 macro type.
// "min" = the function // "min" = the function
// "minValue" = the argument of the function // "minValue" = the argument of the function
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when // func(uint64) bool = our func's evaluator, this executes in serve time when
// a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function.
app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { app.Macros().Get("uint64").RegisterFunc("min", func(minValue uint64) func(uint64) bool {
// do anything before serve here [...] // type of "paramValue" should match the type of the internal macro's evaluator function, which in this case is "uint64".
// at this case we don't need to do anything return func(paramValue uint64) bool {
return func(paramValue string) bool { return paramValue >= minValue
n, err := strconv.ParseUint(paramValue, 10, 64)
if err != nil {
return false
}
return n >= minValue
} }
}) })
@ -142,14 +136,14 @@ func main() {
app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) { app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) {
// second parameter is the error but it will always nil because we use macros, // second parameter is the error but it will always nil because we use macros,
// the validaton already happened. // the validaton already happened.
id, _ := ctx.Params().GetInt("id") id := ctx.Params().GetUint64Default("id", 0)
ctx.Writef("Hello id: %d", id) ctx.Writef("Hello id: %d", id)
}) })
// to change the error code per route's macro evaluator: // to change the error code per route's macro evaluator:
app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id") // or GetUint64. id := ctx.Params().GetUint64Default("id", 0)
friendid, _ := ctx.Params().GetInt("friendid") friendid := ctx.Params().GetUint64Default("friendid", 0)
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed. }) // this will throw e 504 error code instead of 404 if all route's macros not passed.
@ -169,7 +163,7 @@ func main() {
} }
// MatchString is a type of func(string) bool, so we use it as it is. // MatchString is a type of func(string) bool, so we use it as it is.
app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString)
app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) {
ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon"))
@ -178,7 +172,7 @@ func main() {
// //
// Another one is by using a custom body. // Another one is by using a custom body.
app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return len(paramValue) >= minLength && len(paramValue) <= maxLength return len(paramValue) >= minLength && len(paramValue) <= maxLength
} }
@ -193,7 +187,7 @@ func main() {
// //
// Register your custom macro function which accepts a slice of strings `[...,...]`. // Register your custom macro function which accepts a slice of strings `[...,...]`.
app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
for _, validName := range validNames { for _, validName := range validNames {
if validName == paramValue { if validName == paramValue {

View File

@ -16,37 +16,49 @@ func main() {
app.Logger().SetLevel("debug") 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. // 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 { // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool {
_, err := strconv.ParseUint(paramValue, 10, 32) // _, err := strconv.ParseUint(paramValue, 10, 32)
return err == nil // return err == nil
}). // }).
RegisterFunc("min", func(min uint32) func(string) bool { // RegisterFunc("min", func(min uint32) func(string) bool {
return func(paramValue string) bool { // return func(paramValue string) bool {
n, err := strconv.ParseUint(paramValue, 10, 32) // n, err := strconv.ParseUint(paramValue, 10, 32)
if err != nil { // if err != nil {
return false // return false
} // }
return uint32(n) >= min // return uint32(n) >= min
// }
// })
/* 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
} }
}) })
/* TODO: // optionally, only when mvc or hero features are used for this custom macro/parameter type.
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
*/
context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} {
// return func(store memstore.Store) uint32 { /* 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)*/
// param, _ := store.GetEntryAt(paramIndex) // return func(ctx context.Context) uint32 {
// param := ctx.Params().GetEntryAt(paramIndex)
// paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32)
// return uint32(paramValueAsUint32) // return uint32(paramValueAsUint32)
// } // }
return func(ctx context.Context) uint32 { return func(ctx context.Context) uint32 {
param := ctx.Params().GetEntryAt(paramIndex) return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32)
paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) } /* 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,
return uint32(paramValueAsUint32) we must return a value i.e 0 for int for its interface{} */
}
} }
// //
@ -54,11 +66,11 @@ func main() {
return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) return fmt.Sprintf("Value of the parameter is: %d\n", paramValue)
})) }))
app.Get("test_uint64/{myparam:uint64}", handler) app.Get("test_uint64/{myparam:uint64 min(5)}", 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:
ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0))
})
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))
} }
func handler(ctx context.Context) {
ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam"))
}

View File

@ -1,6 +1,7 @@
package context package context
import ( import (
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -33,7 +34,7 @@ func (r *RequestParams) GetEntry(key string) memstore.Entry {
// by the key-value params. // by the key-value params.
func (r *RequestParams) Visit(visitor func(key string, value string)) { func (r *RequestParams) Visit(visitor func(key string, value string)) {
r.Store.Visit(func(k string, v interface{}) { r.Store.Visit(func(k string, v interface{}) {
visitor(k, v.(string)) // always string here. visitor(k, fmt.Sprintf("%v", v)) // always string here.
}) })
} }

View File

@ -6,6 +6,7 @@
package memstore package memstore
import ( import (
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -67,11 +68,19 @@ func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} {
// If not found returns "def". // If not found returns "def".
func (e Entry) StringDefault(def string) string { func (e Entry) StringDefault(def string) string {
v := e.ValueRaw v := e.ValueRaw
if v == nil {
return def
}
if vString, ok := v.(string); ok { if vString, ok := v.(string); ok {
return vString return vString
} }
val := fmt.Sprintf("%v", v)
if val != "" {
return val
}
return def return def
} }
@ -105,6 +114,20 @@ func (e Entry) IntDefault(def int) (int, error) {
return val, nil return val, nil
case int: case int:
return vv, nil return vv, nil
case int8:
return int(vv), nil
case int32:
return int(vv), nil
case int64:
return int(vv), nil
case uint:
return int(vv), nil
case uint8:
return int(vv), nil
case uint32:
return int(vv), nil
case uint64:
return int(vv), nil
} }
return def, errFindParse.Format("int", e.Key) return def, errFindParse.Format("int", e.Key)
@ -123,6 +146,10 @@ func (e Entry) Int64Default(def int64) (int64, error) {
return strconv.ParseInt(vv, 10, 64) return strconv.ParseInt(vv, 10, 64)
case int64: case int64:
return vv, nil return vv, nil
case int32:
return int64(vv), nil
case int8:
return int64(vv), nil
case int: case int:
return int64(vv), nil return int64(vv), nil
} }
@ -151,6 +178,12 @@ func (e Entry) Float64Default(def float64) (float64, error) {
return vv, nil return vv, nil
case int: case int:
return float64(vv), nil return float64(vv), nil
case int64:
return float64(vv), nil
case uint:
return float64(vv), nil
case uint64:
return float64(vv), nil
} }
return def, errFindParse.Format("float64", e.Key) return def, errFindParse.Format("float64", e.Key)

View File

@ -3,6 +3,7 @@ package router
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"strings" "strings"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
@ -83,24 +84,37 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler {
return func(ctx context.Context) { return func(ctx context.Context) {
for _, p := range tmpl.Params { for _, p := range tmpl.Params {
paramValue := ctx.Params().Get(p.Name) paramValue := ctx.Params().Get(p.Name)
// first, check for type evaluator if p.TypeEvaluator == nil {
if !p.TypeEvaluator(paramValue) { // allow.
ctx.Next()
return
}
// first, check for type evaluator.
newValue, passed := p.TypeEvaluator(paramValue)
if !passed {
ctx.StatusCode(p.ErrCode) ctx.StatusCode(p.ErrCode)
ctx.StopExecution() ctx.StopExecution()
return return
} }
// then check for all of its functions if len(p.Funcs) > 0 {
for _, evalFunc := range p.Funcs { paramIn := []reflect.Value{reflect.ValueOf(newValue)}
if !evalFunc(paramValue) { // then check for all of its functions
ctx.StatusCode(p.ErrCode) for _, evalFunc := range p.Funcs {
ctx.StopExecution() // or make it as func(interface{}) bool and pass directly the "newValue"
return // 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 // if all passed, just continue.
ctx.Next() ctx.Next()
} }
}(*tmpl) }(*tmpl)

View File

@ -12,14 +12,51 @@ import (
// EvaluatorFunc is the signature for both param types and param funcs. // EvaluatorFunc is the signature for both param types and param funcs.
// It should accepts the param's value as string // It should accepts the param's value as string
// and return true if validated otherwise false. // and return true if validated otherwise false.
type EvaluatorFunc func(paramValue string) bool // type EvaluatorFunc func(paramValue string) bool
// type BinderFunc func(paramValue string) interface{}
// NewEvaluatorFromRegexp accepts a regexp "expr" expression type (
// and returns an EvaluatorFunc based on that regexp. ParamEvaluator func(paramValue string) (interface{}, bool)
// the regexp is compiled before return. // FuncEvaluator interface{} // i.e func(paramValue int) bool
)
var goodEvaluatorFuncs = []reflect.Type{
reflect.TypeOf(func(string) (interface{}, bool) { return nil, false }),
reflect.TypeOf(ParamEvaluator(func(string) (interface{}, bool) { return nil, false })),
}
func goodParamFunc(typ reflect.Type) bool {
if typ.Kind() == reflect.Func { // it should be a func which returns a func (see below check).
if typ.NumOut() == 1 {
typOut := typ.Out(0)
if typOut.Kind() != reflect.Func {
return false
}
if typOut.NumOut() == 2 { // if it's a type of EvaluatorFunc, used for param evaluator.
for _, fType := range goodEvaluatorFuncs {
if typOut == fType {
return true
}
}
return false
}
if typOut.NumIn() == 1 && typOut.NumOut() == 1 { // if it's a type of func(paramValue [int,string...]) bool, used for param funcs.
return typOut.Out(0).Kind() == reflect.Bool
}
}
}
return false
}
// Regexp accepts a regexp "expr" expression
// and returns its MatchString.
// The regexp is compiled before return.
// //
// Returns a not-nil error on regexp compile failure. // Returns a not-nil error on regexp compile failure.
func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { func Regexp(expr string) (func(string) bool, error) {
if expr == "" { if expr == "" {
return nil, fmt.Errorf("empty regex expression") return nil, fmt.Errorf("empty regex expression")
} }
@ -37,36 +74,16 @@ func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) {
return r.MatchString, nil return r.MatchString, nil
} }
// MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp // MustRegexp same as Regexp
// but it panics on the "expr" parse failure. // but it panics on the "expr" parse failure.
func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { func MustRegexp(expr string) func(string) bool {
r, err := NewEvaluatorFromRegexp(expr) r, err := Regexp(expr)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return r return r
} }
var (
goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false })
goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false }))
)
func goodParamFunc(typ reflect.Type) bool {
// should be a func
// which returns a func(string) bool
if typ.Kind() == reflect.Func {
if typ.NumOut() == 1 {
typOut := typ.Out(0)
if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 {
return true
}
}
}
return false
}
// goodParamFuncName reports whether the function name is a valid identifier. // goodParamFuncName reports whether the function name is a valid identifier.
func goodParamFuncName(name string) bool { func goodParamFuncName(name string) bool {
if name == "" { if name == "" {
@ -85,7 +102,7 @@ func goodParamFuncName(name string) bool {
// the convertBuilderFunc return value is generating at boot time. // the convertBuilderFunc return value is generating at boot time.
// convertFunc converts an interface to a valid full param function. // convertFunc converts an interface to a valid full param function.
func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { func convertBuilderFunc(fn interface{}) ParamFuncBuilder {
typFn := reflect.TypeOf(fn) typFn := reflect.TypeOf(fn)
if !goodParamFunc(typFn) { if !goodParamFunc(typFn) {
@ -94,7 +111,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder {
numFields := typFn.NumIn() numFields := typFn.NumIn()
return func(args []string) EvaluatorFunc { return func(args []string) reflect.Value {
if len(args) != numFields { if len(args) != numFields {
// no variadics support, for now. // no variadics support, for now.
panic("args should be the same len as numFields") panic("args should be the same len as numFields")
@ -179,24 +196,25 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder {
argValue := reflect.ValueOf(val) argValue := reflect.ValueOf(val)
if expected, got := field.Kind(), argValue.Kind(); expected != got { if expected, got := field.Kind(), argValue.Kind(); expected != got {
panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) panic(fmt.Sprintf("func's input arguments should have the same type: [%d] expected %s but got %s", i, expected, got))
} }
argValues = append(argValues, argValue) argValues = append(argValues, argValue)
} }
evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() evalFn := reflect.ValueOf(fn).Call(argValues)[0]
var evaluator EvaluatorFunc // var evaluator EvaluatorFunc
// check for typed and not typed // // check for typed and not typed
if _v, ok := evalFn.(EvaluatorFunc); ok { // if _v, ok := evalFn.(EvaluatorFunc); ok {
evaluator = _v // evaluator = _v
} else if _v, ok = evalFn.(func(string) bool); ok { // } else if _v, ok = evalFn.(func(string) bool); ok {
evaluator = _v // evaluator = _v
} // }
return func(paramValue string) bool { // return func(paramValue interface{}) bool {
return evaluator(paramValue) // return evaluator(paramValue)
} // }
return evalFn
} }
} }
@ -218,16 +236,16 @@ type (
master bool master bool
trailing bool trailing bool
Evaluator EvaluatorFunc Evaluator ParamEvaluator
funcs []ParamFunc funcs []ParamFunc
} }
// ParamEvaluatorBuilder is a func // ParamFuncBuilder is a func
// which accepts a param function's arguments (values) // which accepts a param function's arguments (values)
// and returns an EvaluatorFunc, its job // and returns a function as value, its job
// is to make the macros to be registered // is to make the macros to be registered
// by user at the most generic possible way. // by user at the most generic possible way.
ParamEvaluatorBuilder func([]string) EvaluatorFunc ParamFuncBuilder func([]string) reflect.Value // the func
// ParamFunc represents the parsed // ParamFunc represents the parsed
// parameter function, it holds // parameter function, it holds
@ -236,13 +254,13 @@ type (
// the evaluator func. // the evaluator func.
ParamFunc struct { ParamFunc struct {
Name string Name string
Func ParamEvaluatorBuilder Func ParamFuncBuilder
} }
) )
// NewMacro creates and returns a Macro that can be used as a registry for // NewMacro creates and returns a Macro that can be used as a registry for
// a new customized parameter type and its functions. // a new customized parameter type and its functions.
func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvaluator) *Macro {
return &Macro{ return &Macro{
indent: indent, indent: indent,
alias: alias, alias: alias,
@ -287,7 +305,7 @@ func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro {
return m return m
} }
func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { func (m *Macro) registerFunc(funcName string, fullFn ParamFuncBuilder) {
if !goodParamFuncName(funcName) { if !goodParamFuncName(funcName) {
return return
} }
@ -305,7 +323,7 @@ func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) {
}) })
} }
func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { func (m *Macro) getFunc(funcName string) ParamFuncBuilder {
for _, fn := range m.funcs { for _, fn := range m.funcs {
if fn.Name == funcName { if fn.Name == funcName {
if fn.Func == nil { if fn.Func == nil {

View File

@ -2,6 +2,7 @@ package macro
import ( import (
"reflect" "reflect"
"strconv"
"testing" "testing"
) )
@ -64,9 +65,25 @@ func TestGoodParamFuncName(t *testing.T) {
} }
} }
func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bool, i int) { func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) {
if got := macroEvaluator.Evaluator(input); pass != got { if macroEvaluator.Evaluator == nil && pass {
t.Fatalf("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got) return // if not evaluator defined then it should allow everything.
}
value, passed := macroEvaluator.Evaluator(input)
if pass != passed {
t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed)
}
if !passed {
return
}
if value == nil && expectedType != reflect.Invalid {
t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i)
}
if v := reflect.ValueOf(value); v.Kind() != expectedType {
t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind())
} }
} }
@ -84,30 +101,32 @@ func TestStringEvaluatorRaw(t *testing.T) {
} // 0 } // 0
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, String, tt.input, tt.pass, i) testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i)
} }
} }
func TestNumberEvaluatorRaw(t *testing.T) { func TestIntEvaluatorRaw(t *testing.T) {
x64 := strconv.IntSize == 64
tests := []struct { tests := []struct {
pass bool pass bool
input string input string
}{ }{
{false, "astring"}, // 0 {false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1 {false, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2 {true, "32321"}, // 2
{true, "18446744073709551615"}, // 3 {x64, "9223372036854775807" /*max int64*/}, // 3
{true, "-18446744073709551615"}, // 4 {x64, "-9223372036854775808" /*min int64 */}, // 4
{true, "-18446744073709553213213213213213121615"}, // 5 {false, "-18446744073709553213213213213213121615"}, // 5
{false, "42 18446744073709551615"}, // 6 {false, "42 18446744073709551615"}, // 6
{false, "--42"}, // 7 {false, "--42"}, // 7
{false, "+42"}, // 8 {false, "+42"}, // 8
{false, "main.css"}, // 9 {false, "main.css"}, // 9
{false, "/assets/main.css"}, // 10 {false, "/assets/main.css"}, // 10
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, Number, tt.input, tt.pass, i) testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i)
} }
} }
@ -132,7 +151,7 @@ func TestInt64EvaluatorRaw(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i)
} }
} }
@ -161,7 +180,7 @@ func TestUint8EvaluatorRaw(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i)
} }
} }
@ -186,7 +205,7 @@ func TestUint64EvaluatorRaw(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i)
} }
} }
@ -203,7 +222,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i)
} }
} }
@ -220,7 +239,7 @@ func TestFileEvaluatorRaw(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
testEvaluatorRaw(t, File, tt.input, tt.pass, i) testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i)
} }
} }
@ -238,7 +257,7 @@ func TestPathEvaluatorRaw(t *testing.T) {
} }
for i, tt := range pathTests { for i, tt := range pathTests {
testEvaluatorRaw(t, Path, tt.input, tt.pass, i) testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i)
} }
} }
@ -270,8 +289,7 @@ func TestConvertBuilderFunc(t *testing.T) {
} }
evalFunc := convertBuilderFunc(fn) evalFunc := convertBuilderFunc(fn)
if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) {
if !evalFunc([]string{"1", "[name1,name2]"})("ok") {
t.Fatalf("failed, it should fail already") t.Fatalf("failed, it should fail already")
} }
} }

View File

@ -10,233 +10,205 @@ import (
var ( var (
// String type // String type
// Allows anything (single path segment, as everything except the `Path`). // Allows anything (single path segment, as everything except the `Path`).
String = NewMacro("string", "", true, false, func(string) bool { return true }). String = NewMacro("string", "", true, false, nil). // if nil allows everything.
RegisterFunc("regexp", func(expr string) EvaluatorFunc { RegisterFunc("regexp", func(expr string) func(string) bool {
return MustNewEvaluatorFromRegexp(expr) return MustRegexp(expr)
}). }).
// checks if param value starts with the 'prefix' arg // checks if param value starts with the 'prefix' arg
RegisterFunc("prefix", func(prefix string) EvaluatorFunc { RegisterFunc("prefix", func(prefix string) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return strings.HasPrefix(paramValue, prefix) return strings.HasPrefix(paramValue, prefix)
} }
}). }).
// checks if param value ends with the 'suffix' arg // checks if param value ends with the 'suffix' arg
RegisterFunc("suffix", func(suffix string) EvaluatorFunc { RegisterFunc("suffix", func(suffix string) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return strings.HasSuffix(paramValue, suffix) return strings.HasSuffix(paramValue, suffix)
} }
}). }).
// checks if param value contains the 's' arg // checks if param value contains the 's' arg
RegisterFunc("contains", func(s string) EvaluatorFunc { RegisterFunc("contains", func(s string) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return strings.Contains(paramValue, s) return strings.Contains(paramValue, s)
} }
}). }).
// checks if param value's length is at least 'min' // checks if param value's length is at least 'min'
RegisterFunc("min", func(min int) EvaluatorFunc { RegisterFunc("min", func(min int) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return len(paramValue) >= min return len(paramValue) >= min
} }
}). }).
// checks if param value's length is not bigger than 'max' // checks if param value's length is not bigger than 'max'
RegisterFunc("max", func(max int) EvaluatorFunc { RegisterFunc("max", func(max int) func(string) bool {
return func(paramValue string) bool { return func(paramValue string) bool {
return max >= len(paramValue) return max >= len(paramValue)
} }
}) })
simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") simpleNumberEval = MustRegexp("^-?[0-9]+$")
// Number or int type // Int or int type
// both positive and negative numbers, any number of digits. // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch.
Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) {
if !simpleNumberEval(paramValue) {
return nil, false
}
v, err := strconv.Atoi(paramValue)
if err != nil {
return nil, false
}
return v, true
}).
// checks if the param value's int representation is // checks if the param value's int representation is
// bigger or equal than 'min' // bigger or equal than 'min'
RegisterFunc("min", func(min int) EvaluatorFunc { RegisterFunc("min", func(min int) func(int) bool {
return func(paramValue string) bool { return func(paramValue int) bool {
n, err := strconv.Atoi(paramValue) return paramValue >= min
if err != nil {
return false
}
return n >= min
} }
}). }).
// checks if the param value's int representation is // checks if the param value's int representation is
// smaller or equal than 'max'. // smaller or equal than 'max'.
RegisterFunc("max", func(max int) EvaluatorFunc { RegisterFunc("max", func(max int) func(int) bool {
return func(paramValue string) bool { return func(paramValue int) bool {
n, err := strconv.Atoi(paramValue) return paramValue <= max
if err != nil {
return false
}
return n <= max
} }
}). }).
// checks if the param value's int representation is // checks if the param value's int representation is
// between min and max, including 'min' and 'max'. // between min and max, including 'min' and 'max'.
RegisterFunc("range", func(min, max int) EvaluatorFunc { RegisterFunc("range", func(min, max int) func(int) bool {
return func(paramValue string) bool { return func(paramValue int) bool {
n, err := strconv.Atoi(paramValue) return !(paramValue < min || paramValue > max)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
} }
}) })
// Int64 as int64 type // Int64 as int64 type
// -9223372036854775808 to 9223372036854775807. // -9223372036854775808 to 9223372036854775807.
Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) {
if !simpleNumberEvalutator(paramValue) { if !simpleNumberEval(paramValue) {
return false return nil, false
} }
_, err := strconv.ParseInt(paramValue, 10, 64) v, err := strconv.ParseInt(paramValue, 10, 64)
// if err == strconv.ErrRange... if err != nil { // if err == strconv.ErrRange...
return err == nil return nil, false
}
return v, true
}). }).
// checks if the param value's int64 representation is // checks if the param value's int64 representation is
// bigger or equal than 'min'. // bigger or equal than 'min'.
RegisterFunc("min", func(min int64) EvaluatorFunc { RegisterFunc("min", func(min int64) func(int64) bool {
return func(paramValue string) bool { return func(paramValue int64) bool {
n, err := strconv.ParseInt(paramValue, 10, 64) return paramValue >= min
if err != nil {
return false
}
return n >= min
} }
}). }).
// checks if the param value's int64 representation is // checks if the param value's int64 representation is
// smaller or equal than 'max'. // smaller or equal than 'max'.
RegisterFunc("max", func(max int64) EvaluatorFunc { RegisterFunc("max", func(max int64) func(int64) bool {
return func(paramValue string) bool { return func(paramValue int64) bool {
n, err := strconv.ParseInt(paramValue, 10, 64) return paramValue <= max
if err != nil {
return false
}
return n <= max
} }
}). }).
// checks if the param value's int64 representation is // checks if the param value's int64 representation is
// between min and max, including 'min' and 'max'. // between min and max, including 'min' and 'max'.
RegisterFunc("range", func(min, max int64) EvaluatorFunc { RegisterFunc("range", func(min, max int64) func(int64) bool {
return func(paramValue string) bool { return func(paramValue int64) bool {
n, err := strconv.ParseInt(paramValue, 10, 64) return !(paramValue < min || paramValue > max)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
} }
}) })
uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
// Uint8 as uint8 type // Uint8 as uint8 type
// 0 to 255. // 0 to 255.
Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) {
if !uint8Eval(paramValue) {
return nil, false
}
v, err := strconv.ParseUint(paramValue, 10, 8)
if err != nil {
return nil, false
}
return uint8(v), true
}).
// checks if the param value's uint8 representation is // checks if the param value's uint8 representation is
// bigger or equal than 'min'. // bigger or equal than 'min'.
RegisterFunc("min", func(min uint8) EvaluatorFunc { RegisterFunc("min", func(min uint8) func(uint8) bool {
return func(paramValue string) bool { return func(paramValue uint8) bool {
n, err := strconv.ParseUint(paramValue, 10, 8) return paramValue >= min
if err != nil {
return false
}
return uint8(n) >= min
} }
}). }).
// checks if the param value's uint8 representation is // checks if the param value's uint8 representation is
// smaller or equal than 'max'. // smaller or equal than 'max'.
RegisterFunc("max", func(max uint8) EvaluatorFunc { RegisterFunc("max", func(max uint8) func(uint8) bool {
return func(paramValue string) bool { return func(paramValue uint8) bool {
n, err := strconv.ParseUint(paramValue, 10, 8) return paramValue <= max
if err != nil {
return false
}
return uint8(n) <= max
} }
}). }).
// checks if the param value's uint8 representation is // checks if the param value's uint8 representation is
// between min and max, including 'min' and 'max'. // between min and max, including 'min' and 'max'.
RegisterFunc("range", func(min, max uint8) EvaluatorFunc { RegisterFunc("range", func(min, max uint8) func(uint8) bool {
return func(paramValue string) bool { return func(paramValue uint8) bool {
n, err := strconv.ParseUint(paramValue, 10, 8) return !(paramValue < min || paramValue > max)
if err != nil {
return false
}
if v := uint8(n); v < min || v > max {
return false
}
return true
} }
}) })
// Uint64 as uint64 type // Uint64 as uint64 type
// 0 to 18446744073709551615. // 0 to 18446744073709551615.
Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) {
if !simpleNumberEvalutator(paramValue) { if !simpleNumberEval(paramValue) {
return false return nil, false
} }
_, err := strconv.ParseUint(paramValue, 10, 64) v, err := strconv.ParseUint(paramValue, 10, 64)
return err == nil if err != nil {
return nil, false
}
return v, true
}). }).
// checks if the param value's uint64 representation is // checks if the param value's uint64 representation is
// bigger or equal than 'min'. // bigger or equal than 'min'.
RegisterFunc("min", func(min uint64) EvaluatorFunc { RegisterFunc("min", func(min uint64) func(uint64) bool {
return func(paramValue string) bool { return func(paramValue uint64) bool {
n, err := strconv.ParseUint(paramValue, 10, 64) return paramValue >= min
if err != nil {
return false
}
return n >= min
} }
}). }).
// checks if the param value's uint64 representation is // checks if the param value's uint64 representation is
// smaller or equal than 'max'. // smaller or equal than 'max'.
RegisterFunc("max", func(max uint64) EvaluatorFunc { RegisterFunc("max", func(max uint64) func(uint64) bool {
return func(paramValue string) bool { return func(paramValue uint64) bool {
n, err := strconv.ParseUint(paramValue, 10, 64) return paramValue <= max
if err != nil {
return false
}
return n <= max
} }
}). }).
// checks if the param value's uint64 representation is // checks if the param value's uint64 representation is
// between min and max, including 'min' and 'max'. // between min and max, including 'min' and 'max'.
RegisterFunc("range", func(min, max uint64) EvaluatorFunc { RegisterFunc("range", func(min, max uint64) func(uint64) bool {
return func(paramValue string) bool { return func(paramValue uint64) bool {
n, err := strconv.ParseUint(paramValue, 10, 64) return !(paramValue < min || paramValue > max)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
} }
}) })
// Bool or boolean as bool type // Bool or boolean as bool type
// a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True"
// or "0" or "f" or "F" or "FALSE" or "false" or "False". // or "0" or "f" or "F" or "FALSE" or "false" or "False".
Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) {
// a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
// in this case. // in this case.
_, err := strconv.ParseBool(paramValue) v, err := strconv.ParseBool(paramValue)
return err == nil if err != nil {
return nil, false
}
return v, true
}) })
alphabeticalEval = MustRegexp("^[a-zA-Z ]+$")
// Alphabetical letter type // Alphabetical letter type
// letters only (upper or lowercase) // letters only (upper or lowercase)
Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) {
if !alphabeticalEval(paramValue) {
return nil, false
}
return paramValue, true
})
fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$")
// File type // File type
// letters (upper or lowercase) // letters (upper or lowercase)
// numbers (0-9) // numbers (0-9)
@ -244,7 +216,12 @@ var (
// dash (-) // dash (-)
// point (.) // point (.)
// no spaces! or other character // no spaces! or other character
File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) {
if !fileEval(paramValue) {
return nil, false
}
return paramValue, true
})
// Path type // Path type
// anything, should be the last part // anything, should be the last part
// //
@ -252,11 +229,11 @@ var (
// types because I want to give the opportunity to the user // types because I want to give the opportunity to the user
// to organise the macro functions based on wildcard or single dynamic named path parameter. // to organise the macro functions based on wildcard or single dynamic named path parameter.
// Should be living in the latest path segment of a route path. // Should be living in the latest path segment of a route path.
Path = NewMacro("path", "", false, true, func(string) bool { return true }) Path = NewMacro("path", "", false, true, nil)
Defaults = &Macros{ Defaults = &Macros{
String, String,
Number, Int,
Int64, Int64,
Uint8, Uint8,
Uint64, Uint64,
@ -268,7 +245,7 @@ var (
type Macros []*Macro type Macros []*Macro
func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro {
macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator)
if ms.register(macro) { if ms.register(macro) {
return macro return macro

View File

@ -1,6 +1,8 @@
package macro package macro
import ( import (
"reflect"
"github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/ast"
"github.com/kataras/iris/core/router/macro/interpreter/parser" "github.com/kataras/iris/core/router/macro/interpreter/parser"
) )
@ -27,8 +29,8 @@ type TemplateParam struct {
Name string `json:"name"` Name string `json:"name"`
Index int `json:"index"` Index int `json:"index"`
ErrCode int `json:"errCode"` ErrCode int `json:"errCode"`
TypeEvaluator EvaluatorFunc `json:"-"` TypeEvaluator ParamEvaluator `json:"-"`
Funcs []EvaluatorFunc `json:"-"` Funcs []reflect.Value `json:"-"`
} }
// Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions)
@ -74,7 +76,7 @@ func Parse(src string, macros Macros) (*Template, error) {
} }
evalFn := tmplFn(paramfn.Args) evalFn := tmplFn(paramfn.Args)
if evalFn == nil { if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func {
continue continue
} }
tmplParam.Funcs = append(tmplParam.Funcs, evalFn) tmplParam.Funcs = append(tmplParam.Funcs, evalFn)

View File

@ -6,15 +6,13 @@ import (
"runtime" "runtime"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/memstore"
"github.com/kataras/iris/hero/di" "github.com/kataras/iris/hero/di"
"github.com/kataras/golog" "github.com/kataras/golog"
) )
var ( var (
contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem()
memstoreTyp = reflect.TypeOf(memstore.Store{})
) )
// IsContext returns true if the "inTyp" is a type of Context. // IsContext returns true if the "inTyp" is a type of Context.
@ -22,14 +20,6 @@ func IsContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp) return inTyp.Implements(contextTyp)
} }
// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store.
func IsExpectingStore(inTyp reflect.Type) bool {
print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ")
println(inTyp == memstoreTyp)
return inTyp == memstoreTyp
}
// checks if "handler" is context.Handler: func(context.Context). // checks if "handler" is context.Handler: func(context.Context).
func isContextHandler(handler interface{}) (context.Handler, bool) { func isContextHandler(handler interface{}) (context.Handler, bool) {
h, is := handler.(context.Handler) h, is := handler.(context.Handler)