mirror of
https://github.com/kataras/iris.git
synced 2026-01-05 11:17:03 +00:00
add the ability to add custom parameter types to the interpreter and mapped macros with any number of macro functions - example added - although it's working it is not ready yet - I have to do some cleanup, doc comments and a TODO
Former-commit-id: 8ac751b649a3b8e59948fd4c89ad53d25f49d0d5
This commit is contained in:
@@ -68,7 +68,7 @@ func (r *repository) getAll() []*Route {
|
||||
// and child routers.
|
||||
type APIBuilder struct {
|
||||
// the api builder global macros registry
|
||||
macros *macro.Map
|
||||
macros *macro.Macros
|
||||
// the api builder global handlers per status code registry (used for custom http errors)
|
||||
errorCodeHandlers *ErrorCodeHandlers
|
||||
// the api builder global routes repository
|
||||
@@ -116,7 +116,7 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl
|
||||
// which is responsible to build the API and the router handler.
|
||||
func NewAPIBuilder() *APIBuilder {
|
||||
api := &APIBuilder{
|
||||
macros: defaultMacros(),
|
||||
macros: macro.Defaults,
|
||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
||||
reporter: errors.NewReporter(),
|
||||
relativePath: "/",
|
||||
@@ -246,7 +246,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
|
||||
)
|
||||
|
||||
for _, m := range methods {
|
||||
route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros)
|
||||
route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
|
||||
if err != nil { // template path parser errors:
|
||||
api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
|
||||
return nil // fail on first error.
|
||||
@@ -411,11 +411,11 @@ func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party {
|
||||
return api.Subdomain(SubdomainWildcardIndicator, middleware...)
|
||||
}
|
||||
|
||||
// Macros returns the macro map which is responsible
|
||||
// to register custom macro functions for all routes.
|
||||
// Macros returns the macro collection that is responsible
|
||||
// to register custom macros with their own parameter types and their macro functions for all routes.
|
||||
//
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
func (api *APIBuilder) Macros() *macro.Map {
|
||||
func (api *APIBuilder) Macros() *macro.Macros {
|
||||
return api.macros
|
||||
}
|
||||
|
||||
|
||||
@@ -1,295 +1,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/router/macro"
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
)
|
||||
|
||||
// defaultMacros returns a new macro map which
|
||||
// contains the default router's named param types functions.
|
||||
func defaultMacros() *macro.Map {
|
||||
macros := macro.NewMap()
|
||||
// registers the String and Int default macro funcs
|
||||
// user can add or override of his own funcs later on
|
||||
// i.e:
|
||||
// app.Macro.String.RegisterFunc("equal", func(eqWith string) func(string) bool {
|
||||
// return func(paramValue string) bool {
|
||||
// return eqWith == paramValue
|
||||
// }})
|
||||
registerBuiltinsMacroFuncs(macros)
|
||||
|
||||
return macros
|
||||
}
|
||||
|
||||
func registerBuiltinsMacroFuncs(out *macro.Map) {
|
||||
// register the String which is the default type if not
|
||||
// parameter type is specified or
|
||||
// if a given parameter into path given but the func doesn't exist on the
|
||||
// parameter type's function list.
|
||||
//
|
||||
// these can be overridden by the user, later on.
|
||||
registerStringMacroFuncs(out.String)
|
||||
registerNumberMacroFuncs(out.Number)
|
||||
registerInt64MacroFuncs(out.Int64)
|
||||
registerUint8MacroFuncs(out.Uint8)
|
||||
registerUint64MacroFuncs(out.Uint64)
|
||||
registerAlphabeticalMacroFuncs(out.Alphabetical)
|
||||
registerFileMacroFuncs(out.File)
|
||||
registerPathMacroFuncs(out.Path)
|
||||
}
|
||||
|
||||
// String
|
||||
// anything one part
|
||||
func registerStringMacroFuncs(out *macro.Macro) {
|
||||
// this can be used everywhere, it's to help users to define custom regexp expressions
|
||||
// on all macros
|
||||
out.RegisterFunc("regexp", func(expr string) macro.EvaluatorFunc {
|
||||
regexpEvaluator := macro.MustNewEvaluatorFromRegexp(expr)
|
||||
return regexpEvaluator
|
||||
})
|
||||
|
||||
// checks if param value starts with the 'prefix' arg
|
||||
out.RegisterFunc("prefix", func(prefix string) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.HasPrefix(paramValue, prefix)
|
||||
}
|
||||
})
|
||||
|
||||
// checks if param value ends with the 'suffix' arg
|
||||
out.RegisterFunc("suffix", func(suffix string) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.HasSuffix(paramValue, suffix)
|
||||
}
|
||||
})
|
||||
|
||||
// checks if param value contains the 's' arg
|
||||
out.RegisterFunc("contains", func(s string) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.Contains(paramValue, s)
|
||||
}
|
||||
})
|
||||
|
||||
// checks if param value's length is at least 'min'
|
||||
out.RegisterFunc("min", func(min int) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return len(paramValue) >= min
|
||||
}
|
||||
})
|
||||
// checks if param value's length is not bigger than 'max'
|
||||
out.RegisterFunc("max", func(max int) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return max >= len(paramValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Number
|
||||
// positive and negative numbers, number of digits depends on the arch.
|
||||
func registerNumberMacroFuncs(out *macro.Macro) {
|
||||
// checks if the param value's int representation is
|
||||
// bigger or equal than 'min'
|
||||
out.RegisterFunc("min", func(min int) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's int representation is
|
||||
// smaller or equal than 'max'
|
||||
out.RegisterFunc("max", func(max int) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's int representation is
|
||||
// between min and max, including 'min' and 'max'
|
||||
out.RegisterFunc("range", func(min, max int) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Int64
|
||||
// -9223372036854775808 to 9223372036854775807.
|
||||
func registerInt64MacroFuncs(out *macro.Macro) {
|
||||
// checks if the param value's int64 representation is
|
||||
// bigger or equal than 'min'
|
||||
out.RegisterFunc("min", func(min int64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's int64 representation is
|
||||
// smaller or equal than 'max'
|
||||
out.RegisterFunc("max", func(max int64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's int64 representation is
|
||||
// between min and max, including 'min' and 'max'
|
||||
out.RegisterFunc("range", func(min, max int64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Uint8
|
||||
// 0 to 255.
|
||||
func registerUint8MacroFuncs(out *macro.Macro) {
|
||||
// checks if the param value's uint8 representation is
|
||||
// bigger or equal than 'min'
|
||||
out.RegisterFunc("min", func(min uint8) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return uint8(n) >= min
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's uint8 representation is
|
||||
// smaller or equal than 'max'
|
||||
out.RegisterFunc("max", func(max uint8) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return uint8(n) <= max
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's uint8 representation is
|
||||
// between min and max, including 'min' and 'max'
|
||||
out.RegisterFunc("range", func(min, max uint8) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if v := uint8(n); v < min || v > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Uint64
|
||||
// 0 to 18446744073709551615.
|
||||
func registerUint64MacroFuncs(out *macro.Macro) {
|
||||
// checks if the param value's uint64 representation is
|
||||
// bigger or equal than 'min'
|
||||
out.RegisterFunc("min", func(min uint64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's uint64 representation is
|
||||
// smaller or equal than 'max'
|
||||
out.RegisterFunc("max", func(max uint64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
})
|
||||
|
||||
// checks if the param value's uint64 representation is
|
||||
// between min and max, including 'min' and 'max'
|
||||
out.RegisterFunc("range", func(min, max uint64) macro.EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Alphabetical
|
||||
// letters only (upper or lowercase)
|
||||
func registerAlphabeticalMacroFuncs(out *macro.Macro) {
|
||||
|
||||
}
|
||||
|
||||
// File
|
||||
// letters (upper or lowercase)
|
||||
// numbers (0-9)
|
||||
// underscore (_)
|
||||
// dash (-)
|
||||
// point (.)
|
||||
// no spaces! or other character
|
||||
func registerFileMacroFuncs(out *macro.Macro) {
|
||||
|
||||
}
|
||||
|
||||
// Path
|
||||
// File+slashes(anywhere)
|
||||
// should be the latest param, it's the wildcard
|
||||
func registerPathMacroFuncs(out *macro.Macro) {
|
||||
|
||||
}
|
||||
|
||||
// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path
|
||||
// and the new handlers (prepend all the macro's handler, if any).
|
||||
//
|
||||
@@ -325,9 +45,9 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
|
||||
// then the tmpl.Params will be filled,
|
||||
// so no any further check needed
|
||||
for i, p := range tmpl.Params {
|
||||
if p.Type == ast.ParamTypePath {
|
||||
if ast.IsTrailing(p.Type) {
|
||||
if i != len(tmpl.Params)-1 {
|
||||
return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path")
|
||||
return "", fmt.Errorf("parameter type \"%s\" should be putted to the very last of a path", p.Type.Indent())
|
||||
}
|
||||
routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1)
|
||||
} else {
|
||||
@@ -338,7 +58,7 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
|
||||
return routePath, nil
|
||||
}
|
||||
|
||||
// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware
|
||||
// Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware.
|
||||
func convertTmplToHandler(tmpl *macro.Template) context.Handler {
|
||||
|
||||
needMacroHandler := false
|
||||
@@ -347,7 +67,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler {
|
||||
// 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used)
|
||||
// 2. if we don't have any named params then we don't need a handler too.
|
||||
for _, p := range tmpl.Params {
|
||||
if len(p.Funcs) == 0 && (p.Type == ast.ParamTypeUnExpected || p.Type == ast.ParamTypeString || p.Type == ast.ParamTypePath) && p.ErrCode == http.StatusNotFound {
|
||||
if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound {
|
||||
} else {
|
||||
// println("we need handler for: " + tmpl.Src)
|
||||
needMacroHandler = true
|
||||
|
||||
@@ -1,43 +1,68 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
type (
|
||||
// ParamType holds the necessary information about a parameter type for the parser to lookup for.
|
||||
ParamType interface {
|
||||
// The name of the parameter type.
|
||||
// Indent should contain the characters for the parser.
|
||||
Indent() string
|
||||
}
|
||||
|
||||
// MasterParamType if implemented and its `Master()` returns true then empty type param will be translated to this param type.
|
||||
// Also its functions will be available to the rest of the macro param type's funcs.
|
||||
//
|
||||
// Only one Master is allowed.
|
||||
MasterParamType interface {
|
||||
ParamType
|
||||
Master() bool
|
||||
}
|
||||
|
||||
// TrailingParamType if implemented and its `Trailing()` returns true
|
||||
// then it should be declared at the end of a route path and can accept any trailing path segment as one parameter.
|
||||
TrailingParamType interface {
|
||||
ParamType
|
||||
Trailing() bool
|
||||
}
|
||||
|
||||
// AliasParamType if implemeneted nad its `Alias()` returns a non-empty string
|
||||
// then the param type can be written with that string literal too.
|
||||
AliasParamType interface {
|
||||
ParamType
|
||||
Alias() string
|
||||
}
|
||||
)
|
||||
|
||||
// ParamType holds the necessary information about a parameter type.
|
||||
type ParamType struct {
|
||||
Indent string // the name of the parameter type.
|
||||
Aliases []string // any aliases, can be empty.
|
||||
|
||||
GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings.
|
||||
|
||||
Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs.
|
||||
End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter.
|
||||
|
||||
invalid bool // only true if returned by the parser via `LookupParamType`.
|
||||
// IsMaster returns true if the "pt" param type is a master one.
|
||||
func IsMaster(pt ParamType) bool {
|
||||
p, ok := pt.(MasterParamType)
|
||||
return ok && p.Master()
|
||||
}
|
||||
|
||||
// ParamTypeUnExpected is the unexpected parameter type.
|
||||
var ParamTypeUnExpected = ParamType{invalid: true}
|
||||
|
||||
func (pt ParamType) String() string {
|
||||
return pt.Indent
|
||||
// IsTrailing returns true if the "pt" param type is a marked as trailing,
|
||||
// which should accept more than one path segment when in the end.
|
||||
func IsTrailing(pt ParamType) bool {
|
||||
p, ok := pt.(TrailingParamType)
|
||||
return ok && p.Trailing()
|
||||
}
|
||||
|
||||
// Assignable returns true if the "k" standard type
|
||||
// is assignabled to this ParamType.
|
||||
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
||||
return pt.GoType == k
|
||||
// HasAlias returns any alias of the "pt" param type.
|
||||
// If alias is empty or not found then it returns false as its second output argument.
|
||||
func HasAlias(pt ParamType) (string, bool) {
|
||||
if p, ok := pt.(AliasParamType); ok {
|
||||
alias := p.Alias()
|
||||
return alias, len(alias) > 0
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetDefaultParamType accepts a list of ParamType and returns its default.
|
||||
// If no `Default` specified:
|
||||
// GetMasterParamType accepts a list of ParamType and returns its master.
|
||||
// If no `Master` specified:
|
||||
// and len(paramTypes) > 0 then it will return the first one,
|
||||
// otherwise it returns a "string" parameter type.
|
||||
func GetDefaultParamType(paramTypes ...ParamType) ParamType {
|
||||
// otherwise it returns nil.
|
||||
func GetMasterParamType(paramTypes ...ParamType) ParamType {
|
||||
for _, pt := range paramTypes {
|
||||
if pt.Default == true {
|
||||
if IsMaster(pt) {
|
||||
return pt
|
||||
}
|
||||
}
|
||||
@@ -46,24 +71,12 @@ func GetDefaultParamType(paramTypes ...ParamType) ParamType {
|
||||
return paramTypes[0]
|
||||
}
|
||||
|
||||
return ParamType{Indent: "string", GoType: reflect.String, Default: true}
|
||||
}
|
||||
|
||||
// ValidKind will return true if at least one param type is supported
|
||||
// for this std kind.
|
||||
func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool {
|
||||
for _, pt := range paramTypes {
|
||||
if pt.GoType == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupParamType accepts the string
|
||||
// representation of a parameter type.
|
||||
// Available:
|
||||
// Example:
|
||||
// "string"
|
||||
// "number" or "int"
|
||||
// "long" or "int64"
|
||||
@@ -73,41 +86,20 @@ func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool {
|
||||
// "alphabetical"
|
||||
// "file"
|
||||
// "path"
|
||||
func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) {
|
||||
func LookupParamType(indentOrAlias string, paramTypes ...ParamType) (ParamType, bool) {
|
||||
for _, pt := range paramTypes {
|
||||
if pt.Indent == indent {
|
||||
if pt.Indent() == indentOrAlias {
|
||||
return pt, true
|
||||
}
|
||||
|
||||
for _, alias := range pt.Aliases {
|
||||
if alias == indent {
|
||||
if alias, has := HasAlias(pt); has {
|
||||
if alias == indentOrAlias {
|
||||
return pt, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParamTypeUnExpected, false
|
||||
}
|
||||
|
||||
// LookupParamTypeFromStd accepts the string representation of a standard go type.
|
||||
// It returns a ParamType, but it may differs for example
|
||||
// the alphabetical, file, path and string are all string go types, so
|
||||
// make sure that caller resolves these types before this call.
|
||||
//
|
||||
// string matches to string
|
||||
// int matches to int/number
|
||||
// int64 matches to int64/long
|
||||
// uint64 matches to uint64
|
||||
// bool matches to bool/boolean
|
||||
func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) {
|
||||
goType = strings.ToLower(goType)
|
||||
for _, pt := range paramTypes {
|
||||
if strings.ToLower(pt.GoType.String()) == goType {
|
||||
return pt, true
|
||||
}
|
||||
}
|
||||
|
||||
return ParamTypeUnExpected, false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ParamStatement is a struct
|
||||
|
||||
@@ -2,7 +2,6 @@ package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -11,67 +10,12 @@ import (
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/token"
|
||||
)
|
||||
|
||||
var (
|
||||
// paramTypeString is the string type.
|
||||
// If parameter type is missing then it defaults to String type.
|
||||
// Allows anything
|
||||
// Declaration: /mypath/{myparam:string} or {myparam}
|
||||
paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true}
|
||||
// ParamTypeNumber is the integer, a number type.
|
||||
// Allows both positive and negative numbers, any number of digits.
|
||||
// Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility
|
||||
paramTypeNumber = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int}
|
||||
// ParamTypeInt64 is a number type.
|
||||
// Allows only -9223372036854775808 to 9223372036854775807.
|
||||
// Declaration: /mypath/{myparam:int64} or {myparam:long}
|
||||
paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64}
|
||||
// ParamTypeUint8 a number type.
|
||||
// Allows only 0 to 255.
|
||||
// Declaration: /mypath/{myparam:uint8}
|
||||
paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8}
|
||||
// ParamTypeUint64 a number type.
|
||||
// Allows only 0 to 18446744073709551615.
|
||||
// Declaration: /mypath/{myparam:uint64}
|
||||
paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64}
|
||||
// ParamTypeBool is the bool type.
|
||||
// Allows only "1" or "t" or "T" or "TRUE" or "true" or "True"
|
||||
// or "0" or "f" or "F" or "FALSE" or "false" or "False".
|
||||
// Declaration: /mypath/{myparam:bool} or {myparam:boolean}
|
||||
paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool}
|
||||
// ParamTypeAlphabetical is the alphabetical/letter type type.
|
||||
// Allows letters only (upper or lowercase)
|
||||
// Declaration: /mypath/{myparam:alphabetical}
|
||||
paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String}
|
||||
// ParamTypeFile is the file single path type.
|
||||
// Allows:
|
||||
// letters (upper or lowercase)
|
||||
// numbers (0-9)
|
||||
// underscore (_)
|
||||
// dash (-)
|
||||
// point (.)
|
||||
// no spaces! or other character
|
||||
// Declaration: /mypath/{myparam:file}
|
||||
paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String}
|
||||
// ParamTypePath is the multi path (or wildcard) type.
|
||||
// Allows anything, should be the last part
|
||||
// Declaration: /mypath/{myparam:path}
|
||||
paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true}
|
||||
)
|
||||
|
||||
// DefaultParamTypes are the built'n parameter types.
|
||||
var DefaultParamTypes = []ast.ParamType{
|
||||
paramTypeString,
|
||||
paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64,
|
||||
paramTypeBool,
|
||||
paramTypeAlphabetical, paramTypeFile, paramTypePath,
|
||||
}
|
||||
|
||||
// Parse takes a route "fullpath"
|
||||
// and returns its param statements
|
||||
// and an error on failure.
|
||||
func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) {
|
||||
// or an error if failed.
|
||||
func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) {
|
||||
if len(paramTypes) == 0 {
|
||||
paramTypes = DefaultParamTypes
|
||||
return nil, fmt.Errorf("empty parameter types")
|
||||
}
|
||||
|
||||
pathParts := strings.SplitN(fullpath, "/", -1)
|
||||
@@ -94,7 +38,7 @@ func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement,
|
||||
return nil, err
|
||||
}
|
||||
// if we have param type path but it's not the last path part
|
||||
if stmt.Type.End && i < len(pathParts)-1 {
|
||||
if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 {
|
||||
return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s)
|
||||
}
|
||||
|
||||
@@ -166,7 +110,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er
|
||||
|
||||
stmt := &ast.ParamStatement{
|
||||
ErrorCode: DefaultParamErrorCode,
|
||||
Type: ast.GetDefaultParamType(paramTypes...),
|
||||
Type: ast.GetMasterParamType(paramTypes...),
|
||||
Src: p.src,
|
||||
}
|
||||
|
||||
@@ -190,6 +134,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er
|
||||
// type can accept both letters and numbers but not symbols ofc.
|
||||
nextTok := l.NextToken()
|
||||
paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...)
|
||||
|
||||
if !found {
|
||||
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,44 @@ import (
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
)
|
||||
|
||||
type simpleParamType string
|
||||
|
||||
func (pt simpleParamType) Indent() string { return string(pt) }
|
||||
|
||||
type masterParamType simpleParamType
|
||||
|
||||
func (pt masterParamType) Indent() string { return string(pt) }
|
||||
func (pt masterParamType) Master() bool { return true }
|
||||
|
||||
type wildcardParamType string
|
||||
|
||||
func (pt wildcardParamType) Indent() string { return string(pt) }
|
||||
func (pt wildcardParamType) Trailing() bool { return true }
|
||||
|
||||
type aliasedParamType []string
|
||||
|
||||
func (pt aliasedParamType) Indent() string { return string(pt[0]) }
|
||||
func (pt aliasedParamType) Alias() string { return pt[1] }
|
||||
|
||||
var (
|
||||
paramTypeString = masterParamType("string")
|
||||
paramTypeNumber = aliasedParamType{"number", "int"}
|
||||
paramTypeInt64 = aliasedParamType{"int64", "long"}
|
||||
paramTypeUint8 = simpleParamType("uint8")
|
||||
paramTypeUint64 = simpleParamType("uint64")
|
||||
paramTypeBool = aliasedParamType{"bool", "boolean"}
|
||||
paramTypeAlphabetical = simpleParamType("alphabetical")
|
||||
paramTypeFile = simpleParamType("file")
|
||||
paramTypePath = wildcardParamType("path")
|
||||
)
|
||||
|
||||
var testParamTypes = []ast.ParamType{
|
||||
paramTypeString,
|
||||
paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64,
|
||||
paramTypeBool,
|
||||
paramTypeAlphabetical, paramTypeFile, paramTypePath,
|
||||
}
|
||||
|
||||
func TestParseParamError(t *testing.T) {
|
||||
// fail
|
||||
illegalChar := '$'
|
||||
@@ -16,7 +54,7 @@ func TestParseParamError(t *testing.T) {
|
||||
input := "{id" + string(illegalChar) + "int range(1,5) else 404}"
|
||||
p := NewParamParser(input)
|
||||
|
||||
_, err := p.Parse(DefaultParamTypes)
|
||||
_, err := p.Parse(testParamTypes)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expecting not empty error on input '%s'", input)
|
||||
@@ -32,7 +70,7 @@ func TestParseParamError(t *testing.T) {
|
||||
// success
|
||||
input2 := "{id:uint64 range(1,5) else 404}"
|
||||
p.Reset(input2)
|
||||
_, err = p.Parse(DefaultParamTypes)
|
||||
_, err = p.Parse(testParamTypes)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error())
|
||||
@@ -42,7 +80,7 @@ func TestParseParamError(t *testing.T) {
|
||||
|
||||
// mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type.
|
||||
func mustLookupParamType(indent string) ast.ParamType {
|
||||
pt, found := ast.LookupParamType(indent, DefaultParamTypes...)
|
||||
pt, found := ast.LookupParamType(indent, testParamTypes...)
|
||||
if !found {
|
||||
panic("param type '" + indent + "' is not part of the provided param types")
|
||||
}
|
||||
@@ -113,14 +151,14 @@ func TestParseParam(t *testing.T) {
|
||||
ast.ParamStatement{
|
||||
Src: "{myparam_:thisianunexpected}",
|
||||
Name: "myparam_",
|
||||
Type: ast.ParamTypeUnExpected,
|
||||
Type: nil,
|
||||
ErrorCode: 404,
|
||||
}}, // 5
|
||||
{true,
|
||||
ast.ParamStatement{
|
||||
Src: "{myparam2}",
|
||||
Name: "myparam2", // we now allow integers to the parameter names.
|
||||
Type: ast.GetDefaultParamType(DefaultParamTypes...),
|
||||
Type: ast.GetMasterParamType(testParamTypes...),
|
||||
ErrorCode: 404,
|
||||
}}, // 6
|
||||
{true,
|
||||
@@ -152,7 +190,7 @@ func TestParseParam(t *testing.T) {
|
||||
ast.ParamStatement{
|
||||
Src: "{id:long else 404}",
|
||||
Name: "id",
|
||||
Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType.
|
||||
Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType.
|
||||
ErrorCode: 404,
|
||||
}}, // 10
|
||||
{true,
|
||||
@@ -175,7 +213,7 @@ func TestParseParam(t *testing.T) {
|
||||
p := new(ParamParser)
|
||||
for i, tt := range tests {
|
||||
p.Reset(tt.expectedStatement.Src)
|
||||
resultStmt, err := p.Parse(DefaultParamTypes)
|
||||
resultStmt, err := p.Parse(testParamTypes)
|
||||
|
||||
if tt.valid && err != nil {
|
||||
t.Fatalf("tests[%d] - error %s", i, err.Error())
|
||||
@@ -216,7 +254,7 @@ func TestParse(t *testing.T) {
|
||||
}}, // 0
|
||||
{"/admin/{id:uint64 range(1,5)}", true,
|
||||
[]ast.ParamStatement{{
|
||||
Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int"
|
||||
Src: "{id:uint64 range(1,5)}",
|
||||
Name: "id",
|
||||
Type: paramTypeUint64,
|
||||
Funcs: []ast.ParamFunc{
|
||||
@@ -260,7 +298,7 @@ func TestParse(t *testing.T) {
|
||||
[]ast.ParamStatement{{
|
||||
Src: "{myparam_:thisianunexpected}",
|
||||
Name: "myparam_",
|
||||
Type: ast.ParamTypeUnExpected,
|
||||
Type: nil,
|
||||
ErrorCode: 404,
|
||||
},
|
||||
}}, // 5
|
||||
@@ -282,7 +320,7 @@ func TestParse(t *testing.T) {
|
||||
}}, // 7
|
||||
}
|
||||
for i, tt := range tests {
|
||||
statements, err := Parse(tt.path)
|
||||
statements, err := Parse(tt.path, testParamTypes)
|
||||
|
||||
if tt.valid && err != nil {
|
||||
t.Fatalf("tests[%d] - error %s", i, err.Error())
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
)
|
||||
|
||||
// EvaluatorFunc is the signature for both param types and param funcs.
|
||||
@@ -108,53 +106,78 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder {
|
||||
|
||||
// try to convert the string literal as we get it from the parser.
|
||||
var (
|
||||
v interface{}
|
||||
err error
|
||||
val interface{}
|
||||
|
||||
panicIfErr = func(err error) {
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("on field index: %d: %v", i, err))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// try to get the value based on the expected type.
|
||||
switch field.Kind() {
|
||||
case reflect.Int:
|
||||
v, err = strconv.Atoi(arg)
|
||||
v, err := strconv.Atoi(arg)
|
||||
panicIfErr(err)
|
||||
val = v
|
||||
case reflect.Int8:
|
||||
v, err = strconv.ParseInt(arg, 10, 8)
|
||||
v, err := strconv.ParseInt(arg, 10, 8)
|
||||
panicIfErr(err)
|
||||
val = int8(v)
|
||||
case reflect.Int16:
|
||||
v, err = strconv.ParseInt(arg, 10, 16)
|
||||
v, err := strconv.ParseInt(arg, 10, 16)
|
||||
panicIfErr(err)
|
||||
val = int16(v)
|
||||
case reflect.Int32:
|
||||
v, err = strconv.ParseInt(arg, 10, 32)
|
||||
v, err := strconv.ParseInt(arg, 10, 32)
|
||||
panicIfErr(err)
|
||||
val = int32(v)
|
||||
case reflect.Int64:
|
||||
v, err = strconv.ParseInt(arg, 10, 64)
|
||||
v, err := strconv.ParseInt(arg, 10, 64)
|
||||
panicIfErr(err)
|
||||
val = v
|
||||
case reflect.Uint8:
|
||||
v, err = strconv.ParseUint(arg, 10, 8)
|
||||
v, err := strconv.ParseUint(arg, 10, 8)
|
||||
panicIfErr(err)
|
||||
val = uint8(v)
|
||||
case reflect.Uint16:
|
||||
v, err = strconv.ParseUint(arg, 10, 16)
|
||||
v, err := strconv.ParseUint(arg, 10, 16)
|
||||
panicIfErr(err)
|
||||
val = uint16(v)
|
||||
case reflect.Uint32:
|
||||
v, err = strconv.ParseUint(arg, 10, 32)
|
||||
v, err := strconv.ParseUint(arg, 10, 32)
|
||||
panicIfErr(err)
|
||||
val = uint32(v)
|
||||
case reflect.Uint64:
|
||||
v, err = strconv.ParseUint(arg, 10, 64)
|
||||
v, err := strconv.ParseUint(arg, 10, 64)
|
||||
panicIfErr(err)
|
||||
val = v
|
||||
case reflect.Float32:
|
||||
v, err = strconv.ParseFloat(arg, 32)
|
||||
v, err := strconv.ParseFloat(arg, 32)
|
||||
panicIfErr(err)
|
||||
val = float32(v)
|
||||
case reflect.Float64:
|
||||
v, err = strconv.ParseFloat(arg, 64)
|
||||
v, err := strconv.ParseFloat(arg, 64)
|
||||
panicIfErr(err)
|
||||
val = v
|
||||
case reflect.Bool:
|
||||
v, err = strconv.ParseBool(arg)
|
||||
v, err := strconv.ParseBool(arg)
|
||||
panicIfErr(err)
|
||||
val = v
|
||||
case reflect.Slice:
|
||||
if len(arg) > 1 {
|
||||
if arg[0] == '[' && arg[len(arg)-1] == ']' {
|
||||
// it is a single argument but as slice.
|
||||
v = strings.Split(arg[1:len(arg)-1], ",") // only string slices.
|
||||
val = strings.Split(arg[1:len(arg)-1], ",") // only string slices.
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
v = arg
|
||||
val = arg
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("on field index: %d: %v", i, err))
|
||||
}
|
||||
|
||||
argValue := reflect.ValueOf(v)
|
||||
argValue := reflect.ValueOf(val)
|
||||
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))
|
||||
}
|
||||
@@ -190,6 +213,11 @@ type (
|
||||
// and it can register param functions
|
||||
// to that macro which maps to a parameter type.
|
||||
Macro struct {
|
||||
indent string
|
||||
alias string
|
||||
master bool
|
||||
trailing bool
|
||||
|
||||
Evaluator EvaluatorFunc
|
||||
funcs []ParamFunc
|
||||
}
|
||||
@@ -212,19 +240,51 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func newMacro(evaluator EvaluatorFunc) *Macro {
|
||||
return &Macro{Evaluator: evaluator}
|
||||
// NewMacro creates and returns a Macro that can be used as a registry for
|
||||
// a new customized parameter type and its functions.
|
||||
func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro {
|
||||
return &Macro{
|
||||
indent: indent,
|
||||
alias: alias,
|
||||
master: master,
|
||||
trailing: trailing,
|
||||
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Macro) Indent() string {
|
||||
return m.indent
|
||||
}
|
||||
|
||||
func (m *Macro) Alias() string {
|
||||
return m.alias
|
||||
}
|
||||
|
||||
func (m *Macro) Master() bool {
|
||||
return m.master
|
||||
}
|
||||
|
||||
func (m *Macro) Trailing() bool {
|
||||
return m.trailing
|
||||
}
|
||||
|
||||
// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro {
|
||||
// m.ParamResolver = fn
|
||||
// return m
|
||||
// }
|
||||
|
||||
// RegisterFunc registers a parameter function
|
||||
// to that macro.
|
||||
// Accepts the func name ("range")
|
||||
// and the function body, which should return an EvaluatorFunc
|
||||
// a bool (it will be converted to EvaluatorFunc later on),
|
||||
// i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){})
|
||||
func (m *Macro) RegisterFunc(funcName string, fn interface{}) {
|
||||
func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro {
|
||||
fullFn := convertBuilderFunc(fn)
|
||||
m.registerFunc(funcName, fullFn)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) {
|
||||
@@ -256,113 +316,3 @@ func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map contains the default macros mapped to their types.
|
||||
// This is the manager which is used by the caller to register custom
|
||||
// parameter functions per param-type (String, Int, Long, Boolean, Alphabetical, File, Path).
|
||||
type Map struct {
|
||||
// string type
|
||||
// anything
|
||||
String *Macro
|
||||
|
||||
// int type
|
||||
// both positive and negative numbers, any number of digits.
|
||||
Number *Macro
|
||||
// int64 as int64 type
|
||||
// -9223372036854775808 to 9223372036854775807.
|
||||
Int64 *Macro
|
||||
// uint8 as uint8 type
|
||||
// 0 to 255.
|
||||
Uint8 *Macro
|
||||
// uint64 as uint64 type
|
||||
// 0 to 18446744073709551615.
|
||||
Uint64 *Macro
|
||||
|
||||
// boolean as bool type
|
||||
// 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".
|
||||
Boolean *Macro
|
||||
// alphabetical/letter type
|
||||
// letters only (upper or lowercase)
|
||||
Alphabetical *Macro
|
||||
// file type
|
||||
// letters (upper or lowercase)
|
||||
// numbers (0-9)
|
||||
// underscore (_)
|
||||
// dash (-)
|
||||
// point (.)
|
||||
// no spaces! or other character
|
||||
File *Macro
|
||||
// path type
|
||||
// anything, should be the last part
|
||||
Path *Macro
|
||||
}
|
||||
|
||||
// NewMap returns a new macro Map with default
|
||||
// type evaluators.
|
||||
//
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
func NewMap() *Map {
|
||||
simpleNumberEvalutator := MustNewEvaluatorFromRegexp("^-?[0-9]+$")
|
||||
return &Map{
|
||||
// it allows everything, so no need for a regexp here.
|
||||
String: newMacro(func(string) bool { return true }),
|
||||
Number: newMacro(simpleNumberEvalutator), //"^(-?0\\.[0-9]*[1-9]+[0-9]*$)|(^-?[1-9]+[0-9]*((\\.[0-9]*[1-9]+[0-9]*$)|(\\.[0-9]+)))|(^-?[1-9]+[0-9]*$)|(^0$){1}")), //("^-?[0-9]+$")),
|
||||
Int64: newMacro(func(paramValue string) bool {
|
||||
if !simpleNumberEvalutator(paramValue) {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
// if err == strconv.ErrRange...
|
||||
return err == nil
|
||||
}), //("^-[1-9]|-?[1-9][0-9]{1,14}|-?1000000000000000|-?10000000000000000|-?100000000000000000|-?[1-9]000000000000000000|-?9[0-2]00000000000000000|-?92[0-2]0000000000000000|-?922[0-3]000000000000000|-?9223[0-3]00000000000000|-?92233[0-7]0000000000000|-?922337[0-2]000000000000|-?92233720[0-3]0000000000|-?922337203[0-6]000000000|-?9223372036[0-8]00000000|-?92233720368[0-5]0000000|-?922337203685[0-4]000000|-?9223372036854[0-7]00000|-?92233720368547[0-7]0000|-?922337203685477[0-5]000|-?922337203685477[56]000|[0-9]$")),
|
||||
Uint8: newMacro(MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")),
|
||||
Uint64: newMacro(func(paramValue string) bool {
|
||||
if !simpleNumberEvalutator(paramValue) {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
return err == nil
|
||||
}), //("^[0-9]|[1-9][0-9]{1,14}|1000000000000000|10000000000000000|100000000000000000|1000000000000000000|1[0-8]000000000000000000|18[0-4]00000000000000000|184[0-4]0000000000000000|1844[0-6]000000000000000|18446[0-7]00000000000000|184467[0-4]0000000000000|1844674[0-4]000000000000|184467440[0-7]0000000000|1844674407[0-3]000000000|18446744073[0-7]00000000|1844674407370000000[0-9]|18446744073709[0-5]00000|184467440737095[0-5]0000|1844674407370955[0-2]000$")),
|
||||
Boolean: newMacro(func(paramValue string) bool {
|
||||
// a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
|
||||
// in this case.
|
||||
_, err := strconv.ParseBool(paramValue)
|
||||
return err == nil
|
||||
}),
|
||||
Alphabetical: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")),
|
||||
File: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")),
|
||||
// it allows everything, we have String and Path as different
|
||||
// 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.
|
||||
// Should be the last.
|
||||
Path: newMacro(func(string) bool { return true }),
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns the specific Macro from the map
|
||||
// based on the parameter type.
|
||||
// i.e if ast.ParamTypeNumber then it will return the m.Number.
|
||||
// Returns the m.String if not matched.
|
||||
func (m *Map) Lookup(typ ast.ParamType) *Macro {
|
||||
switch typ {
|
||||
case ast.ParamTypeNumber:
|
||||
return m.Number
|
||||
case ast.ParamTypeInt64:
|
||||
return m.Int64
|
||||
case ast.ParamTypeUint8:
|
||||
return m.Uint8
|
||||
case ast.ParamTypeUint64:
|
||||
return m.Uint64
|
||||
case ast.ParamTypeBoolean:
|
||||
return m.Boolean
|
||||
case ast.ParamTypeAlphabetical:
|
||||
return m.Alphabetical
|
||||
case ast.ParamTypeFile:
|
||||
return m.File
|
||||
case ast.ParamTypePath:
|
||||
return m.Path
|
||||
default:
|
||||
return m.String
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,6 @@ func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bo
|
||||
}
|
||||
|
||||
func TestStringEvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -86,13 +84,11 @@ func TestStringEvaluatorRaw(t *testing.T) {
|
||||
} // 0
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.String, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, String, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberEvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -111,13 +107,11 @@ func TestNumberEvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.Number, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Number, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64EvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -138,13 +132,11 @@ func TestInt64EvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.Int64, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Int64, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint8EvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -169,13 +161,11 @@ func TestUint8EvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.Uint8, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64EvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -196,13 +186,11 @@ func TestUint64EvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.Uint64, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphabeticalEvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -215,13 +203,11 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.Alphabetical, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileEvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
tests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -234,13 +220,11 @@ func TestFileEvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
testEvaluatorRaw(t, f.File, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, File, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathEvaluatorRaw(t *testing.T) {
|
||||
f := NewMap()
|
||||
|
||||
pathTests := []struct {
|
||||
pass bool
|
||||
input string
|
||||
@@ -254,28 +238,10 @@ func TestPathEvaluatorRaw(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range pathTests {
|
||||
testEvaluatorRaw(t, f.Path, tt.input, tt.pass, i)
|
||||
testEvaluatorRaw(t, Path, tt.input, tt.pass, i)
|
||||
}
|
||||
}
|
||||
|
||||
// func TestMapRegisterFunc(t *testing.T) {
|
||||
// m := NewMap()
|
||||
// m.String.RegisterFunc("prefix", func(prefix string) EvaluatorFunc {
|
||||
// return func(paramValue string) bool {
|
||||
// return strings.HasPrefix(paramValue, prefix)
|
||||
// }
|
||||
// })
|
||||
|
||||
// p, err := Parse("/user/@iris")
|
||||
// if err != nil {
|
||||
// t.Fatalf(err)
|
||||
// }
|
||||
|
||||
// // p.Params = append(p.)
|
||||
|
||||
// testEvaluatorRaw(t, m.String, p.Src, false, 0)
|
||||
// }
|
||||
|
||||
func TestConvertBuilderFunc(t *testing.T) {
|
||||
fn := func(min uint64, slice []string) func(string) bool {
|
||||
return func(paramValue string) bool {
|
||||
|
||||
375
core/router/macro/macros.go
Normal file
375
core/router/macro/macros.go
Normal file
@@ -0,0 +1,375 @@
|
||||
package macro
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
// String type
|
||||
// Allows anything (single path segment, as everything except the `Path`).
|
||||
String = NewMacro("string", "", true, false, func(string) bool { return true }).
|
||||
RegisterFunc("regexp", func(expr string) EvaluatorFunc {
|
||||
return MustNewEvaluatorFromRegexp(expr)
|
||||
}).
|
||||
// checks if param value starts with the 'prefix' arg
|
||||
RegisterFunc("prefix", func(prefix string) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.HasPrefix(paramValue, prefix)
|
||||
}
|
||||
}).
|
||||
// checks if param value ends with the 'suffix' arg
|
||||
RegisterFunc("suffix", func(suffix string) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.HasSuffix(paramValue, suffix)
|
||||
}
|
||||
}).
|
||||
// checks if param value contains the 's' arg
|
||||
RegisterFunc("contains", func(s string) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return strings.Contains(paramValue, s)
|
||||
}
|
||||
}).
|
||||
// checks if param value's length is at least 'min'
|
||||
RegisterFunc("min", func(min int) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return len(paramValue) >= min
|
||||
}
|
||||
}).
|
||||
// checks if param value's length is not bigger than 'max'
|
||||
RegisterFunc("max", func(max int) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
return max >= len(paramValue)
|
||||
}
|
||||
})
|
||||
|
||||
simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$")
|
||||
// Number or int type
|
||||
// both positive and negative numbers, any number of digits.
|
||||
Number = NewMacro("number", "int", false, false, simpleNumberEvalutator).
|
||||
// checks if the param value's int representation is
|
||||
// bigger or equal than 'min'
|
||||
RegisterFunc("min", func(min int) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
}).
|
||||
// checks if the param value's int representation is
|
||||
// smaller or equal than 'max'.
|
||||
RegisterFunc("max", func(max int) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
}).
|
||||
// checks if the param value's int representation is
|
||||
// between min and max, including 'min' and 'max'.
|
||||
RegisterFunc("range", func(min, max int) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Int64 as int64 type
|
||||
// -9223372036854775808 to 9223372036854775807.
|
||||
Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool {
|
||||
if !simpleNumberEvalutator(paramValue) {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
// if err == strconv.ErrRange...
|
||||
return err == nil
|
||||
}).
|
||||
// checks if the param value's int64 representation is
|
||||
// bigger or equal than 'min'.
|
||||
RegisterFunc("min", func(min int64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
}).
|
||||
// checks if the param value's int64 representation is
|
||||
// smaller or equal than 'max'.
|
||||
RegisterFunc("max", func(max int64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
}).
|
||||
// checks if the param value's int64 representation is
|
||||
// between min and max, including 'min' and 'max'.
|
||||
RegisterFunc("range", func(min, max int64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseInt(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Uint8 as uint8 type
|
||||
// 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])$")).
|
||||
// checks if the param value's uint8 representation is
|
||||
// bigger or equal than 'min'.
|
||||
RegisterFunc("min", func(min uint8) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return uint8(n) >= min
|
||||
}
|
||||
}).
|
||||
// checks if the param value's uint8 representation is
|
||||
// smaller or equal than 'max'.
|
||||
RegisterFunc("max", func(max uint8) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return uint8(n) <= max
|
||||
}
|
||||
}).
|
||||
// checks if the param value's uint8 representation is
|
||||
// between min and max, including 'min' and 'max'.
|
||||
RegisterFunc("range", func(min, max uint8) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 8)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if v := uint8(n); v < min || v > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Uint64 as uint64 type
|
||||
// 0 to 18446744073709551615.
|
||||
Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool {
|
||||
if !simpleNumberEvalutator(paramValue) {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
return err == nil
|
||||
}).
|
||||
// checks if the param value's uint64 representation is
|
||||
// bigger or equal than 'min'.
|
||||
RegisterFunc("min", func(min uint64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= min
|
||||
}
|
||||
}).
|
||||
// checks if the param value's uint64 representation is
|
||||
// smaller or equal than 'max'.
|
||||
RegisterFunc("max", func(max uint64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n <= max
|
||||
}
|
||||
}).
|
||||
// checks if the param value's uint64 representation is
|
||||
// between min and max, including 'min' and 'max'.
|
||||
RegisterFunc("range", func(min, max uint64) EvaluatorFunc {
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.ParseUint(paramValue, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if n < min || n > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// Bool or boolean as bool type
|
||||
// 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".
|
||||
Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool {
|
||||
// a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
|
||||
// in this case.
|
||||
_, err := strconv.ParseBool(paramValue)
|
||||
return err == nil
|
||||
})
|
||||
|
||||
// Alphabetical letter type
|
||||
// letters only (upper or lowercase)
|
||||
Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$"))
|
||||
// File type
|
||||
// letters (upper or lowercase)
|
||||
// numbers (0-9)
|
||||
// underscore (_)
|
||||
// dash (-)
|
||||
// point (.)
|
||||
// no spaces! or other character
|
||||
File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$"))
|
||||
// Path type
|
||||
// anything, should be the last part
|
||||
//
|
||||
// It allows everything, we have String and Path as different
|
||||
// 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.
|
||||
// Should be living in the latest path segment of a route path.
|
||||
Path = NewMacro("path", "", false, true, func(string) bool { return true })
|
||||
|
||||
Defaults = &Macros{
|
||||
String,
|
||||
Number,
|
||||
Int64,
|
||||
Uint8,
|
||||
Uint64,
|
||||
Bool,
|
||||
Alphabetical,
|
||||
Path,
|
||||
}
|
||||
)
|
||||
|
||||
type Macros []*Macro
|
||||
|
||||
func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro {
|
||||
macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator)
|
||||
if ms.register(macro) {
|
||||
return macro
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *Macros) register(macro *Macro) bool {
|
||||
if macro.Indent() == "" || macro.Evaluator == nil {
|
||||
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 macro.Master() && m.Master() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cp = append(cp, macro)
|
||||
|
||||
*ms = cp
|
||||
return true
|
||||
}
|
||||
|
||||
func (ms *Macros) Unregister(indent string) bool {
|
||||
cp := *ms
|
||||
|
||||
for i, m := range cp {
|
||||
if m.Indent() == indent {
|
||||
copy(cp[i:], cp[i+1:])
|
||||
cp[len(cp)-1] = nil
|
||||
cp = cp[:len(cp)-1]
|
||||
|
||||
*ms = cp
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms *Macros) Lookup(pt ast.ParamType) *Macro {
|
||||
if m := ms.Get(pt.Indent()); m != nil {
|
||||
return m
|
||||
}
|
||||
|
||||
if alias, has := ast.HasAlias(pt); has {
|
||||
if m := ms.Get(alias); m != nil {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *Macros) Get(indentOrAlias string) *Macro {
|
||||
if indentOrAlias == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, m := range *ms {
|
||||
if m.Indent() == indentOrAlias {
|
||||
return m
|
||||
}
|
||||
|
||||
if m.Alias() == indentOrAlias {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *Macros) GetMaster() *Macro {
|
||||
for _, m := range *ms {
|
||||
if m.Master() {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *Macros) GetTrailings() (macros []*Macro) {
|
||||
for _, m := range *ms {
|
||||
if m.Trailing() {
|
||||
macros = append(macros, m)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -25,6 +25,7 @@ type TemplateParam struct {
|
||||
// it's useful on host to decide how to convert the path template to specific router's syntax
|
||||
Type ast.ParamType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Index int `json:"index"`
|
||||
ErrCode int `json:"errCode"`
|
||||
TypeEvaluator EvaluatorFunc `json:"-"`
|
||||
Funcs []EvaluatorFunc `json:"-"`
|
||||
@@ -34,15 +35,20 @@ type TemplateParam struct {
|
||||
// and returns a new Template.
|
||||
// It builds all the parameter functions for that template
|
||||
// and their evaluators, it's the api call that makes use the interpeter's parser -> lexer.
|
||||
func Parse(src string, macros *Map) (*Template, error) {
|
||||
params, err := parser.Parse(src)
|
||||
func Parse(src string, macros Macros) (*Template, error) {
|
||||
types := make([]ast.ParamType, len(macros))
|
||||
for i, m := range macros {
|
||||
types[i] = m
|
||||
}
|
||||
|
||||
params, err := parser.Parse(src, types)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := new(Template)
|
||||
t.Src = src
|
||||
|
||||
for _, p := range params {
|
||||
for idx, p := range params {
|
||||
funcMap := macros.Lookup(p.Type)
|
||||
typEval := funcMap.Evaluator
|
||||
|
||||
@@ -50,17 +56,23 @@ func Parse(src string, macros *Map) (*Template, error) {
|
||||
Src: p.Src,
|
||||
Type: p.Type,
|
||||
Name: p.Name,
|
||||
Index: idx,
|
||||
ErrCode: p.ErrorCode,
|
||||
TypeEvaluator: typEval,
|
||||
}
|
||||
|
||||
for _, paramfn := range p.Funcs {
|
||||
tmplFn := funcMap.getFunc(paramfn.Name)
|
||||
if tmplFn == nil { // if not find on this type, check for String's which is for global funcs too
|
||||
tmplFn = macros.String.getFunc(paramfn.Name)
|
||||
if tmplFn == nil { // if not found then just skip this param
|
||||
if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too.
|
||||
if m := macros.GetMaster(); m != nil {
|
||||
tmplFn = m.getFunc(paramfn.Name)
|
||||
}
|
||||
|
||||
if tmplFn == nil { // if not found then just skip this param.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
evalFn := tmplFn(paramfn.Args)
|
||||
if evalFn == nil {
|
||||
continue
|
||||
|
||||
@@ -18,11 +18,11 @@ type Party interface {
|
||||
GetRelPath() string
|
||||
// GetReporter returns the reporter for adding errors
|
||||
GetReporter() *errors.Reporter
|
||||
// Macros returns the macro map which is responsible
|
||||
// to register custom macro functions for all routes.
|
||||
// Macros returns the macro collection that is responsible
|
||||
// to register custom macros with their own parameter types and their macro functions for all routes.
|
||||
//
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
Macros() *macro.Map
|
||||
Macros() *macro.Macros
|
||||
|
||||
// Party groups routes which may have the same prefix and share same handlers,
|
||||
// returns that new rich subrouter.
|
||||
|
||||
@@ -39,7 +39,7 @@ type Route struct {
|
||||
// It parses the path based on the "macros",
|
||||
// handlers are being changed to validate the macros at serve time, if needed.
|
||||
func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
|
||||
handlers context.Handlers, macros *macro.Map) (*Route, error) {
|
||||
handlers context.Handlers, macros macro.Macros) (*Route, error) {
|
||||
|
||||
tmpl, err := macro.Parse(unparsedPath, macros)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user