feat: move capytalcode/project-comicverse/libs to ./groute
This commit is contained in:
215
groute/forms/forms.go
Normal file
215
groute/forms/forms.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/loreddev/x/groute/router/rerrors"
|
||||
)
|
||||
|
||||
type Unmarshaler interface {
|
||||
UnmarshalForm(r *http.Request) error
|
||||
}
|
||||
|
||||
func Unmarshal(r *http.Request, v any) (err error) {
|
||||
if u, ok := v.(Unmarshaler); ok {
|
||||
return u.UnmarshalForm(r)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(ErrReflectPanic, fmt.Errorf("Panic recovered: %#v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
rt := rv.Type()
|
||||
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
ft := rt.Field(i)
|
||||
fv := rv.FieldByName(ft.Name)
|
||||
|
||||
log.Print(ft.Name)
|
||||
|
||||
if !fv.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Support embedded fields
|
||||
if ft.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
var tv string
|
||||
if t := ft.Tag.Get("form"); t != "" {
|
||||
tv = t
|
||||
} else if t = ft.Tag.Get("query"); t != "" {
|
||||
tv = t
|
||||
} else {
|
||||
tv = ft.Name
|
||||
}
|
||||
|
||||
tvs := strings.Split(tv, ",")
|
||||
|
||||
name := tvs[0]
|
||||
required := false
|
||||
defaultv := ""
|
||||
|
||||
for _, v := range tvs {
|
||||
if v == "required" {
|
||||
required = true
|
||||
} else if strings.HasPrefix(v, "default=") {
|
||||
defaultv = strings.TrimPrefix(v, "default=")
|
||||
}
|
||||
}
|
||||
|
||||
qv := r.FormValue(name)
|
||||
if qv == "" {
|
||||
if defaultv != "" {
|
||||
qv = defaultv
|
||||
} else if required {
|
||||
return &ErrMissingRequiredValue{name}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := setFieldValue(fv, qv); errors.Is(err, &ErrInvalidValueType{}) {
|
||||
e, _ := err.(*ErrInvalidValueType)
|
||||
e.value = name
|
||||
return e
|
||||
} else if errors.Is(err, &ErrUnsuportedValueType{}) {
|
||||
e, _ := err.(*ErrUnsuportedValueType)
|
||||
e.value = name
|
||||
return e
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RerrUnsmarshal(err error) rerrors.RouteError {
|
||||
if e, ok := err.(*ErrMissingRequiredValue); ok {
|
||||
return rerrors.MissingParameters([]string{e.value})
|
||||
} else if e, ok := err.(*ErrInvalidValueType); ok {
|
||||
return rerrors.BadRequest(e.Error())
|
||||
} else {
|
||||
return rerrors.InternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setFieldValue(rv reflect.Value, v string) error {
|
||||
switch rv.Kind() {
|
||||
|
||||
case reflect.Pointer:
|
||||
return setFieldValue(rv.Elem(), v)
|
||||
|
||||
case reflect.String:
|
||||
rv.SetString(v)
|
||||
|
||||
case reflect.Bool:
|
||||
if cv, err := strconv.ParseBool(v); err != nil {
|
||||
return &ErrInvalidValueType{"bool", err, ""}
|
||||
} else {
|
||||
rv.SetBool(cv)
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if cv, err := strconv.Atoi(v); err != nil {
|
||||
return &ErrInvalidValueType{"int", err, ""}
|
||||
} else {
|
||||
rv.SetInt(int64(cv))
|
||||
}
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if cv, err := strconv.Atoi(v); err != nil {
|
||||
return &ErrInvalidValueType{"uint", err, ""}
|
||||
} else {
|
||||
rv.SetUint(uint64(cv))
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if cv, err := strconv.ParseFloat(v, 64); err != nil {
|
||||
return &ErrInvalidValueType{"float64", err, ""}
|
||||
} else {
|
||||
rv.SetFloat(cv)
|
||||
}
|
||||
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
if cv, err := strconv.ParseComplex(v, 128); err != nil {
|
||||
return &ErrInvalidValueType{"complex128", err, ""}
|
||||
} else {
|
||||
rv.SetComplex(cv)
|
||||
}
|
||||
|
||||
// TODO: Support strucys
|
||||
// TODO: Support slices
|
||||
// TODO: Support maps
|
||||
default:
|
||||
return &ErrUnsuportedValueType{
|
||||
[]string{
|
||||
"string",
|
||||
"bool",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"float32", "float64",
|
||||
"complex64", "complex64",
|
||||
},
|
||||
"",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ErrInvalidValueType struct {
|
||||
expected string
|
||||
err error
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrInvalidValueType) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Value \"%s\" is a invalid type, expected type \"%s\". Got err: %s",
|
||||
e.value,
|
||||
e.expected,
|
||||
e.err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
type ErrUnsuportedValueType struct {
|
||||
supported []string
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrUnsuportedValueType) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Value \"%s\" is a unsupported type, supported types are: \"%s\"",
|
||||
e.value,
|
||||
strings.Join(e.supported, ", "),
|
||||
)
|
||||
}
|
||||
|
||||
type ErrMissingRequiredValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (e ErrMissingRequiredValue) Error() string {
|
||||
return fmt.Sprintf("Required value \"%s\" missing from query", e.value)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrParseForm = errors.New("Failed to parse form from body or query parameters")
|
||||
ErrReflectPanic = errors.New("Reflect panic while trying to parse request")
|
||||
)
|
||||
Reference in New Issue
Block a user