form/form.go

228 lines
5.7 KiB
Go

// Package form implements encoding and decoding of urlencoded form. The mapping
// between form and Go values is described by `form:"query_name"` struct tags.
package form
import (
"bytes"
"fmt"
"io"
"net/url"
"reflect"
"strconv"
"strings"
)
type (
// Unmarshaler is the interface implemented by types that can unmarshal
// a form description of themselves. The input can be assumed to be a
// valid encoding of a form value. UnmarshalForm must copy the form data
// if it wishes to retain the data after returning.
Unmarshaler interface {
UnmarshalForm(v []byte) error
}
Decoder struct {
args url.Values
tag string
}
)
const (
tagIgnore = "-"
tagOmitempty = "omitempty"
methodName = "UnmarshalForm"
)
func NewDecoder(r io.Reader) *Decoder {
buf := new(bytes.Buffer)
defer buf.Reset()
_, _ = buf.ReadFrom(r)
args, _ := url.ParseQuery(buf.String())
return &Decoder{
tag: "form",
args: args,
}
}
// Unmarshal parses the form-encoded data and stores the result in the value
// pointed to by v. If v is nil or not a pointer, Unmarshal returns error.
//
// Unmarshal uses the reflection, allocating maps, slices, and pointers as
// necessary, with the following additional rules:
//
// To unmarshal form into a pointer, Unmarshal first handles the case of the
// form being the form literal null. In that case, Unmarshal sets the pointer to
// nil. Otherwise, Unmarshal unmarshals the form into the value pointed at by
// the pointer. If the pointer is nil, Unmarshal allocates a new value for it to
// point to.
//
// To unmarshal form into a value implementing the Unmarshaler interface,
// Unmarshal calls that value's UnmarshalForm method, including when the input
// is a form null.
//
// To unmarshal form into a struct, Unmarshal matches incoming object keys to
// the keys (either the struct field name or its tag), preferring an exact match
// but also accepting a case-insensitive match. By default, object keys which
// don't have a corresponding struct field are ignored.
func Unmarshal(data []byte, v any) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
func (d Decoder) Decode(dst any) (err error) {
src := reflect.ValueOf(dst)
if !src.IsValid() || src.Kind() != reflect.Pointer || src.Elem().Kind() != reflect.Struct {
return &InvalidUnmarshalError{
Type: reflect.TypeOf(dst),
}
}
defer func() {
if r := recover(); r != nil {
if ve, ok := r.(*reflect.ValueError); ok {
err = fmt.Errorf("recovered: %w", ve)
} else {
panic(r)
}
}
}()
return d.decode("", src, "")
}
func (d Decoder) decode(key string, dst reflect.Value, opts tagOptions) error {
src := d.args
if keyIndex := strings.LastIndex(key, ","); keyIndex != -1 {
if index, err := strconv.Atoi(key[keyIndex+1:]); err == nil {
key = key[:keyIndex]
src = make(url.Values)
src.Set(key, d.args[key][index])
}
}
switch dst.Kind() {
case reflect.Bool:
out, err := strconv.ParseBool(src.Get(key))
if err != nil {
return err
}
dst.SetBool(out)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
out, err := strconv.ParseInt(src.Get(key), 10, 64)
if err != nil {
return err
}
dst.SetInt(out)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
out, err := strconv.ParseUint(src.Get(key), 10, 64)
if err != nil {
return err
}
dst.SetUint(out)
case reflect.Float32, reflect.Float64:
out, err := strconv.ParseFloat(src.Get(key), 64)
if err != nil {
return err
}
dst.SetFloat(out)
// case reflect.Array: // TODO(toby3d)
// case reflect.Interface: // TODO(toby3d)
case reflect.Slice:
// NOTE(toby3d): copy raw []byte value as is
if dst.Type().Elem().Kind() == reflect.Uint8 {
dst.SetBytes([]byte(src.Get(key)))
return nil
}
// NOTE(toby3d): if contains UnmarshalForm method
for i := 0; i < dst.Addr().NumMethod(); i++ {
if dst.Addr().Type().Method(i).Name != methodName {
continue
}
in := make([]reflect.Value, 1)
in[0] = reflect.ValueOf([]byte(src.Get(key)))
out := dst.Addr().Method(i).Call(in)
if len(out) > 0 && out[0].Interface() != nil && !opts.Contains(tagOmitempty) {
return out[0].Interface().(error)
}
return nil
}
if dst.IsNil() {
slice := d.args[key]
dst.Set(reflect.MakeSlice(dst.Type(), len(slice), cap(slice)))
}
for i := 0; i < dst.Len(); i++ {
if err := d.decode(fmt.Sprintf("%s,%d", key, i), dst.Index(i), ""); err != nil {
return err
}
}
case reflect.String:
dst.SetString(string(src.Get(key)))
case reflect.Pointer:
if dst.IsNil() {
dst.Set(reflect.New(dst.Type().Elem()))
}
// NOTE(toby3d): if contains UnmarshalForm method
for i := 0; i < dst.NumMethod(); i++ {
if dst.Type().Method(i).Name != methodName {
continue
}
in := make([]reflect.Value, 1)
in[0] = reflect.ValueOf([]byte(src.Get(key)))
out := dst.Method(i).Call(in)
if len(out) > 0 && out[0].Interface() != nil && !opts.Contains(tagOmitempty) {
return out[0].Interface().(error)
}
return nil
}
if err := d.decode(key, dst.Elem(), ""); err != nil {
return err
}
case reflect.Struct:
// NOTE(toby3d): if contains UnmarshalForm method
for i := 0; i < dst.Addr().NumMethod(); i++ {
if dst.Addr().Type().Method(i).Name != methodName {
continue
}
in := make([]reflect.Value, 1)
in[0] = reflect.ValueOf([]byte(src.Get(key)))
out := dst.Addr().Method(i).Call(in)
if len(out) > 0 && out[0].Interface() != nil && !opts.Contains(tagOmitempty) {
return out[0].Interface().(error)
}
return nil
}
for i := 0; i < dst.NumField(); i++ {
if name, opts := parseTag(string(dst.Type().Field(i).Tag.Get(d.tag))); name != tagIgnore {
if err := d.decode(name, dst.Field(i), opts); err != nil {
return err
}
}
}
}
return nil
}