forms/forms.go

167 lines
2.9 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
}
tu.UnmarshalText([]byte(v[0]))
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
}