lazyhandler/magic/extractors.go
2025-02-22 23:06:16 +08:00

170 lines
5.4 KiB
Go

package magic
import (
"log/slog"
"net/http"
"reflect"
"git.jeffthecoder.xyz/public/lazyhandler/util"
)
var (
extractors = make(map[reflect.Type]func(*http.Request) (any, error))
extractorsTakesResponseWriter = make(map[reflect.Type]func(http.ResponseWriter, *http.Request) (any, error))
)
type FromRequest interface {
FromRequest(*http.Request) error
}
type TakeResponseWriter interface {
TakeResponseWriter(http.ResponseWriter)
}
func RegisterExtractor(o any, extractor func(*http.Request) (any, error)) {
if t, ok := o.(reflect.Type); ok {
slog.With("type", t.String()).Debug("extractor type registered with reflect.Type")
extractors[t] = extractor
} else if v, ok := o.(reflect.Value); ok {
slog.With("type", v.Type().String(), "value", v.Interface()).Debug("extractor type registered with reflect.Value")
extractors[v.Type()] = extractor
} else {
t := reflect.TypeOf(o)
slog.With("type", t.String(), "value", o).Debug("extractor type registered with object")
extractors[t] = extractor
}
}
func RegisterExtractorGeneric[T any](extractor func(*http.Request) (any, error)) {
var pointerToT *T
RegisterExtractor(reflect.TypeOf(pointerToT).Elem(), extractor)
}
func RegisterExtractorThatTakesResponseWriter(o any, extractor func(http.ResponseWriter, *http.Request) (any, error)) {
if t, ok := o.(reflect.Type); ok {
slog.With("type", t.String()).Debug("extractor type registered with reflect.Type")
extractorsTakesResponseWriter[t] = extractor
} else if v, ok := o.(reflect.Value); ok {
slog.With("type", v.Type().String(), "value", v.Interface()).Debug("extractor type registered with reflect.Value")
extractorsTakesResponseWriter[v.Type()] = extractor
} else {
t := reflect.TypeOf(o)
slog.With("type", t.String(), "value", o).Debug("extractor type registered with object")
extractorsTakesResponseWriter[t] = extractor
}
}
func RegisterExtractorThatTakesResponseWriterGeneric[T any](extractor func(http.ResponseWriter, *http.Request) (any, error)) {
var pointerToT *T
RegisterExtractorThatTakesResponseWriter(reflect.TypeOf(pointerToT).Elem(), extractor)
}
func GetExtractor(t reflect.Type) (func(http.ResponseWriter, *http.Request) (any, error), bool) {
_, isTakeResponseWriter := util.Implements[TakeResponseWriter](t)
if t.Kind() == reflect.Pointer {
_, ptrIsTakeResponseWriter := util.Implements[TakeResponseWriter](t.Elem())
isTakeResponseWriter = isTakeResponseWriter || ptrIsTakeResponseWriter
}
if _, ok := util.Implements[FromRequest](t); ok {
if t.Kind() == reflect.Pointer {
// T.Implement(FromRequest) and T is Pointer
// create new actual T and call T.FromRequest on it
// if error happens, no error is returned. just return nil
return func(w http.ResponseWriter, r *http.Request) (any, error) {
// var t T
// return t, t.FromRequest(r)
z := reflect.New(t.Elem())
if isTakeResponseWriter {
z.MethodByName("TakeResponseWriter").Call([]reflect.Value{reflect.ValueOf(w)})
}
results := z.MethodByName("FromRequest").Call([]reflect.Value{reflect.ValueOf(r)})
if err, ok := results[0].Interface().(error); ok && err != nil {
if errResponse, ok := err.(ErrorResponse); ok {
return reflect.Zero(t).Interface(), errResponse
}
return reflect.Zero(t).Interface(), nil
}
return z.Interface(), nil
}, isTakeResponseWriter
}
// T.Implement(FromRequest) and T is not Pointer
// create zero T and call T.FromRequest directly on it
return func(w http.ResponseWriter, r *http.Request) (any, error) {
// var t T
// return t, t.FromRequest(r)
z := reflect.Zero(t)
if isTakeResponseWriter {
z.MethodByName("TakeResponseWriter").Call([]reflect.Value{reflect.ValueOf(w)})
}
results := z.MethodByName("FromRequest").Call([]reflect.Value{reflect.ValueOf(r)})
if err, ok := results[0].Interface().(error); ok {
return z.Interface(), err
}
return z.Interface(), nil
}, isTakeResponseWriter
} else if v, ok := util.PointerImplements[FromRequest](t); ok {
return func(w http.ResponseWriter, r *http.Request) (any, error) {
// var t T
// return t, t.FromRequest(r)
z := reflect.New(v.Type().Elem())
if isTakeResponseWriter {
z.MethodByName("TakeResponseWriter").Call([]reflect.Value{reflect.ValueOf(w)})
}
results := z.MethodByName("FromRequest").Call([]reflect.Value{reflect.ValueOf(r)})
if err := results[0].Interface(); err == nil {
return z.Elem().Interface(), nil
}
return z.Elem().Interface(), results[0].Interface().(error)
}, isTakeResponseWriter
}
if extractor, ok := extractors[t]; ok {
return func(_ http.ResponseWriter, r *http.Request) (any, error) {
return extractor(r)
}, false
}
if t.Kind() == reflect.Pointer {
if extractor, ok := extractors[t.Elem()]; ok {
return func(w http.ResponseWriter, r *http.Request) (any, error) {
v, err := extractor(r)
if err != nil {
return nil, err
}
return &v, nil
}, false
}
}
if extractor, ok := extractorsTakesResponseWriter[t]; ok {
return func(w http.ResponseWriter, r *http.Request) (any, error) {
return extractor(w, r)
}, true
}
if t.Kind() == reflect.Pointer {
if extractor, ok := extractorsTakesResponseWriter[t.Elem()]; ok {
return func(w http.ResponseWriter, r *http.Request) (any, error) {
v, err := extractor(w, r)
if err != nil {
return nil, err
}
return &v, nil
}, true
}
}
return nil, isTakeResponseWriter
}