iris/core/router/macro.go
Gerasimos (Makis) Maropoulos d6d27b2605 Conversion once at macros and their functions, internal changes required
Former-commit-id: 7b778cccfb7c0e30ca5e8106017ada065993aba5
2018-09-27 03:17:45 +03:00

123 lines
3.8 KiB
Go

package router
import (
"fmt"
"net/http"
"reflect"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path
// and the new handlers (prepend all the macro's handler, if any).
//
// It's not exported for direct use.
func compileRoutePathAndHandlers(handlers context.Handlers, tmpl *macro.Template) (string, context.Handlers, error) {
// parse the path to node's path, now.
path, err := convertTmplToNodePath(tmpl)
if err != nil {
return tmpl.Src, handlers, err
}
// prepend the macro handler to the route, now,
// right before the register to the tree, so routerbuilder.UseGlobal will work as expected.
if len(tmpl.Params) > 0 {
macroEvaluatorHandler := convertTmplToHandler(tmpl)
// may return nil if no really need a macro handler evaluator
if macroEvaluatorHandler != nil {
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
}
}
return path, handlers, nil
}
func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
routePath := tmpl.Src
if len(tmpl.Params) > 0 {
if routePath[len(routePath)-1] == '/' {
routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's
}
}
// if it has started with {} and it's valid
// then the tmpl.Params will be filled,
// so no any further check needed
for i, p := range tmpl.Params {
if ast.IsTrailing(p.Type) {
if i != len(tmpl.Params)-1 {
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 {
routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1)
}
}
return routePath, nil
}
// 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
// check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params.
// 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 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound {
} else {
// println("we need handler for: " + tmpl.Src)
needMacroHandler = true
}
}
if !needMacroHandler {
// println("we don't need handler for: " + tmpl.Src)
return nil
}
return func(tmpl macro.Template) context.Handler {
return func(ctx context.Context) {
for _, p := range tmpl.Params {
paramValue := ctx.Params().Get(p.Name)
if p.TypeEvaluator == nil {
// allow.
ctx.Next()
return
}
// first, check for type evaluator.
newValue, passed := p.TypeEvaluator(paramValue)
if !passed {
ctx.StatusCode(p.ErrCode)
ctx.StopExecution()
return
}
if len(p.Funcs) > 0 {
paramIn := []reflect.Value{reflect.ValueOf(newValue)}
// then check for all of its functions
for _, evalFunc := range p.Funcs {
// or make it as func(interface{}) bool and pass directly the "newValue"
// but that would not be as easy for end-developer, so keep that "slower":
if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool
ctx.StatusCode(p.ErrCode)
ctx.StopExecution()
return
}
}
}
ctx.Params().Store.Set(p.Name, newValue)
}
// if all passed, just continue.
ctx.Next()
}
}(*tmpl)
}