1
0
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:
Gerasimos (Makis) Maropoulos
2018-09-26 11:37:11 +03:00
parent 52a07df0f4
commit dc3c38b189
26 changed files with 1070 additions and 1036 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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