gin/auto_binder.go
2024-01-01 12:52:53 +04:00

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)
}
}
}