mirror of
https://github.com/gin-gonic/gin.git
synced 2025-04-06 03:57:46 +08:00
115 lines
2.5 KiB
Go
115 lines
2.5 KiB
Go
package gin
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
var (
|
|
defaultAutoBinderErrorHandler = func(ctx *Context, err error) {
|
|
ctx.Error(err)
|
|
ctx.Abort()
|
|
}
|
|
)
|
|
|
|
type binderType func(obj any) error
|
|
|
|
func isFunc(obj any) bool {
|
|
return reflect.TypeOf(obj).Kind() == reflect.Func
|
|
}
|
|
|
|
func isGinContext(rt reflect.Type) bool {
|
|
return rt == reflect.TypeOf((*Context)(nil))
|
|
}
|
|
|
|
func isPtr(rt reflect.Type) bool {
|
|
return rt.Kind() == reflect.Pointer
|
|
}
|
|
|
|
func isStruct(rt reflect.Type) bool {
|
|
return rt.Kind() == reflect.Struct
|
|
}
|
|
|
|
func constructStruct(prt reflect.Type, binder binderType) (reflect.Value, error) {
|
|
var pInstancePtr any
|
|
|
|
if isPtr(prt) {
|
|
pInstancePtr = reflect.New(prt.Elem()).Interface()
|
|
} else {
|
|
pInstancePtr = reflect.New(prt).Interface()
|
|
}
|
|
|
|
if err := binder(pInstancePtr); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
|
|
if prt.Kind() == reflect.Pointer {
|
|
return reflect.ValueOf(pInstancePtr), nil
|
|
}
|
|
|
|
return reflect.ValueOf(pInstancePtr).Elem(), nil
|
|
}
|
|
|
|
func callHandler(rt reflect.Type, rv reflect.Value, ctx *Context, binder binderType) error {
|
|
numberOfParams := rt.NumIn()
|
|
|
|
var args []reflect.Value
|
|
|
|
for i := 0; i < numberOfParams; i++ {
|
|
prt := rt.In(i)
|
|
|
|
if isGinContext(prt) {
|
|
args = append(args, reflect.ValueOf(ctx))
|
|
continue
|
|
}
|
|
|
|
if isStruct(prt) || isStruct(prt.Elem()) {
|
|
if prv, err := constructStruct(prt, binder); err != nil {
|
|
return err
|
|
} else {
|
|
args = append(args, prv)
|
|
}
|
|
}
|
|
}
|
|
|
|
rv.Call(args)
|
|
|
|
return nil
|
|
}
|
|
|
|
// AutoBinder is a handler wrapper that binds the actual handler's request.
|
|
//
|
|
// Example: func MyGetHandler(ctx *gin.Context, request *MyRequest) {}
|
|
//
|
|
// engine.GET("/endpoint", gin.AutoBinder(MyGetHandler)) and you can handel the errors by passing a handler
|
|
//
|
|
// engine.GET("/endpoint", gin.AutoBinder(MyGetHandler, func(ctx *gin.Context, err error) {}))
|
|
func AutoBinder(handler any, errorHandler ...func(*Context, error)) HandlerFunc {
|
|
rt := reflect.TypeOf(handler)
|
|
|
|
if rt.Kind() != reflect.Func {
|
|
panic(errors.New("invalid handler type"))
|
|
}
|
|
|
|
if rt.NumIn() == 0 {
|
|
panic(fmt.Errorf("handler should have at least one parameter, handler: %v", rt.Name()))
|
|
}
|
|
|
|
return func(ctx *Context) {
|
|
selectedErrorHandler := defaultAutoBinderErrorHandler
|
|
if len(errorHandler) > 0 && errorHandler[0] != nil {
|
|
selectedErrorHandler = errorHandler[0]
|
|
}
|
|
|
|
rt := reflect.TypeOf(handler)
|
|
rv := reflect.ValueOf(handler)
|
|
|
|
if err := callHandler(rt, rv, ctx, func(obj any) error {
|
|
return ctx.ShouldBind(obj)
|
|
}); err != nil {
|
|
selectedErrorHandler(ctx, err)
|
|
}
|
|
}
|
|
}
|