Crow Crowcrow
d3c1898383
Co-authored-by: Crow Crowcrow <crow@fuyu.moe> Co-committed-by: Crow Crowcrow <crow@fuyu.moe>
171 lines
3.0 KiB
Go
171 lines
3.0 KiB
Go
package forms
|
|
|
|
import (
|
|
"encoding"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type DecodeError struct {
|
|
Field string
|
|
Type string
|
|
Value string
|
|
}
|
|
|
|
func (de DecodeError) Error() string {
|
|
return `Failed to decode "` + de.Value + `" into field ` + de.Field + ` of type ` + de.Type + `.`
|
|
}
|
|
|
|
type Values = map[string][]string
|
|
|
|
func Decode(form Values, v interface{}) error {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() != reflect.Ptr {
|
|
panic(`Input is not a pointer`)
|
|
}
|
|
|
|
return decode(form, rv.Elem(), ``)
|
|
}
|
|
|
|
func decode(form Values, rv reflect.Value, prefix string) error {
|
|
for i := 0; i < rv.NumField(); i++ {
|
|
ft, fv := rv.Type().Field(i), rv.Field(i)
|
|
|
|
if !unicode.IsUpper(rune(ft.Name[0])) {
|
|
continue
|
|
}
|
|
|
|
fe := ft.Type
|
|
if fe.Kind() == reflect.Ptr {
|
|
fe = fe.Elem()
|
|
}
|
|
|
|
if tu, ok := fv.Addr().Interface().(encoding.TextUnmarshaler); ok {
|
|
v, ok := form[getName(ft, prefix)]
|
|
if !ok || len(v) == 0 {
|
|
continue
|
|
}
|
|
|
|
err := tu.UnmarshalText([]byte(v[0]))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if fe.Kind() == reflect.Struct {
|
|
if fv.Kind() == reflect.Ptr {
|
|
found := false
|
|
for k, _ := range form {
|
|
if strings.HasPrefix(k, getPrefix(ft, prefix)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
|
|
fv.Set(reflect.New(ft.Type.Elem()))
|
|
|
|
fv = fv.Elem()
|
|
}
|
|
|
|
err := decode(form, fv, getPrefix(ft, prefix))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
fieldName := getName(ft, prefix)
|
|
v, ok := form[fieldName]
|
|
if !ok || len(v) == 0 {
|
|
continue
|
|
}
|
|
|
|
if fv.Kind() != reflect.Ptr {
|
|
fv = fv.Addr()
|
|
} else if fv.IsNil() {
|
|
fv.Set(reflect.New(ft.Type.Elem()))
|
|
}
|
|
|
|
err := setValue(fieldName, v, fv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setValue(fieldName string, values []string, fv reflect.Value) error {
|
|
if fv.Elem().Type().Kind() != reflect.Slice {
|
|
return parse(fieldName, values[0], fv)
|
|
}
|
|
|
|
slice := reflect.MakeSlice(fv.Elem().Type(), len(values), len(values))
|
|
val := reflect.New(slice.Type().Elem())
|
|
for i, v := range values {
|
|
err := parse(fieldName, v, val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
slice.Index(i).Set(val.Elem())
|
|
}
|
|
fv.Elem().Set(slice)
|
|
|
|
return nil
|
|
}
|
|
|
|
func parse(fieldName, v string, fv reflect.Value) error {
|
|
var err error
|
|
|
|
switch f := fv.Interface().(type) {
|
|
case *string:
|
|
*f = v
|
|
case *int:
|
|
*f, err = strconv.Atoi(v)
|
|
case *float32:
|
|
var f64 float64
|
|
f64, err = strconv.ParseFloat(v, 32)
|
|
*f = float32(f64)
|
|
case *float64:
|
|
*f, err = strconv.ParseFloat(v, 64)
|
|
}
|
|
|
|
if err != nil {
|
|
return DecodeError{fieldName, fv.Elem().Type().Kind().String(), v}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getPrefix(ft reflect.StructField, prefix string) string {
|
|
if ft.Anonymous {
|
|
return prefix
|
|
}
|
|
|
|
if prefix != `` {
|
|
prefix += `.`
|
|
}
|
|
|
|
return prefix + ft.Name
|
|
}
|
|
|
|
func getName(ft reflect.StructField, prefix string) string {
|
|
name := ft.Tag.Get(`form`)
|
|
if name == `` {
|
|
name = ft.Name
|
|
}
|
|
|
|
if prefix == `` {
|
|
return name
|
|
}
|
|
|
|
return prefix + `.` + name
|
|
}
|