bencode: Add ignore_unmarshal_type_error tag

This commit is contained in:
Matt Joiner 2018-01-27 14:31:12 +11:00
parent 1725fc9a36
commit 1f3eace72f
5 changed files with 143 additions and 102 deletions

View File

@ -20,7 +20,6 @@ type Decoder struct {
// Sum of bytes used to Decode values.
Offset int64
buf bytes.Buffer
key string
}
func (d *Decoder) Decode(v interface{}) (err error) {
@ -186,6 +185,82 @@ func (d *Decoder) parseString(v reflect.Value) error {
return nil
}
// Info for parsing a dict value.
type dictField struct {
Value reflect.Value // Storage for the parsed value.
// True if field value should be parsed into Value. If false, the value
// should be parsed and discarded.
Ok bool
Set func() // Call this after parsing into Value.
IgnoreUnmarshalTypeError bool
}
// Returns specifics for parsing a dict field value.
func getDictField(dict reflect.Value, key string) dictField {
// get valuev as a map value or as a struct field
switch dict.Kind() {
case reflect.Map:
value := reflect.New(dict.Type().Elem()).Elem()
return dictField{
Value: value,
Ok: true,
Set: func() {
// Assigns the value into the map.
dict.SetMapIndex(reflect.ValueOf(key), value)
},
}
case reflect.Struct:
sf, ok := getStructFieldForKey(dict.Type(), key)
if !ok {
return dictField{}
}
if sf.PkgPath != "" {
panic(&UnmarshalFieldError{
Key: key,
Type: dict.Type(),
Field: sf,
})
}
return dictField{
Value: dict.FieldByIndex(sf.Index),
Ok: true,
Set: func() {},
IgnoreUnmarshalTypeError: getTag(sf.Tag).IgnoreUnmarshalTypeError(),
}
default:
panic(dict.Kind())
}
}
func getStructFieldForKey(struct_ reflect.Type, key string) (f reflect.StructField, ok bool) {
for i, n := 0, struct_.NumField(); i < n; i++ {
f = struct_.Field(i)
tag := f.Tag.Get("bencode")
if tag == "-" {
continue
}
if f.Anonymous {
continue
}
if parseTag(tag).Key() == key {
ok = true
break
}
if f.Name == key {
ok = true
break
}
if strings.EqualFold(f.Name, key) {
ok = true
break
}
}
return
}
func (d *Decoder) parseDict(v reflect.Value) error {
switch v.Kind() {
case reflect.Map:
@ -207,14 +282,12 @@ func (d *Decoder) parseDict(v reflect.Value) error {
})
}
var mapElem reflect.Value
// so, at this point 'd' byte was consumed, let's just read key/value
// pairs one by one
for {
var valuev reflect.Value
keyv := reflect.ValueOf(&d.key).Elem()
ok, err := d.parseValue(keyv)
var keyStr string
keyValue := reflect.ValueOf(&keyStr).Elem()
ok, err := d.parseValue(keyValue)
if err != nil {
return fmt.Errorf("error parsing dict key: %s", err)
}
@ -222,77 +295,30 @@ func (d *Decoder) parseDict(v reflect.Value) error {
return nil
}
// get valuev as a map value or as a struct field
switch v.Kind() {
case reflect.Map:
elem_type := v.Type().Elem()
if !mapElem.IsValid() {
mapElem = reflect.New(elem_type).Elem()
} else {
mapElem.Set(reflect.Zero(elem_type))
}
valuev = mapElem
case reflect.Struct:
var f reflect.StructField
var ok bool
t := v.Type()
for i, n := 0, t.NumField(); i < n; i++ {
f = t.Field(i)
tag := f.Tag.Get("bencode")
if tag == "-" {
continue
}
if f.Anonymous {
continue
}
tag_name, _ := parseTag(tag)
if tag_name == d.key {
ok = true
break
}
if f.Name == d.key {
ok = true
break
}
if strings.EqualFold(f.Name, d.key) {
ok = true
break
}
}
if ok {
if f.PkgPath != "" {
panic(&UnmarshalFieldError{
Key: d.key,
Type: v.Type(),
Field: f,
})
} else {
valuev = v.FieldByIndex(f.Index)
}
} else {
_, ok := d.parseValueInterface()
if !ok {
return fmt.Errorf("error parsing dict value for key %q", d.key)
}
continue
}
}
df := getDictField(v, keyStr)
// now we need to actually parse it
ok, err = d.parseValue(valuev)
if df.Ok {
// log.Printf("parsing ok struct field for key %q", keyStr)
ok, err = d.parseValue(df.Value)
} else {
// Discard the value, there's nowhere to put it.
var if_ interface{}
if_, ok = d.parseValueInterface()
if if_ == nil {
err = fmt.Errorf("error parsing value for key %q", keyStr)
}
}
if err != nil {
return fmt.Errorf("parsing value for key %q: %s", d.key, err)
if _, ok := err.(*UnmarshalTypeError); !ok || !df.IgnoreUnmarshalTypeError {
return fmt.Errorf("parsing value for key %q: %s", keyStr, err)
}
}
if !ok {
return fmt.Errorf("missing value for key %q", d.key)
return fmt.Errorf("missing value for key %q", keyStr)
}
if v.Kind() == reflect.Map {
v.SetMapIndex(keyv, valuev)
if df.Ok {
df.Set()
}
}
}

View File

@ -136,3 +136,14 @@ func TestUnmarshalerBencode(t *testing.T) {
assert_equal(t, ss[2].x, "3:way")
}
func TestIgnoreUnmarshalTypeError(t *testing.T) {
s := struct {
Ignore int `bencode:",ignore_unmarshal_type_error"`
Normal int
}{}
require.Error(t, Unmarshal([]byte("d6:Normal5:helloe"), &s))
assert.Nil(t, Unmarshal([]byte("d6:Ignore5:helloe"), &s))
require.Nil(t, Unmarshal([]byte("d6:Ignorei42ee"), &s))
assert.EqualValues(t, 42, s.Ignore)
}

View File

@ -240,17 +240,14 @@ func encodeFields(t reflect.Type) []encodeField {
ef.i = i
ef.tag = f.Name
tv := f.Tag.Get("bencode")
if tv != "" {
if tv == "-" {
continue
}
name, opts := parseTag(tv)
if name != "" {
ef.tag = name
}
ef.omit_empty = opts.contains("omitempty")
tv := getTag(f.Tag)
if tv.Ignore() {
continue
}
if tv.Key() != "" {
ef.tag = tv.Key()
}
ef.omit_empty = tv.OmitEmpty()
fs = append(fs, ef)
}
fss := encodeFieldsSortType(fs)

View File

@ -1,34 +1,41 @@
package bencode
import (
"reflect"
"strings"
)
type tagOptions string
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
func getTag(st reflect.StructTag) tag {
return parseTag(st.Get("bencode"))
}
func (opts tagOptions) contains(option_name string) bool {
if len(opts) == 0 {
return false
}
type tag []string
s := string(opts)
for s != "" {
var next string
i := strings.Index(s, ",")
if i != -1 {
s, next = s[:i], s[i+1:]
}
if s == option_name {
func parseTag(tagStr string) tag {
return strings.Split(tagStr, ",")
}
func (me tag) Ignore() bool {
return me[0] == "-"
}
func (me tag) Key() string {
return me[0]
}
func (me tag) HasOpt(opt string) bool {
for _, s := range me[1:] {
if s == opt {
return true
}
s = next
}
return false
}
func (me tag) OmitEmpty() bool {
return me.HasOpt("omitempty")
}
func (me tag) IgnoreUnmarshalTypeError() bool {
return me.HasOpt("ignore_unmarshal_type_error")
}

View File

@ -13,7 +13,7 @@ type MetaInfo struct {
Announce string `bencode:"announce,omitempty"`
AnnounceList AnnounceList `bencode:"announce-list,omitempty"`
Nodes []Node `bencode:"nodes,omitempty"`
CreationDate int64 `bencode:"creation date,omitempty"`
CreationDate int64 `bencode:"creation date,omitempty,ignore_unmarshal_type_error"`
Comment string `bencode:"comment,omitempty"`
CreatedBy string `bencode:"created by,omitempty"`
Encoding string `bencode:"encoding,omitempty"`