mirror of
https://github.com/kataras/iris.git
synced 2025-01-24 03:01:03 +01:00
230 lines
5.1 KiB
Go
230 lines
5.1 KiB
Go
|
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package macro
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"regexp"
|
||
|
"unicode"
|
||
|
|
||
|
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||
|
)
|
||
|
|
||
|
// final evaluator signature for both param types and param funcs
|
||
|
type EvaluatorFunc func(paramValue string) bool
|
||
|
|
||
|
func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) {
|
||
|
if expr == "" {
|
||
|
return nil, fmt.Errorf("empty regex expression")
|
||
|
}
|
||
|
|
||
|
// add the last $ if missing (and not wildcard(?))
|
||
|
if i := expr[len(expr)-1]; i != '$' && i != '*' {
|
||
|
expr += "$"
|
||
|
}
|
||
|
|
||
|
r, err := regexp.Compile(expr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return r.MatchString, nil
|
||
|
}
|
||
|
|
||
|
func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc {
|
||
|
r, err := NewEvaluatorFromRegexp(expr)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false })
|
||
|
goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false }))
|
||
|
)
|
||
|
|
||
|
func goodParamFunc(typ reflect.Type) bool {
|
||
|
// should be a func
|
||
|
// which returns a func(string) bool
|
||
|
if typ.Kind() == reflect.Func {
|
||
|
if typ.NumOut() == 1 {
|
||
|
typOut := typ.Out(0)
|
||
|
if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// goodParamFuncName reports whether the function name is a valid identifier.
|
||
|
func goodParamFuncName(name string) bool {
|
||
|
if name == "" {
|
||
|
return false
|
||
|
}
|
||
|
// valid names are only letters and _
|
||
|
for _, r := range name {
|
||
|
switch {
|
||
|
case r == '_':
|
||
|
case !unicode.IsLetter(r):
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// the convertBuilderFunc return value is generating at boot time.
|
||
|
// convertFunc converts an interface to a valid full param function.
|
||
|
func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder {
|
||
|
|
||
|
typFn := reflect.TypeOf(fn)
|
||
|
if !goodParamFunc(typFn) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
numFields := typFn.NumIn()
|
||
|
|
||
|
return func(args []ast.ParamFuncArg) EvaluatorFunc {
|
||
|
if len(args) != numFields {
|
||
|
// no variadics support, for now.
|
||
|
panic("args should be the same len as numFields")
|
||
|
}
|
||
|
var argValues []reflect.Value
|
||
|
for i := 0; i < numFields; i++ {
|
||
|
field := typFn.In(i)
|
||
|
arg := args[i]
|
||
|
|
||
|
if field.Kind() != reflect.TypeOf(arg).Kind() {
|
||
|
panic("fields should have the same type")
|
||
|
}
|
||
|
|
||
|
argValues = append(argValues, reflect.ValueOf(arg))
|
||
|
}
|
||
|
|
||
|
evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface()
|
||
|
|
||
|
var evaluator EvaluatorFunc
|
||
|
// check for typed and not typed
|
||
|
if _v, ok := evalFn.(EvaluatorFunc); ok {
|
||
|
evaluator = _v
|
||
|
} else if _v, ok = evalFn.(func(string) bool); ok {
|
||
|
evaluator = _v
|
||
|
}
|
||
|
return func(paramValue string) bool {
|
||
|
return evaluator(paramValue)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type (
|
||
|
Macro struct {
|
||
|
Evaluator EvaluatorFunc
|
||
|
funcs []ParamFunc
|
||
|
}
|
||
|
|
||
|
ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc
|
||
|
|
||
|
ParamFunc struct {
|
||
|
Name string
|
||
|
Func ParamEvaluatorBuilder
|
||
|
}
|
||
|
)
|
||
|
|
||
|
func newMacro(evaluator EvaluatorFunc) *Macro {
|
||
|
return &Macro{Evaluator: evaluator}
|
||
|
}
|
||
|
|
||
|
// at boot time, per param
|
||
|
func (m *Macro) RegisterFunc(funcName string, fn interface{}) {
|
||
|
fullFn := convertBuilderFunc(fn)
|
||
|
m.registerFunc(funcName, fullFn)
|
||
|
}
|
||
|
|
||
|
func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) {
|
||
|
if !goodParamFuncName(funcName) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for _, fn := range m.funcs {
|
||
|
if fn.Name == funcName {
|
||
|
fn.Func = fullFn
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m.funcs = append(m.funcs, ParamFunc{
|
||
|
Name: funcName,
|
||
|
Func: fullFn,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder {
|
||
|
for _, fn := range m.funcs {
|
||
|
if fn.Name == funcName {
|
||
|
if fn.Func == nil {
|
||
|
continue
|
||
|
}
|
||
|
return fn.Func
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type MacroMap struct {
|
||
|
// string type
|
||
|
// anything
|
||
|
String *Macro
|
||
|
// int type
|
||
|
// only numbers (0-9)
|
||
|
Int *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
|
||
|
}
|
||
|
|
||
|
func NewMacroMap() *MacroMap {
|
||
|
return &MacroMap{
|
||
|
// it allows everything, so no need for a regexp here.
|
||
|
String: newMacro(func(string) bool { return true }),
|
||
|
Int: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")),
|
||
|
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 }),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *MacroMap) Lookup(typ ast.ParamType) *Macro {
|
||
|
switch typ {
|
||
|
case ast.ParamTypeInt:
|
||
|
return m.Int
|
||
|
case ast.ParamTypeAlphabetical:
|
||
|
return m.Alphabetical
|
||
|
case ast.ParamTypeFile:
|
||
|
return m.File
|
||
|
case ast.ParamTypePath:
|
||
|
return m.Path
|
||
|
default:
|
||
|
return m.String
|
||
|
}
|
||
|
}
|