mirror of
https://github.com/kataras/iris.git
synced 2025-12-24 05:17:03 +00:00
Update to 8.4.0 | New macro type, new high-optimized MVC features. Read HISTORY.md
Former-commit-id: b72a23ba063be60a9750c8b1b0df024b0c8ed549
This commit is contained in:
61
mvc/activator/methodfunc/func_caller.go
Normal file
61
mvc/activator/methodfunc/func_caller.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package methodfunc
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
// FuncCaller is responsible to call the controller's function
|
||||
// which is responsible
|
||||
// for that request for this http method.
|
||||
type FuncCaller interface {
|
||||
// MethodCall fires the actual handler.
|
||||
// The "ctx" is the current context, helps us to get any path parameter's values.
|
||||
//
|
||||
// The "f" is the controller's function which is responsible
|
||||
// for that request for this http method.
|
||||
// That function can accept one parameter.
|
||||
//
|
||||
// The default callers (and the only one for now)
|
||||
// are pre-calculated by the framework.
|
||||
MethodCall(ctx context.Context, f interface{})
|
||||
}
|
||||
|
||||
type callerFunc func(ctx context.Context, f interface{})
|
||||
|
||||
func (c callerFunc) MethodCall(ctx context.Context, f interface{}) {
|
||||
c(ctx, f)
|
||||
}
|
||||
|
||||
func resolveCaller(p pathInfo) callerFunc {
|
||||
// if it's standard `Get`, `Post` without parameters.
|
||||
if p.ParamType == "" {
|
||||
return func(ctx context.Context, f interface{}) {
|
||||
f.(func())()
|
||||
}
|
||||
}
|
||||
|
||||
// remember,
|
||||
// the router already checks for the correct type,
|
||||
// we did pre-calculate everything
|
||||
// and now we will pre-calculate the method caller itself as well.
|
||||
|
||||
if p.ParamType == paramTypeInt {
|
||||
return func(ctx context.Context, f interface{}) {
|
||||
paramValue, _ := ctx.Params().GetInt(paramName)
|
||||
f.(func(int))(paramValue)
|
||||
}
|
||||
}
|
||||
|
||||
if p.ParamType == paramTypeLong {
|
||||
return func(ctx context.Context, f interface{}) {
|
||||
paramValue, _ := ctx.Params().GetInt64(paramName)
|
||||
f.(func(int64))(paramValue)
|
||||
}
|
||||
}
|
||||
|
||||
// else it's string or path, both of them are simple strings.
|
||||
return func(ctx context.Context, f interface{}) {
|
||||
paramValue := ctx.Params().Get(paramName)
|
||||
f.(func(string))(paramValue)
|
||||
}
|
||||
}
|
||||
92
mvc/activator/methodfunc/func_info.go
Normal file
92
mvc/activator/methodfunc/func_info.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package methodfunc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var availableMethods = [...]string{
|
||||
"ANY", // will be registered using the `core/router#APIBuilder#Any`
|
||||
"ALL", // same as ANY
|
||||
"NONE", // offline route
|
||||
// valid http methods
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"CONNECT",
|
||||
"HEAD",
|
||||
"PATCH",
|
||||
"OPTIONS",
|
||||
"TRACE",
|
||||
}
|
||||
|
||||
// FuncInfo is part of the `TController`,
|
||||
// it contains the index for a specific http method,
|
||||
// taken from user's controller struct.
|
||||
type FuncInfo struct {
|
||||
// Name is the map function name.
|
||||
Name string
|
||||
// Trailing is not empty when the Name contains
|
||||
// characters after the titled method, i.e
|
||||
// if Name = Get -> empty
|
||||
// if Name = GetLogin -> Login
|
||||
// if Name = GetUserPost -> UserPost
|
||||
Trailing string
|
||||
|
||||
// The Type of the method, includes the receivers.
|
||||
Type reflect.Type
|
||||
|
||||
// Index is the index of this function inside the controller type.
|
||||
Index int
|
||||
// HTTPMethod is the original http method that this
|
||||
// function should be registered to and serve.
|
||||
// i.e "GET","POST","PUT"...
|
||||
HTTPMethod string
|
||||
}
|
||||
|
||||
// or resolve methods
|
||||
func fetchInfos(typ reflect.Type) (methods []FuncInfo) {
|
||||
// search the entire controller
|
||||
// for any compatible method function
|
||||
// and add that.
|
||||
|
||||
for i, n := 0, typ.NumMethod(); i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
name := m.Name
|
||||
|
||||
for _, method := range availableMethods {
|
||||
possibleMethodFuncName := methodTitle(method)
|
||||
|
||||
if strings.Index(name, possibleMethodFuncName) == 0 {
|
||||
trailing := ""
|
||||
// if has chars after the method itself
|
||||
if lname, lmethod := len(name), len(possibleMethodFuncName); lname > lmethod {
|
||||
ch := rune(name[lmethod])
|
||||
// if the next char is upper, otherise just skip the whole func info.
|
||||
if unicode.IsUpper(ch) {
|
||||
trailing = name[lmethod:]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
methodInfo := FuncInfo{
|
||||
Name: name,
|
||||
Trailing: trailing,
|
||||
Type: m.Type,
|
||||
HTTPMethod: method,
|
||||
Index: m.Index,
|
||||
}
|
||||
methods = append(methods, methodInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func methodTitle(httpMethod string) string {
|
||||
httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
|
||||
return httpMethodFuncName
|
||||
}
|
||||
119
mvc/activator/methodfunc/func_path.go
Normal file
119
mvc/activator/methodfunc/func_path.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package methodfunc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
by = "By"
|
||||
wildcard = "Wildcard"
|
||||
paramName = "param"
|
||||
)
|
||||
|
||||
type pathInfo struct {
|
||||
GoParamType string
|
||||
ParamType string
|
||||
RelPath string
|
||||
}
|
||||
|
||||
const (
|
||||
paramTypeInt = "int"
|
||||
paramTypeLong = "long"
|
||||
paramTypeString = "string"
|
||||
paramTypePath = "path"
|
||||
)
|
||||
|
||||
var macroTypes = map[string]string{
|
||||
"int": paramTypeInt,
|
||||
"int64": paramTypeLong,
|
||||
"string": paramTypeString,
|
||||
// there is "path" param type but it's being captured "on-air"
|
||||
// "file" param type is not supported by the current implementation, yet
|
||||
// but if someone ask for it I'll implement it, it's easy.
|
||||
}
|
||||
|
||||
func resolveRelativePath(info FuncInfo) (p pathInfo, ok bool) {
|
||||
if info.Trailing == "" {
|
||||
// it's valid
|
||||
// it's just don't have a relative path,
|
||||
// therefore p.RelPath will be empty, as we want.
|
||||
return p, true
|
||||
}
|
||||
|
||||
var (
|
||||
typ = info.Type
|
||||
tr = info.Trailing
|
||||
relPath = resolvePathFromFunc(tr)
|
||||
|
||||
goType, paramType string
|
||||
)
|
||||
|
||||
byKeywordIdx := strings.LastIndex(tr, by)
|
||||
if byKeywordIdx != -1 && typ.NumIn() == 2 { // first is the struct receiver.
|
||||
funcPath := tr[0:byKeywordIdx] // remove the "By"
|
||||
goType = typ.In(1).Name()
|
||||
afterBy := byKeywordIdx + len(by)
|
||||
if len(tr) > afterBy {
|
||||
if tr[afterBy:] == wildcard {
|
||||
paramType = paramTypePath
|
||||
} else {
|
||||
// invalid syntax
|
||||
return p, false
|
||||
}
|
||||
} else {
|
||||
// it's not wildcard, so check base on our available macro types.
|
||||
if paramType, ok = macroTypes[goType]; !ok {
|
||||
// ivalid type
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
|
||||
// int and string are supported.
|
||||
// as there is no way to get the parameter name
|
||||
// we will use the "param" everywhere.
|
||||
suffix := fmt.Sprintf("/{%s:%s}", paramName, paramType)
|
||||
relPath = resolvePathFromFunc(funcPath) + suffix
|
||||
}
|
||||
|
||||
// if GetSomething/PostSomething/PutSomething...
|
||||
// we will not check for "Something" because we could
|
||||
// occur unexpected behaviors to the existing users
|
||||
// who using exported functions for controller's internal
|
||||
// functionalities and not for serving a request path.
|
||||
|
||||
return pathInfo{
|
||||
GoParamType: goType,
|
||||
ParamType: paramType,
|
||||
RelPath: relPath,
|
||||
}, true
|
||||
}
|
||||
|
||||
func resolvePathFromFunc(funcName string) string {
|
||||
end := len(funcName)
|
||||
start := -1
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for i, n := 0, end; i < n; i++ {
|
||||
c := rune(funcName[i])
|
||||
if unicode.IsUpper(c) {
|
||||
// it doesn't count the last uppercase
|
||||
if start != -1 {
|
||||
end = i
|
||||
s := "/" + strings.ToLower(funcName[start:end])
|
||||
buf.WriteString(s)
|
||||
}
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
end = i + 1
|
||||
}
|
||||
|
||||
if end > 0 && len(funcName) >= end {
|
||||
buf.WriteString("/" + strings.ToLower(funcName[start:end]))
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
35
mvc/activator/methodfunc/methodfunc.go
Normal file
35
mvc/activator/methodfunc/methodfunc.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package methodfunc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// MethodFunc the handler function.
|
||||
type MethodFunc struct {
|
||||
FuncInfo
|
||||
FuncCaller
|
||||
RelPath string
|
||||
}
|
||||
|
||||
// Resolve returns all the method funcs
|
||||
// necessary information and actions to
|
||||
// perform the request.
|
||||
func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) {
|
||||
infos := fetchInfos(typ)
|
||||
for _, info := range infos {
|
||||
p, ok := resolveRelativePath(info)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
caller := resolveCaller(p)
|
||||
methodFunc := MethodFunc{
|
||||
RelPath: p.RelPath,
|
||||
FuncInfo: info,
|
||||
FuncCaller: caller,
|
||||
}
|
||||
|
||||
methodFuncs = append(methodFuncs, methodFunc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user