فهرست منبع

Proper reporting of type errors.

Gustavo Niemeyer 11 سال پیش
والد
کامیت
c544d03421
4فایلهای تغییر یافته به همراه186 افزوده شده و 101 حذف شده
  1. 40 11
      decode.go
  2. 93 42
      decode_test.go
  3. 10 10
      encode_test.go
  4. 43 38
      yaml.go

+ 40 - 11
decode.go

@@ -2,6 +2,7 @@ package yaml
 
 import (
 	"encoding/base64"
+	"fmt"
 	"reflect"
 	"strconv"
 	"time"
@@ -187,6 +188,7 @@ type decoder struct {
 	doc     *node
 	aliases map[string]bool
 	mapType reflect.Type
+	terrors []string
 }
 
 var defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
@@ -197,15 +199,35 @@ func newDecoder() *decoder {
 	return d
 }
 
+func (d *decoder) terror(n *node, tag string, out reflect.Value) {
+	if n.tag != "" {
+		tag = n.tag
+	}
+	value := n.value
+	if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
+		if len(value) > 10 {
+			value = " `" + value[:7] + "...`"
+		} else {
+			value = " `" + value + "`"
+		}
+	}
+	d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
+}
+
 func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
+	terrlen := len(d.terrors)
 	err := u.UnmarshalYAML(func(v interface{}) (err error) {
 		defer handleErr(&err)
-		if !d.unmarshal(n, reflect.ValueOf(v)) {
-			return ErrMismatch
+		d.unmarshal(n, reflect.ValueOf(v))
+		if len(d.terrors) > terrlen {
+			issues := d.terrors[terrlen:]
+			d.terrors = d.terrors[:terrlen]
+			return &TypeError{issues}
 		}
 		return nil
 	})
-	if err == ErrMismatch {
+	if e, ok := err.(*TypeError); ok {
+		d.terrors = append(d.terrors, e.Errors...)
 		return false
 	}
 	if err != nil {
@@ -260,15 +282,15 @@ func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
 	}
 	switch n.kind {
 	case scalarNode:
-		return d.scalar(n, out)
+		good = d.scalar(n, out)
 	case mappingNode:
-		return d.mapping(n, out)
+		good = d.mapping(n, out)
 	case sequenceNode:
-		return d.sequence(n, out)
+		good = d.sequence(n, out)
 	default:
 		panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
 	}
-	return false
+	return good
 }
 
 func (d *decoder) document(n *node, out reflect.Value) (good bool) {
@@ -416,6 +438,9 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
 			good = true
 		}
 	}
+	if !good {
+		d.terror(n, tag, out)
+	}
 	return good
 }
 
@@ -428,13 +453,15 @@ func settableValueOf(i interface{}) reflect.Value {
 
 func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
 	var iface reflect.Value
-	if out.Kind() == reflect.Interface {
+	switch out.Kind() {
+	case reflect.Slice:
+		// okay
+	case reflect.Interface:
 		// No type hints. Will have to use a generic sequence.
 		iface = out
 		out = settableValueOf(make([]interface{}, 0))
-	}
-
-	if out.Kind() != reflect.Slice {
+	default:
+		d.terror(n, yaml_SEQ_TAG, out)
 		return false
 	}
 	et := out.Type().Elem()
@@ -474,6 +501,7 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 			return true
 		}
 	default:
+		d.terror(n, yaml_MAP_TAG, out)
 		return false
 	}
 	outt := out.Type()
@@ -516,6 +544,7 @@ var mapItemType = reflect.TypeOf(MapItem{})
 func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
 	outt := out.Type()
 	if outt.Elem() != mapItemType {
+		d.terror(n, yaml_MAP_TAG, out)
 		return false
 	}
 

+ 93 - 42
decode_test.go

@@ -421,7 +421,7 @@ type inlineC struct {
 }
 
 func (s *S) TestUnmarshal(c *C) {
-	for i, item := range unmarshalTests {
+	for _, item := range unmarshalTests {
 		t := reflect.ValueOf(item.value).Type()
 		var value interface{}
 		switch t.Kind() {
@@ -435,11 +435,13 @@ func (s *S) TestUnmarshal(c *C) {
 			c.Fatalf("missing case for %s", t)
 		}
 		err := yaml.Unmarshal([]byte(item.data), value)
-		c.Assert(err, IsNil, Commentf("Item #%d", i))
+		if _, ok := err.(*yaml.TypeError); !ok {
+			c.Assert(err, IsNil)
+		}
 		if t.Kind() == reflect.String {
-			c.Assert(*value.(*string), Equals, item.value, Commentf("Item #%d", i))
+			c.Assert(*value.(*string), Equals, item.value)
 		} else {
-			c.Assert(value, DeepEquals, item.value, Commentf("Item #%d", i))
+			c.Assert(value, DeepEquals, item.value)
 		}
 	}
 }
@@ -473,7 +475,7 @@ func (s *S) TestUnmarshalErrors(c *C) {
 	}
 }
 
-var setterTests = []struct {
+var unmarshalerTests = []struct {
 	data, tag string
 	value     interface{}
 }{
@@ -486,35 +488,35 @@ var setterTests = []struct {
 	{"_: !!foo 'BAR!'", "!!foo", "BAR!"},
 }
 
-var setterResult = map[int]error{}
+var unmarshalerResult = map[int]error{}
 
-type typeWithSetter struct {
+type unmarshalerType struct {
 	value interface{}
 }
 
-func (o *typeWithSetter) UnmarshalYAML(unmarshal func(v interface{}) error) error {
+func (o *unmarshalerType) UnmarshalYAML(unmarshal func(v interface{}) error) error {
 	if err := unmarshal(&o.value); err != nil {
 		return err
 	}
 	if i, ok := o.value.(int); ok {
-		if result, ok := setterResult[i]; ok {
+		if result, ok := unmarshalerResult[i]; ok {
 			return result
 		}
 	}
 	return nil
 }
 
-type setterPointerType struct {
-	Field *typeWithSetter "_"
+type unmarshalerPointer struct {
+	Field *unmarshalerType "_"
 }
 
-type setterValueType struct {
-	Field typeWithSetter "_"
+type unmarshalerValue struct {
+	Field unmarshalerType "_"
 }
 
-func (s *S) TestUnmarshalWithPointerSetter(c *C) {
-	for _, item := range setterTests {
-		obj := &setterPointerType{}
+func (s *S) TestUnmarshalerPointerField(c *C) {
+	for _, item := range unmarshalerTests {
+		obj := &unmarshalerPointer{}
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		c.Assert(err, IsNil)
 		if item.value == nil {
@@ -526,9 +528,9 @@ func (s *S) TestUnmarshalWithPointerSetter(c *C) {
 	}
 }
 
-func (s *S) TestUnmarshalWithValueSetter(c *C) {
-	for _, item := range setterTests {
-		obj := &setterValueType{}
+func (s *S) TestUnmarshalerValueField(c *C) {
+	for _, item := range unmarshalerTests {
+		obj := &unmarshalerValue{}
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		c.Assert(err, IsNil)
 		c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
@@ -536,34 +538,82 @@ func (s *S) TestUnmarshalWithValueSetter(c *C) {
 	}
 }
 
-func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) {
-	obj := &typeWithSetter{}
-	err := yaml.Unmarshal([]byte(setterTests[0].data), obj)
+func (s *S) TestUnmarshalerWholeDocument(c *C) {
+	obj := &unmarshalerType{}
+	err := yaml.Unmarshal([]byte(unmarshalerTests[0].data), obj)
 	c.Assert(err, IsNil)
 	value, ok := obj.value.(map[interface{}]interface{})
 	c.Assert(ok, Equals, true, Commentf("value: %#v", obj.value))
-	c.Assert(value["_"], DeepEquals, setterTests[0].value)
+	c.Assert(value["_"], DeepEquals, unmarshalerTests[0].value)
 }
 
-func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
-	setterResult[2] = yaml.ErrMismatch
-	setterResult[4] = yaml.ErrMismatch
+func (s *S) TestUnmarshalerTypeError(c *C) {
+	unmarshalerResult[2] = &yaml.TypeError{[]string{"foo"}}
+	unmarshalerResult[4] = &yaml.TypeError{[]string{"bar"}}
 	defer func() {
-		delete(setterResult, 2)
-		delete(setterResult, 4)
+		delete(unmarshalerResult, 2)
+		delete(unmarshalerResult, 4)
 	}()
 
-	m := map[string]*typeWithSetter{}
-	data := `{abc: 1, def: 2, ghi: 3, jkl: 4}`
-	err := yaml.Unmarshal([]byte(data), m)
-	c.Assert(err, IsNil)
-	c.Assert(m["abc"], NotNil)
-	c.Assert(m["def"], IsNil)
-	c.Assert(m["ghi"], NotNil)
-	c.Assert(m["jkl"], IsNil)
+	type T struct {
+		Before int
+		After  int
+		M      map[string]*unmarshalerType
+	}
+	var v T
+	data := `{before: A, m: {abc: 1, def: 2, ghi: 3, jkl: 4}, after: B}`
+	err := yaml.Unmarshal([]byte(data), &v)
+	c.Assert(err, ErrorMatches, ""+
+		"yaml: unmarshal errors:\n"+
+		"  line 1: cannot unmarshal !!str `A` into int\n"+
+		"  foo\n"+
+		"  bar\n"+
+		"  line 1: cannot unmarshal !!str `B` into int")
+	c.Assert(v.M["abc"], NotNil)
+	c.Assert(v.M["def"], IsNil)
+	c.Assert(v.M["ghi"], NotNil)
+	c.Assert(v.M["jkl"], IsNil)
+
+	c.Assert(v.M["abc"].value, Equals, 1)
+	c.Assert(v.M["ghi"].value, Equals, 3)
+}
+
+type proxyTypeError struct{}
+
+func (v *proxyTypeError) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var s string
+	var a int32
+	var b int64
+	if err := unmarshal(&s); err != nil {
+		panic(err)
+	}
+	if s == "a" {
+		if err := unmarshal(&b); err == nil {
+			panic("should have failed")
+		}
+		return unmarshal(&a)
+	}
+	if err := unmarshal(&a); err == nil {
+		panic("should have failed")
+	}
+	return unmarshal(&b)
+}
 
-	c.Assert(m["abc"].value, Equals, 1)
-	c.Assert(m["ghi"].value, Equals, 3)
+func (s *S) TestUnmarshalerTypeErrorProxying(c *C) {
+	type T struct {
+		Before int
+		After  int
+		M      map[string]*proxyTypeError
+	}
+	var v T
+	data := `{before: A, m: {abc: a, def: b}, after: B}`
+	err := yaml.Unmarshal([]byte(data), &v)
+	c.Assert(err, ErrorMatches, ""+
+		"yaml: unmarshal errors:\n"+
+		"  line 1: cannot unmarshal !!str `A` into int\n"+
+		"  line 1: cannot unmarshal !!str `a` into int32\n"+
+		"  line 1: cannot unmarshal !!str `b` into int64\n"+
+		"  line 1: cannot unmarshal !!str `B` into int")
 }
 
 type failingUnmarshaler struct{}
@@ -582,10 +632,11 @@ func (s *S) TestUnmarshalerError(c *C) {
 // From http://yaml.org/type/merge.html
 var mergeTests = `
 anchors:
-  - &CENTER { "x": 1, "y": 2 }
-  - &LEFT   { "x": 0, "y": 2 }
-  - &BIG    { "r": 10 }
-  - &SMALL  { "r": 1 }
+  list:
+    - &CENTER { "x": 1, "y": 2 }
+    - &LEFT   { "x": 0, "y": 2 }
+    - &BIG    { "r": 10 }
+    - &SMALL  { "r": 1 }
 
 # All the following maps are equal:
 

+ 10 - 10
encode_test.go

@@ -299,7 +299,7 @@ func (s *S) TestMarshalTypeCache(c *C) {
 	c.Assert(string(data), Equals, "b: 0\n")
 }
 
-var getterTests = []struct {
+var marshalerTests = []struct {
 	data  string
 	value interface{}
 }{
@@ -310,21 +310,21 @@ var getterTests = []struct {
 	{"_: BAR!\n", "BAR!"},
 }
 
-type typeWithGetter struct {
+type marshalerType struct {
 	value interface{}
 }
 
-func (o typeWithGetter) MarshalYAML() (interface{}, error) {
+func (o marshalerType) MarshalYAML() (interface{}, error) {
 	return o.value, nil
 }
 
-type typeWithGetterField struct {
-	Field typeWithGetter "_"
+type marshalerValue struct {
+	Field marshalerType "_"
 }
 
-func (s *S) TestMashalWithGetter(c *C) {
-	for _, item := range getterTests {
-		obj := &typeWithGetterField{}
+func (s *S) TestMarshaler(c *C) {
+	for _, item := range marshalerTests {
+		obj := &marshalerValue{}
 		obj.Field.value = item.value
 		data, err := yaml.Marshal(obj)
 		c.Assert(err, IsNil)
@@ -332,8 +332,8 @@ func (s *S) TestMashalWithGetter(c *C) {
 	}
 }
 
-func (s *S) TestUnmarshalWholeDocumentWithGetter(c *C) {
-	obj := &typeWithGetter{}
+func (s *S) TestMarshalerWholeDocument(c *C) {
+	obj := &marshalerType{}
 	obj.value = map[string]string{"hello": "world!"}
 	data, err := yaml.Marshal(obj)
 	c.Assert(err, IsNil)

+ 43 - 38
yaml.go

@@ -14,36 +14,6 @@ import (
 	"sync"
 )
 
-type yamlError struct {
-	err error
-}
-
-func fail(err error) {
-	panic(yamlError{err})
-}
-
-func failf(format string, args ...interface{}) {
-	panic(yamlError{fmt.Errorf("yaml: " + format, args...)})
-}
-
-func handleErr(err *error) {
-	if v := recover(); v != nil {
-		if e, ok := v.(yamlError); ok {
-			*err = e.err
-		} else {
-			panic(v)
-		}
-	}
-}
-
-// A TypeError is returned by Unmarshal when one or more fields in
-// the YAML document cannot be properly decoded.
-type TypeError struct {
-	Issues []string
-}
-
-var ErrMismatch = errors.New("type not suitable for decoded value")
-
 // MapSlice encodes and decodes as a YAML map.
 // The order of keys is preserved when encoding and decoding.
 type MapSlice []MapItem
@@ -77,17 +47,15 @@ type Marshaler interface {
 // and assigns decoded values into the out value.
 //
 // Maps and pointers (to a struct, string, int, etc) are accepted as out
-// values.  If an internal pointer within a struct is not initialized,
+// values. If an internal pointer within a struct is not initialized,
 // the yaml package will initialize it if necessary for unmarshalling
 // the provided data. The out parameter must not be nil.
 //
-// The type of the decoded values and the type of out will be considered,
-// and Unmarshal will do the best possible job to unmarshal values
-// appropriately.  It is NOT considered an error, though, to skip values
-// because they are not available in the decoded YAML, or if they are not
-// compatible with the out value. To ensure something was properly
-// unmarshaled use a map or compare against the previous value for the
-// field (usually the zero value).
+// The type of the decoded values should be compatible with the respective
+// values in out. If one or more values cannot be decoded due to a type
+// mismatches, decoding continues partially until the end of the YAML
+// content, and a *yaml.TypeError is returned with details for all
+// missed values.
 //
 // Struct fields are only unmarshalled if they are exported (have an
 // upper case first letter), and are unmarshalled using the field name
@@ -122,6 +90,9 @@ func Unmarshal(in []byte, out interface{}) (err error) {
 		}
 		d.unmarshal(node, v)
 	}
+	if d.terrors != nil {
+		return &TypeError{d.terrors}
+	}
 	return nil
 }
 
@@ -174,6 +145,40 @@ func Marshal(in interface{}) (out []byte, err error) {
 	return
 }
 
+func handleErr(err *error) {
+	if v := recover(); v != nil {
+		if e, ok := v.(yamlError); ok {
+			*err = e.err
+		} else {
+			panic(v)
+		}
+	}
+}
+
+type yamlError struct {
+	err error
+}
+
+func fail(err error) {
+	panic(yamlError{err})
+}
+
+func failf(format string, args ...interface{}) {
+	panic(yamlError{fmt.Errorf("yaml: " + format, args...)})
+}
+
+// A TypeError is returned by Unmarshal when one or more fields in
+// the YAML document cannot be properly decoded into the requested
+// types. When this error is returned, the value is still
+// unmarshaled partially.
+type TypeError struct {
+	Errors []string
+}
+
+func (e *TypeError) Error() string {
+	return fmt.Sprintf("yaml: unmarshal errors:\n  %s", strings.Join(e.Errors, "\n  "))
+}
+
 // --------------------------------------------------------------------------
 // Maintain a mapping of keys to structure field indexes