initial commit
This commit is contained in:
199
inject.go
Normal file
199
inject.go
Normal file
@ -0,0 +1,199 @@
|
||||
package lazyhandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/magic"
|
||||
"git.jeffthecoder.xyz/public/lazyhandler/util"
|
||||
)
|
||||
|
||||
type ErrArgumentIsNotExtractable int
|
||||
|
||||
func (err ErrArgumentIsNotExtractable) Error() string {
|
||||
return fmt.Sprintf("argument %v is not extractable", int(err))
|
||||
}
|
||||
|
||||
type ErrReturnValueNotConvertableIntoResponsePart int
|
||||
|
||||
func (err ErrReturnValueNotConvertableIntoResponsePart) Error() string {
|
||||
return fmt.Sprintf("return value %v is not convertable into response part", int(err))
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotAFunc = errors.New("not a function")
|
||||
ErrDuplicateResponseWriterExtractor = errors.New("duplicate response writer extractor")
|
||||
ErrResponseWriterCannotBeExtracted = errors.New("http.ResponseWriter extractor must not exists if function has return value")
|
||||
)
|
||||
|
||||
func canConvert[T any](o any) bool {
|
||||
t := reflect.TypeOf((*T)(nil)).Elem()
|
||||
if reflectValue, ok := o.(reflect.Value); ok {
|
||||
return reflectValue.CanConvert(t)
|
||||
}
|
||||
if reflectType, ok := o.(reflect.Type); ok {
|
||||
return reflectType.ConvertibleTo(t)
|
||||
}
|
||||
return reflect.ValueOf(o).CanConvert(t)
|
||||
}
|
||||
|
||||
func MagicHandler(fn any) (http.Handler, error) {
|
||||
funcValue := reflect.ValueOf(fn)
|
||||
t := funcValue.Type()
|
||||
|
||||
if t.Kind() != reflect.Func {
|
||||
return nil, ErrNotAFunc
|
||||
}
|
||||
|
||||
responseWriterExtracted := -1
|
||||
|
||||
extractors := make([]func(http.ResponseWriter, *http.Request) (any, error), t.NumIn())
|
||||
for idx := 0; idx < t.NumIn(); idx++ {
|
||||
in := t.In(idx)
|
||||
|
||||
extractor, isTakeResponseWriter := magic.GetExtractor(in)
|
||||
if extractor == nil {
|
||||
return nil, ErrArgumentIsNotExtractable(idx)
|
||||
}
|
||||
if isTakeResponseWriter {
|
||||
if responseWriterExtracted >= 0 {
|
||||
return nil, ErrDuplicateResponseWriterExtractor
|
||||
}
|
||||
responseWriterExtracted = idx
|
||||
}
|
||||
extractors[idx] = extractor
|
||||
}
|
||||
|
||||
// http.ResponseWriter extractor must not exists if function has return value
|
||||
if responseWriterExtracted >= 0 && t.NumOut() > 0 {
|
||||
return nil, ErrResponseWriterCannotBeExtracted
|
||||
}
|
||||
|
||||
for idx := 0; idx < t.NumOut(); idx++ {
|
||||
out := t.Out(idx)
|
||||
|
||||
// int(status) || string(body)
|
||||
if out.Kind() == reflect.Int || out.Kind() == reflect.String {
|
||||
continue
|
||||
}
|
||||
|
||||
// []byte(body)
|
||||
if out.Kind() == reflect.Slice && out.Elem().Kind() == reflect.Uint8 {
|
||||
continue
|
||||
}
|
||||
|
||||
// [T] map[string]T(header)
|
||||
if out.Kind() == reflect.Map && out.Key().Kind() == reflect.String {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := util.Implements[io.Reader](out); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := util.Implements[magic.RespondWriter](out); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// last is error
|
||||
if _, ok := util.Implements[error](out); ok && idx == t.NumOut()-1 {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, ErrReturnValueNotConvertableIntoResponsePart(idx)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var closers []io.Closer
|
||||
defer func() {
|
||||
for _, closer := range closers {
|
||||
closer.Close()
|
||||
}
|
||||
}()
|
||||
in := make([]reflect.Value, len(extractors))
|
||||
for idx, extractor := range extractors {
|
||||
v, err := extractor(w, r)
|
||||
if err != nil {
|
||||
if errResponse, ok := err.(magic.ErrorResponse); ok {
|
||||
errResponse.WriteResponse(w)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(magic.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
if closer, ok := v.(io.Closer); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
in[idx] = reflect.ValueOf(v)
|
||||
}
|
||||
values := funcValue.Call(in)
|
||||
|
||||
if numValues := len(values); numValues > 0 {
|
||||
lastValue := values[numValues-1].Interface()
|
||||
if err, isError := lastValue.(error); isError {
|
||||
if err != nil {
|
||||
if errResponse, ok := lastValue.(magic.ErrorResponse); ok {
|
||||
errResponse.WriteResponse(w)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(magic.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
values = values[:numValues-1]
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
obj := value.Interface()
|
||||
|
||||
if value.Kind() == reflect.Int {
|
||||
w.WriteHeader(int(value.Int()))
|
||||
} else if value.Kind() == reflect.String {
|
||||
w.Write([]byte(value.String()))
|
||||
} else if canConvert[[]byte](value) {
|
||||
w.Write(value.Bytes())
|
||||
} else if reader, ok := util.Implements[io.Reader](value); ok {
|
||||
io.Copy(w, reader)
|
||||
} else if responseWriter, ok := util.Implements[magic.RespondWriter](value); ok {
|
||||
if responseWriter == nil {
|
||||
continue
|
||||
}
|
||||
responseWriter.WriteResponse(w)
|
||||
} else if headers, ok := obj.(http.Header); ok { // not
|
||||
for name, values := range headers {
|
||||
for idx, value := range values {
|
||||
if idx == 0 {
|
||||
w.Header().Set(name, value)
|
||||
} else {
|
||||
w.Header().Add(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if value.Kind() == reflect.Map {
|
||||
for _, key := range value.MapKeys() {
|
||||
value := value.MapIndex(key).Interface()
|
||||
w.Header().Set(key.String(), fmt.Sprint(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func MustMagicHandler(fn any) http.Handler {
|
||||
if handler, err := MagicHandler(fn); err != nil {
|
||||
panic("can not be converted to http.Handler: " + fmt.Sprintf("%T", fn) + ": " + err.Error())
|
||||
} else {
|
||||
return handler
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user