You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
422 lines
9.1 KiB
422 lines
9.1 KiB
package dsn |
|
|
|
import ( |
|
"encoding" |
|
"net/url" |
|
"reflect" |
|
"runtime" |
|
"strconv" |
|
"strings" |
|
) |
|
|
|
const ( |
|
_tagID = "dsn" |
|
_queryPrefix = "query." |
|
) |
|
|
|
// InvalidBindError describes an invalid argument passed to DecodeQuery. |
|
// (The argument to DecodeQuery must be a non-nil pointer.) |
|
type InvalidBindError struct { |
|
Type reflect.Type |
|
} |
|
|
|
func (e *InvalidBindError) Error() string { |
|
if e.Type == nil { |
|
return "Bind(nil)" |
|
} |
|
|
|
if e.Type.Kind() != reflect.Ptr { |
|
return "Bind(non-pointer " + e.Type.String() + ")" |
|
} |
|
return "Bind(nil " + e.Type.String() + ")" |
|
} |
|
|
|
// BindTypeError describes a query value that was |
|
// not appropriate for a value of a specific Go type. |
|
type BindTypeError struct { |
|
Value string |
|
Type reflect.Type |
|
} |
|
|
|
func (e *BindTypeError) Error() string { |
|
return "cannot decode " + e.Value + " into Go value of type " + e.Type.String() |
|
} |
|
|
|
type assignFunc func(v reflect.Value, to tagOpt) error |
|
|
|
func stringsAssignFunc(val string) assignFunc { |
|
return func(v reflect.Value, to tagOpt) error { |
|
if v.Kind() != reflect.String || !v.CanSet() { |
|
return &BindTypeError{Value: "string", Type: v.Type()} |
|
} |
|
if val == "" { |
|
v.SetString(to.Default) |
|
} else { |
|
v.SetString(val) |
|
} |
|
return nil |
|
} |
|
} |
|
|
|
// bindQuery parses url.Values and stores the result in the value pointed to by v. |
|
// if v is nil or not a pointer, bindQuery returns an InvalidDecodeError |
|
func bindQuery(query url.Values, v interface{}, assignFuncs map[string]assignFunc) (url.Values, error) { |
|
if assignFuncs == nil { |
|
assignFuncs = make(map[string]assignFunc) |
|
} |
|
d := decodeState{ |
|
data: query, |
|
used: make(map[string]bool), |
|
assignFuncs: assignFuncs, |
|
} |
|
err := d.decode(v) |
|
ret := d.unused() |
|
return ret, err |
|
} |
|
|
|
type tagOpt struct { |
|
Name string |
|
Default string |
|
} |
|
|
|
func parseTag(tag string) tagOpt { |
|
vs := strings.SplitN(tag, ",", 2) |
|
if len(vs) == 2 { |
|
return tagOpt{Name: vs[0], Default: vs[1]} |
|
} |
|
return tagOpt{Name: vs[0]} |
|
} |
|
|
|
type decodeState struct { |
|
data url.Values |
|
used map[string]bool |
|
assignFuncs map[string]assignFunc |
|
} |
|
|
|
func (d *decodeState) unused() url.Values { |
|
ret := make(url.Values) |
|
for k, v := range d.data { |
|
if !d.used[k] { |
|
ret[k] = v |
|
} |
|
} |
|
return ret |
|
} |
|
|
|
func (d *decodeState) decode(v interface{}) (err error) { |
|
defer func() { |
|
if r := recover(); r != nil { |
|
if _, ok := r.(runtime.Error); ok { |
|
panic(r) |
|
} |
|
err = r.(error) |
|
} |
|
}() |
|
rv := reflect.ValueOf(v) |
|
if rv.Kind() != reflect.Ptr || rv.IsNil() { |
|
return &InvalidBindError{reflect.TypeOf(v)} |
|
} |
|
return d.root(rv) |
|
} |
|
|
|
func (d *decodeState) root(v reflect.Value) error { |
|
var tu encoding.TextUnmarshaler |
|
tu, v = d.indirect(v) |
|
if tu != nil { |
|
return tu.UnmarshalText([]byte(d.data.Encode())) |
|
} |
|
// TODO support map, slice as root |
|
if v.Kind() != reflect.Struct { |
|
return &BindTypeError{Value: d.data.Encode(), Type: v.Type()} |
|
} |
|
tv := v.Type() |
|
for i := 0; i < tv.NumField(); i++ { |
|
fv := v.Field(i) |
|
field := tv.Field(i) |
|
to := parseTag(field.Tag.Get(_tagID)) |
|
if to.Name == "-" { |
|
continue |
|
} |
|
if af, ok := d.assignFuncs[to.Name]; ok { |
|
if err := af(fv, tagOpt{}); err != nil { |
|
return err |
|
} |
|
continue |
|
} |
|
if !strings.HasPrefix(to.Name, _queryPrefix) { |
|
continue |
|
} |
|
to.Name = to.Name[len(_queryPrefix):] |
|
if err := d.value(fv, "", to); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func combinekey(prefix string, to tagOpt) string { |
|
key := to.Name |
|
if prefix != "" { |
|
key = prefix + "." + key |
|
} |
|
return key |
|
} |
|
|
|
func (d *decodeState) value(v reflect.Value, prefix string, to tagOpt) (err error) { |
|
key := combinekey(prefix, to) |
|
d.used[key] = true |
|
var tu encoding.TextUnmarshaler |
|
tu, v = d.indirect(v) |
|
if tu != nil { |
|
if val, ok := d.data[key]; ok { |
|
return tu.UnmarshalText([]byte(val[0])) |
|
} |
|
if to.Default != "" { |
|
return tu.UnmarshalText([]byte(to.Default)) |
|
} |
|
return |
|
} |
|
switch v.Kind() { |
|
case reflect.Bool: |
|
err = d.valueBool(v, prefix, to) |
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: |
|
err = d.valueInt64(v, prefix, to) |
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: |
|
err = d.valueUint64(v, prefix, to) |
|
case reflect.Float32, reflect.Float64: |
|
err = d.valueFloat64(v, prefix, to) |
|
case reflect.String: |
|
err = d.valueString(v, prefix, to) |
|
case reflect.Slice: |
|
err = d.valueSlice(v, prefix, to) |
|
case reflect.Struct: |
|
err = d.valueStruct(v, prefix, to) |
|
case reflect.Ptr: |
|
if !d.hasKey(combinekey(prefix, to)) { |
|
break |
|
} |
|
if !v.CanSet() { |
|
break |
|
} |
|
nv := reflect.New(v.Type().Elem()) |
|
v.Set(nv) |
|
err = d.value(nv, prefix, to) |
|
} |
|
return |
|
} |
|
|
|
func (d *decodeState) hasKey(key string) bool { |
|
for k := range d.data { |
|
if strings.HasPrefix(k, key+".") || k == key { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func (d *decodeState) valueBool(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
val := d.data.Get(key) |
|
if val == "" { |
|
if to.Default == "" { |
|
return nil |
|
} |
|
val = to.Default |
|
} |
|
return d.setBool(v, val) |
|
} |
|
|
|
func (d *decodeState) setBool(v reflect.Value, val string) error { |
|
bval, err := strconv.ParseBool(val) |
|
if err != nil { |
|
return &BindTypeError{Value: val, Type: v.Type()} |
|
} |
|
v.SetBool(bval) |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueInt64(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
val := d.data.Get(key) |
|
if val == "" { |
|
if to.Default == "" { |
|
return nil |
|
} |
|
val = to.Default |
|
} |
|
return d.setInt64(v, val) |
|
} |
|
|
|
func (d *decodeState) setInt64(v reflect.Value, val string) error { |
|
ival, err := strconv.ParseInt(val, 10, 64) |
|
if err != nil { |
|
return &BindTypeError{Value: val, Type: v.Type()} |
|
} |
|
v.SetInt(ival) |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueUint64(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
val := d.data.Get(key) |
|
if val == "" { |
|
if to.Default == "" { |
|
return nil |
|
} |
|
val = to.Default |
|
} |
|
return d.setUint64(v, val) |
|
} |
|
|
|
func (d *decodeState) setUint64(v reflect.Value, val string) error { |
|
uival, err := strconv.ParseUint(val, 10, 64) |
|
if err != nil { |
|
return &BindTypeError{Value: val, Type: v.Type()} |
|
} |
|
v.SetUint(uival) |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueFloat64(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
val := d.data.Get(key) |
|
if val == "" { |
|
if to.Default == "" { |
|
return nil |
|
} |
|
val = to.Default |
|
} |
|
return d.setFloat64(v, val) |
|
} |
|
|
|
func (d *decodeState) setFloat64(v reflect.Value, val string) error { |
|
fval, err := strconv.ParseFloat(val, 64) |
|
if err != nil { |
|
return &BindTypeError{Value: val, Type: v.Type()} |
|
} |
|
v.SetFloat(fval) |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueString(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
val := d.data.Get(key) |
|
if val == "" { |
|
if to.Default == "" { |
|
return nil |
|
} |
|
val = to.Default |
|
} |
|
return d.setString(v, val) |
|
} |
|
|
|
func (d *decodeState) setString(v reflect.Value, val string) error { |
|
v.SetString(val) |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueSlice(v reflect.Value, prefix string, to tagOpt) error { |
|
key := combinekey(prefix, to) |
|
strs, ok := d.data[key] |
|
if !ok { |
|
strs = strings.Split(to.Default, ",") |
|
} |
|
if len(strs) == 0 { |
|
return nil |
|
} |
|
et := v.Type().Elem() |
|
var setFunc func(reflect.Value, string) error |
|
switch et.Kind() { |
|
case reflect.Bool: |
|
setFunc = d.setBool |
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: |
|
setFunc = d.setInt64 |
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: |
|
setFunc = d.setUint64 |
|
case reflect.Float32, reflect.Float64: |
|
setFunc = d.setFloat64 |
|
case reflect.String: |
|
setFunc = d.setString |
|
default: |
|
return &BindTypeError{Type: et, Value: strs[0]} |
|
} |
|
vals := reflect.MakeSlice(v.Type(), len(strs), len(strs)) |
|
for i, str := range strs { |
|
if err := setFunc(vals.Index(i), str); err != nil { |
|
return err |
|
} |
|
} |
|
if v.CanSet() { |
|
v.Set(vals) |
|
} |
|
return nil |
|
} |
|
|
|
func (d *decodeState) valueStruct(v reflect.Value, prefix string, to tagOpt) error { |
|
tv := v.Type() |
|
for i := 0; i < tv.NumField(); i++ { |
|
fv := v.Field(i) |
|
field := tv.Field(i) |
|
fto := parseTag(field.Tag.Get(_tagID)) |
|
if fto.Name == "-" { |
|
continue |
|
} |
|
if af, ok := d.assignFuncs[fto.Name]; ok { |
|
if err := af(fv, tagOpt{}); err != nil { |
|
return err |
|
} |
|
continue |
|
} |
|
if !strings.HasPrefix(fto.Name, _queryPrefix) { |
|
continue |
|
} |
|
fto.Name = fto.Name[len(_queryPrefix):] |
|
if err := d.value(fv, to.Name, fto); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (d *decodeState) indirect(v reflect.Value) (encoding.TextUnmarshaler, reflect.Value) { |
|
v0 := v |
|
haveAddr := false |
|
|
|
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { |
|
haveAddr = true |
|
v = v.Addr() |
|
} |
|
for { |
|
if v.Kind() == reflect.Interface && !v.IsNil() { |
|
e := v.Elem() |
|
if e.Kind() == reflect.Ptr && !e.IsNil() && e.Elem().Kind() == reflect.Ptr { |
|
haveAddr = false |
|
v = e |
|
continue |
|
} |
|
} |
|
|
|
if v.Kind() != reflect.Ptr { |
|
break |
|
} |
|
|
|
if v.Elem().Kind() != reflect.Ptr && v.CanSet() { |
|
break |
|
} |
|
if v.IsNil() { |
|
v.Set(reflect.New(v.Type().Elem())) |
|
} |
|
if v.Type().NumMethod() > 0 { |
|
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { |
|
return u, reflect.Value{} |
|
} |
|
} |
|
if haveAddr { |
|
v = v0 |
|
haveAddr = false |
|
} else { |
|
v = v.Elem() |
|
} |
|
} |
|
return nil, v |
|
}
|
|
|