mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
204 lines
4.9 KiB
Go
204 lines
4.9 KiB
Go
|
package context
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"reflect"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
// ErrInvalidArgs fires when the `Context.CallFunc`
|
||
|
// is called with invalid number of arguments.
|
||
|
var ErrInvalidArgs = errors.New("invalid arguments")
|
||
|
|
||
|
// Func represents a function registered by the Context.
|
||
|
// See its `buildMeta` and `call` internal methods.
|
||
|
type Func struct {
|
||
|
RegisterName string // the name of which this function is registered, for information only.
|
||
|
Raw interface{} // the Raw function, can be used for custom casting.
|
||
|
PersistenceArgs []interface{} // the persistence input arguments given on registration.
|
||
|
|
||
|
once sync.Once // guards build once, on first call.
|
||
|
// Available after the first call.
|
||
|
Meta *FuncMeta
|
||
|
}
|
||
|
|
||
|
func newFunc(name string, fn interface{}, persistenceArgs ...interface{}) *Func {
|
||
|
return &Func{
|
||
|
RegisterName: name,
|
||
|
Raw: fn,
|
||
|
PersistenceArgs: persistenceArgs,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FuncMeta holds the necessary information about a registered
|
||
|
// context function. Built once by the Func.
|
||
|
type FuncMeta struct {
|
||
|
Handler Handler // when it's just a handler.
|
||
|
HandlerWithErr func(*Context) error // when it's just a handler which returns an error.
|
||
|
RawFunc func() // when it's just a func.
|
||
|
RawFuncWithErr func() error // when it's just a func which returns an error.
|
||
|
RawFuncArgs func(...interface{})
|
||
|
RawFuncArgsWithErr func(...interface{}) error
|
||
|
|
||
|
Value reflect.Value
|
||
|
Type reflect.Type
|
||
|
ExpectedArgumentsLength int
|
||
|
PersistenceInputs []reflect.Value
|
||
|
AcceptsContext bool // the Context, if exists should be always first argument.
|
||
|
ReturnsError bool // when the function's last output argument is error.
|
||
|
}
|
||
|
|
||
|
func (f *Func) buildMeta() {
|
||
|
switch fn := f.Raw.(type) {
|
||
|
case Handler:
|
||
|
f.Meta = &FuncMeta{Handler: fn}
|
||
|
return
|
||
|
case func(*Context):
|
||
|
f.Meta = &FuncMeta{Handler: fn}
|
||
|
return
|
||
|
case func(*Context) error:
|
||
|
f.Meta = &FuncMeta{HandlerWithErr: fn}
|
||
|
return
|
||
|
case func():
|
||
|
f.Meta = &FuncMeta{RawFunc: fn}
|
||
|
return
|
||
|
case func() error:
|
||
|
f.Meta = &FuncMeta{RawFuncWithErr: fn}
|
||
|
return
|
||
|
case func(...interface{}):
|
||
|
f.Meta = &FuncMeta{RawFuncArgs: fn}
|
||
|
return
|
||
|
case func(...interface{}) error:
|
||
|
f.Meta = &FuncMeta{RawFuncArgsWithErr: fn}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fn := f.Raw
|
||
|
|
||
|
meta := FuncMeta{}
|
||
|
if val, ok := fn.(reflect.Value); ok {
|
||
|
meta.Value = val
|
||
|
} else {
|
||
|
meta.Value = reflect.ValueOf(fn)
|
||
|
}
|
||
|
|
||
|
meta.Type = meta.Value.Type()
|
||
|
|
||
|
if meta.Type.Kind() != reflect.Func {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
meta.ExpectedArgumentsLength = meta.Type.NumIn()
|
||
|
|
||
|
skipInputs := len(meta.PersistenceInputs)
|
||
|
if meta.ExpectedArgumentsLength > skipInputs {
|
||
|
meta.AcceptsContext = isContext(meta.Type.In(skipInputs))
|
||
|
}
|
||
|
|
||
|
if numOut := meta.Type.NumOut(); numOut > 0 {
|
||
|
// error should be the last output.
|
||
|
if isError(meta.Type.Out(numOut - 1)) {
|
||
|
meta.ReturnsError = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
persistenceArgs := f.PersistenceArgs
|
||
|
if len(persistenceArgs) > 0 {
|
||
|
inputs := make([]reflect.Value, 0, len(persistenceArgs))
|
||
|
for _, arg := range persistenceArgs {
|
||
|
if in, ok := arg.(reflect.Value); ok {
|
||
|
inputs = append(inputs, in)
|
||
|
} else {
|
||
|
inputs = append(inputs, reflect.ValueOf(in))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
meta.PersistenceInputs = inputs
|
||
|
}
|
||
|
|
||
|
f.Meta = &meta
|
||
|
}
|
||
|
|
||
|
func (f *Func) call(ctx *Context, args ...interface{}) ([]reflect.Value, error) {
|
||
|
f.once.Do(f.buildMeta)
|
||
|
meta := f.Meta
|
||
|
|
||
|
if meta.Handler != nil {
|
||
|
meta.Handler(ctx)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if meta.HandlerWithErr != nil {
|
||
|
return nil, meta.HandlerWithErr(ctx)
|
||
|
}
|
||
|
|
||
|
if meta.RawFunc != nil {
|
||
|
meta.RawFunc()
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if meta.RawFuncWithErr != nil {
|
||
|
return nil, meta.RawFuncWithErr()
|
||
|
}
|
||
|
|
||
|
if meta.RawFuncArgs != nil {
|
||
|
meta.RawFuncArgs(args...)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if meta.RawFuncArgsWithErr != nil {
|
||
|
return nil, meta.RawFuncArgsWithErr(args...)
|
||
|
}
|
||
|
|
||
|
inputs := make([]reflect.Value, 0, f.Meta.ExpectedArgumentsLength)
|
||
|
inputs = append(inputs, f.Meta.PersistenceInputs...)
|
||
|
if f.Meta.AcceptsContext {
|
||
|
inputs = append(inputs, reflect.ValueOf(ctx))
|
||
|
}
|
||
|
|
||
|
for _, arg := range args {
|
||
|
if in, ok := arg.(reflect.Value); ok {
|
||
|
inputs = append(inputs, in)
|
||
|
} else {
|
||
|
inputs = append(inputs, reflect.ValueOf(arg))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// keep it here, the inptus may contain the context.
|
||
|
if f.Meta.ExpectedArgumentsLength != len(inputs) {
|
||
|
return nil, ErrInvalidArgs
|
||
|
}
|
||
|
|
||
|
outputs := f.Meta.Value.Call(inputs)
|
||
|
return outputs, getError(outputs)
|
||
|
}
|
||
|
|
||
|
var contextType = reflect.TypeOf((*Context)(nil))
|
||
|
|
||
|
// isContext returns true if the "typ" is a type of Context.
|
||
|
func isContext(typ reflect.Type) bool {
|
||
|
return typ == contextType
|
||
|
}
|
||
|
|
||
|
var errTyp = reflect.TypeOf((*error)(nil)).Elem()
|
||
|
|
||
|
// isError returns true if "typ" is type of `error`.
|
||
|
func isError(typ reflect.Type) bool {
|
||
|
return typ.Implements(errTyp)
|
||
|
}
|
||
|
|
||
|
func getError(outputs []reflect.Value) error {
|
||
|
if n := len(outputs); n > 0 {
|
||
|
lastOut := outputs[n-1]
|
||
|
if isError(lastOut.Type()) {
|
||
|
if lastOut.IsNil() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return lastOut.Interface().(error)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|