Explorar el Código

New marshaler and unmarshaler interfaces.

Gustavo Niemeyer hace 11 años
padre
commit
b51f82a2e3
Se han modificado 6 ficheros con 185 adiciones y 176 borrados
  1. 72 79
      decode.go
  2. 39 24
      decode_test.go
  3. 12 11
      encode.go
  4. 24 42
      encode_test.go
  5. 1 2
      resolve.go
  6. 37 18
      yaml.go

+ 72 - 79
decode.go

@@ -2,7 +2,6 @@ package yaml
 
 
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
-	"fmt"
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
 	"time"
 	"time"
@@ -38,7 +37,7 @@ type parser struct {
 func newParser(b []byte) *parser {
 func newParser(b []byte) *parser {
 	p := parser{}
 	p := parser{}
 	if !yaml_parser_initialize(&p.parser) {
 	if !yaml_parser_initialize(&p.parser) {
-		panic("Failed to initialize YAML emitter")
+		panic("failed to initialize YAML emitter")
 	}
 	}
 
 
 	if len(b) == 0 {
 	if len(b) == 0 {
@@ -49,7 +48,7 @@ func newParser(b []byte) *parser {
 
 
 	p.skip()
 	p.skip()
 	if p.event.typ != yaml_STREAM_START_EVENT {
 	if p.event.typ != yaml_STREAM_START_EVENT {
-		panic("Expected stream start event, got " + strconv.Itoa(int(p.event.typ)))
+		panic("expected stream start event, got " + strconv.Itoa(int(p.event.typ)))
 	}
 	}
 	p.skip()
 	p.skip()
 	return &p
 	return &p
@@ -65,7 +64,7 @@ func (p *parser) destroy() {
 func (p *parser) skip() {
 func (p *parser) skip() {
 	if p.event.typ != yaml_NO_EVENT {
 	if p.event.typ != yaml_NO_EVENT {
 		if p.event.typ == yaml_STREAM_END_EVENT {
 		if p.event.typ == yaml_STREAM_END_EVENT {
-			fail("Attempted to go past the end of stream. Corrupted value?")
+			failf("attempted to go past the end of stream; corrupted value?")
 		}
 		}
 		yaml_event_delete(&p.event)
 		yaml_event_delete(&p.event)
 	}
 	}
@@ -89,9 +88,9 @@ func (p *parser) fail() {
 	if len(p.parser.problem) > 0 {
 	if len(p.parser.problem) > 0 {
 		msg = p.parser.problem
 		msg = p.parser.problem
 	} else {
 	} else {
-		msg = "Unknown problem parsing YAML content"
+		msg = "unknown problem parsing YAML content"
 	}
 	}
-	fail(where + msg)
+	failf("%s%s", where, msg)
 }
 }
 
 
 func (p *parser) anchor(n *node, anchor []byte) {
 func (p *parser) anchor(n *node, anchor []byte) {
@@ -116,7 +115,7 @@ func (p *parser) parse() *node {
 		// Happens when attempting to decode an empty buffer.
 		// Happens when attempting to decode an empty buffer.
 		return nil
 		return nil
 	default:
 	default:
-		panic("Attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
+		panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
 	}
 	}
 	panic("unreachable")
 	panic("unreachable")
 }
 }
@@ -136,7 +135,7 @@ func (p *parser) document() *node {
 	p.skip()
 	p.skip()
 	n.children = append(n.children, p.parse())
 	n.children = append(n.children, p.parse())
 	if p.event.typ != yaml_DOCUMENT_END_EVENT {
 	if p.event.typ != yaml_DOCUMENT_END_EVENT {
-		panic("Expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
+		panic("expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
 	}
 	}
 	p.skip()
 	p.skip()
 	return n
 	return n
@@ -198,76 +197,78 @@ func newDecoder() *decoder {
 	return d
 	return d
 }
 }
 
 
-// d.setter deals with setters and pointer dereferencing and initialization.
-//
-// It's a slightly convoluted case to handle properly:
-//
-// - nil pointers should be initialized, unless being set to nil
-// - we don't know at this point yet what's the value to SetYAML() with.
-// - we can't separate pointer deref/init and setter checking, because
-//   a setter may be found while going down a pointer chain.
-//
-// Thus, here is how it takes care of it:
-//
-// - out is provided as a pointer, so that it can be replaced.
-// - when looking at a non-setter ptr, *out=ptr.Elem(), unless tag=!!null
-// - when a setter is found, *out=interface{}, and a set() function is
-//   returned to call SetYAML() with the value of *out once it's defined.
-//
-func (d *decoder) setter(tag string, out *reflect.Value, good *bool) (set func()) {
-	if (*out).Kind() != reflect.Ptr && (*out).CanAddr() {
-		setter, _ := (*out).Addr().Interface().(Setter)
-		if setter != nil {
-			var arg interface{}
-			*out = reflect.ValueOf(&arg).Elem()
-			return func() {
-				*good = setter.SetYAML(shortTag(tag), arg)
-			}
+func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
+	err := u.UnmarshalYAML(func(v interface{}) (err error) {
+		defer handleErr(&err)
+		if !d.unmarshal(n, reflect.ValueOf(v)) {
+			return ErrMismatch
 		}
 		}
+		return nil
+	})
+	if err == ErrMismatch {
+		return false
+	}
+	if err != nil {
+		fail(err)
+	}
+	return true
+}
+
+// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
+// if a value is found to implement it.
+// It returns the initialized and dereferenced out value, whether
+// unmarshalling was already done by UnmarshalYAML, and if so whether
+// its types unmarshalled appropriately.
+//
+// If n holds a null value, prepare returns before doing anything.
+func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
+	if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "") {
+		return out, false, false
 	}
 	}
 	again := true
 	again := true
 	for again {
 	for again {
 		again = false
 		again = false
-		setter, _ := (*out).Interface().(Setter)
-		if tag != yaml_NULL_TAG || setter != nil {
-			if pv := (*out); pv.Kind() == reflect.Ptr {
-				if pv.IsNil() {
-					*out = reflect.New(pv.Type().Elem()).Elem()
-					pv.Set((*out).Addr())
-				} else {
-					*out = pv.Elem()
-				}
-				setter, _ = pv.Interface().(Setter)
-				again = true
+		if out.Kind() == reflect.Ptr {
+			if out.IsNil() {
+				out.Set(reflect.New(out.Type().Elem()))
+				out = out.Elem()
+			} else {
+				out = out.Elem()
 			}
 			}
+			again = true
 		}
 		}
-		if setter != nil {
-			var arg interface{}
-			*out = reflect.ValueOf(&arg).Elem()
-			return func() {
-				*good = setter.SetYAML(shortTag(tag), arg)
+		if out.CanAddr() {
+			if u, ok := out.Addr().Interface().(Unmarshaler); ok {
+				good = d.callUnmarshaler(n, u)
+				return out, true, good
 			}
 			}
 		}
 		}
 	}
 	}
-	return nil
+	return out, false, false
 }
 }
 
 
 func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
 func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
 	switch n.kind {
 	switch n.kind {
 	case documentNode:
 	case documentNode:
-		good = d.document(n, out)
-	case scalarNode:
-		good = d.scalar(n, out)
+		return d.document(n, out)
 	case aliasNode:
 	case aliasNode:
-		good = d.alias(n, out)
+		return d.alias(n, out)
+	}
+	out, unmarshaled, good := d.prepare(n, out)
+	if unmarshaled {
+		return good
+	}
+	switch n.kind {
+	case scalarNode:
+		return d.scalar(n, out)
 	case mappingNode:
 	case mappingNode:
-		good = d.mapping(n, out)
+		return d.mapping(n, out)
 	case sequenceNode:
 	case sequenceNode:
-		good = d.sequence(n, out)
+		return d.sequence(n, out)
 	default:
 	default:
-		panic("Internal error: unknown node kind: " + strconv.Itoa(n.kind))
+		panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
 	}
 	}
-	return
+	return false
 }
 }
 
 
 func (d *decoder) document(n *node, out reflect.Value) (good bool) {
 func (d *decoder) document(n *node, out reflect.Value) (good bool) {
@@ -282,10 +283,10 @@ func (d *decoder) document(n *node, out reflect.Value) (good bool) {
 func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
 func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
 	an, ok := d.doc.anchors[n.value]
 	an, ok := d.doc.anchors[n.value]
 	if !ok {
 	if !ok {
-		fail("Unknown anchor '" + n.value + "' referenced")
+		failf("unknown anchor '%s' referenced", n.value)
 	}
 	}
 	if d.aliases[n.value] {
 	if d.aliases[n.value] {
-		fail("Anchor '" + n.value + "' value contains itself")
+		failf("anchor '%s' value contains itself", n.value)
 	}
 	}
 	d.aliases[n.value] = true
 	d.aliases[n.value] = true
 	good = d.unmarshal(an, out)
 	good = d.unmarshal(an, out)
@@ -314,14 +315,11 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
 		if tag == yaml_BINARY_TAG {
 		if tag == yaml_BINARY_TAG {
 			data, err := base64.StdEncoding.DecodeString(resolved.(string))
 			data, err := base64.StdEncoding.DecodeString(resolved.(string))
 			if err != nil {
 			if err != nil {
-				fail("!!binary value contains invalid base64 data")
+				failf("!!binary value contains invalid base64 data")
 			}
 			}
 			resolved = string(data)
 			resolved = string(data)
 		}
 		}
 	}
 	}
-	if set := d.setter(tag, &out, &good); set != nil {
-		defer set()
-	}
 	if resolved == nil {
 	if resolved == nil {
 		if out.Kind() == reflect.Map && !out.CanAddr() {
 		if out.Kind() == reflect.Map && !out.CanAddr() {
 			resetMap(out)
 			resetMap(out)
@@ -411,6 +409,7 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
 		}
 		}
 	case reflect.Ptr:
 	case reflect.Ptr:
 		if out.Type().Elem() == reflect.TypeOf(resolved) {
 		if out.Type().Elem() == reflect.TypeOf(resolved) {
+			// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
 			elem := reflect.New(out.Type().Elem())
 			elem := reflect.New(out.Type().Elem())
 			elem.Elem().Set(reflect.ValueOf(resolved))
 			elem.Elem().Set(reflect.ValueOf(resolved))
 			out.Set(elem)
 			out.Set(elem)
@@ -428,9 +427,6 @@ func settableValueOf(i interface{}) reflect.Value {
 }
 }
 
 
 func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
 func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
-	if set := d.setter(yaml_SEQ_TAG, &out, &good); set != nil {
-		defer set()
-	}
 	var iface reflect.Value
 	var iface reflect.Value
 	if out.Kind() == reflect.Interface {
 	if out.Kind() == reflect.Interface {
 		// No type hints. Will have to use a generic sequence.
 		// No type hints. Will have to use a generic sequence.
@@ -457,9 +453,6 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
 }
 }
 
 
 func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
-	if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
-		defer set()
-	}
 	switch out.Kind() {
 	switch out.Kind() {
 	case reflect.Struct:
 	case reflect.Struct:
 		return d.mappingStruct(n, out)
 		return d.mappingStruct(n, out)
@@ -506,7 +499,7 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 				kkind = k.Elem().Kind()
 				kkind = k.Elem().Kind()
 			}
 			}
 			if kkind == reflect.Map || kkind == reflect.Slice {
 			if kkind == reflect.Map || kkind == reflect.Slice {
-				fail(fmt.Sprintf("invalid map key: %#v", k.Interface()))
+				failf("invalid map key: %#v", k.Interface())
 			}
 			}
 			e := reflect.New(et).Elem()
 			e := reflect.New(et).Elem()
 			if d.unmarshal(n.children[i+1], e) {
 			if d.unmarshal(n.children[i+1], e) {
@@ -525,9 +518,6 @@ func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
 	if outt.Elem() != mapItemType {
 	if outt.Elem() != mapItemType {
 		return false
 		return false
 	}
 	}
-	if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
-		defer set()
-	}
 
 
 	mapType := d.mapType
 	mapType := d.mapType
 	d.mapType = outt
 	d.mapType = outt
@@ -582,15 +572,18 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
 	return true
 	return true
 }
 }
 
 
+func failWantMap() {
+	failf("map merge requires map or sequence of maps as the value")
+}
+
 func (d *decoder) merge(n *node, out reflect.Value) {
 func (d *decoder) merge(n *node, out reflect.Value) {
-	const wantMap = "map merge requires map or sequence of maps as the value"
 	switch n.kind {
 	switch n.kind {
 	case mappingNode:
 	case mappingNode:
 		d.unmarshal(n, out)
 		d.unmarshal(n, out)
 	case aliasNode:
 	case aliasNode:
 		an, ok := d.doc.anchors[n.value]
 		an, ok := d.doc.anchors[n.value]
 		if ok && an.kind != mappingNode {
 		if ok && an.kind != mappingNode {
-			fail(wantMap)
+			failWantMap()
 		}
 		}
 		d.unmarshal(n, out)
 		d.unmarshal(n, out)
 	case sequenceNode:
 	case sequenceNode:
@@ -600,15 +593,15 @@ func (d *decoder) merge(n *node, out reflect.Value) {
 			if ni.kind == aliasNode {
 			if ni.kind == aliasNode {
 				an, ok := d.doc.anchors[ni.value]
 				an, ok := d.doc.anchors[ni.value]
 				if ok && an.kind != mappingNode {
 				if ok && an.kind != mappingNode {
-					fail(wantMap)
+					failWantMap()
 				}
 				}
 			} else if ni.kind != mappingNode {
 			} else if ni.kind != mappingNode {
-				fail(wantMap)
+				failWantMap()
 			}
 			}
 			d.unmarshal(ni, out)
 			d.unmarshal(ni, out)
 		}
 		}
 	default:
 	default:
-		fail(wantMap)
+		failWantMap()
 	}
 	}
 }
 }
 
 

+ 39 - 24
decode_test.go

@@ -1,6 +1,7 @@
 package yaml_test
 package yaml_test
 
 
 import (
 import (
+	"errors"
 	. "gopkg.in/check.v1"
 	. "gopkg.in/check.v1"
 	"gopkg.in/yaml.v1"
 	"gopkg.in/yaml.v1"
 	"math"
 	"math"
@@ -453,15 +454,15 @@ func (s *S) TestUnmarshalNaN(c *C) {
 var unmarshalErrorTests = []struct {
 var unmarshalErrorTests = []struct {
 	data, error string
 	data, error string
 }{
 }{
-	{"v: !!float 'error'", "YAML error: cannot decode !!str `error` as a !!float"},
-	{"v: [A,", "YAML error: line 1: did not find expected node content"},
-	{"v:\n- [A,", "YAML error: line 2: did not find expected node content"},
-	{"a: *b\n", "YAML error: Unknown anchor 'b' referenced"},
-	{"a: &a\n  b: *a\n", "YAML error: Anchor 'a' value contains itself"},
-	{"value: -", "YAML error: block sequence entries are not allowed in this context"},
-	{"a: !!binary ==", "YAML error: !!binary value contains invalid base64 data"},
-	{"{[.]}", `YAML error: invalid map key: \[\]interface \{\}\{"\."\}`},
-	{"{{.}}", `YAML error: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`},
+	{"v: !!float 'error'", "yaml: cannot decode !!str `error` as a !!float"},
+	{"v: [A,", "yaml: line 1: did not find expected node content"},
+	{"v:\n- [A,", "yaml: line 2: did not find expected node content"},
+	{"a: *b\n", "yaml: unknown anchor 'b' referenced"},
+	{"a: &a\n  b: *a\n", "yaml: anchor 'a' value contains itself"},
+	{"value: -", "yaml: block sequence entries are not allowed in this context"},
+	{"a: !!binary ==", "yaml: !!binary value contains invalid base64 data"},
+	{"{[.]}", `yaml: invalid map key: \[\]interface \{\}\{"\."\}`},
+	{"{{.}}", `yaml: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`},
 }
 }
 
 
 func (s *S) TestUnmarshalErrors(c *C) {
 func (s *S) TestUnmarshalErrors(c *C) {
@@ -485,22 +486,22 @@ var setterTests = []struct {
 	{"_: !!foo 'BAR!'", "!!foo", "BAR!"},
 	{"_: !!foo 'BAR!'", "!!foo", "BAR!"},
 }
 }
 
 
-var setterResult = map[int]bool{}
+var setterResult = map[int]error{}
 
 
 type typeWithSetter struct {
 type typeWithSetter struct {
-	tag   string
 	value interface{}
 	value interface{}
 }
 }
 
 
-func (o *typeWithSetter) SetYAML(tag string, value interface{}) (ok bool) {
-	o.tag = tag
-	o.value = value
-	if i, ok := value.(int); ok {
+func (o *typeWithSetter) 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 := setterResult[i]; ok {
 			return result
 			return result
 		}
 		}
 	}
 	}
-	return true
+	return nil
 }
 }
 
 
 type setterPointerType struct {
 type setterPointerType struct {
@@ -516,9 +517,12 @@ func (s *S) TestUnmarshalWithPointerSetter(c *C) {
 		obj := &setterPointerType{}
 		obj := &setterPointerType{}
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		c.Assert(err, IsNil)
 		c.Assert(err, IsNil)
-		c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
-		c.Assert(obj.Field.tag, Equals, item.tag)
-		c.Assert(obj.Field.value, DeepEquals, item.value)
+		if item.value == nil {
+			c.Assert(obj.Field, IsNil)
+		} else {
+			c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
+			c.Assert(obj.Field.value, DeepEquals, item.value)
+		}
 	}
 	}
 }
 }
 
 
@@ -528,7 +532,6 @@ func (s *S) TestUnmarshalWithValueSetter(c *C) {
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		err := yaml.Unmarshal([]byte(item.data), obj)
 		c.Assert(err, IsNil)
 		c.Assert(err, IsNil)
 		c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
 		c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
-		c.Assert(obj.Field.tag, Equals, item.tag)
 		c.Assert(obj.Field.value, DeepEquals, item.value)
 		c.Assert(obj.Field.value, DeepEquals, item.value)
 	}
 	}
 }
 }
@@ -537,15 +540,14 @@ func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) {
 	obj := &typeWithSetter{}
 	obj := &typeWithSetter{}
 	err := yaml.Unmarshal([]byte(setterTests[0].data), obj)
 	err := yaml.Unmarshal([]byte(setterTests[0].data), obj)
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
-	c.Assert(obj.tag, Equals, setterTests[0].tag)
 	value, ok := obj.value.(map[interface{}]interface{})
 	value, ok := obj.value.(map[interface{}]interface{})
-	c.Assert(ok, Equals, true)
+	c.Assert(ok, Equals, true, Commentf("value: %#v", obj.value))
 	c.Assert(value["_"], DeepEquals, setterTests[0].value)
 	c.Assert(value["_"], DeepEquals, setterTests[0].value)
 }
 }
 
 
 func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
 func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
-	setterResult[2] = false
-	setterResult[4] = false
+	setterResult[2] = yaml.ErrMismatch
+	setterResult[4] = yaml.ErrMismatch
 	defer func() {
 	defer func() {
 		delete(setterResult, 2)
 		delete(setterResult, 2)
 		delete(setterResult, 4)
 		delete(setterResult, 4)
@@ -564,6 +566,19 @@ func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
 	c.Assert(m["ghi"].value, Equals, 3)
 	c.Assert(m["ghi"].value, Equals, 3)
 }
 }
 
 
+type failingUnmarshaler struct{}
+
+var failingErr = errors.New("failingErr")
+
+func (ft *failingUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	return failingErr
+}
+
+func (s *S) TestUnmarshalerError(c *C) {
+	err := yaml.Unmarshal([]byte("a: b"), &failingUnmarshaler{})
+	c.Assert(err, Equals, failingErr)
+}
+
 // From http://yaml.org/type/merge.html
 // From http://yaml.org/type/merge.html
 var mergeTests = `
 var mergeTests = `
 anchors:
 anchors:

+ 12 - 11
encode.go

@@ -50,22 +50,23 @@ func (e *encoder) must(ok bool) {
 	if !ok {
 	if !ok {
 		msg := e.emitter.problem
 		msg := e.emitter.problem
 		if msg == "" {
 		if msg == "" {
-			msg = "Unknown problem generating YAML content"
+			msg = "unknown problem generating YAML content"
 		}
 		}
-		fail(msg)
+		failf("%s", msg)
 	}
 	}
 }
 }
 
 
 func (e *encoder) marshal(tag string, in reflect.Value) {
 func (e *encoder) marshal(tag string, in reflect.Value) {
-	var value interface{}
-	if getter, ok := in.Interface().(Getter); ok {
-		tag, value = getter.GetYAML()
-		tag = longTag(tag)
-		if value == nil {
+	if m, ok := in.Interface().(Marshaler); ok {
+		v, err := m.MarshalYAML()
+		if err != nil {
+			fail(err)
+		}
+		if v == nil {
 			e.nilv()
 			e.nilv()
 			return
 			return
 		}
 		}
-		in = reflect.ValueOf(value)
+		in = reflect.ValueOf(v)
 	}
 	}
 	switch in.Kind() {
 	switch in.Kind() {
 	case reflect.Interface:
 	case reflect.Interface:
@@ -105,7 +106,7 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
 	case reflect.Bool:
 	case reflect.Bool:
 		e.boolv(tag, in)
 		e.boolv(tag, in)
 	default:
 	default:
-		panic("Can't marshal type: " + in.Type().String())
+		panic("cannot marshal type: " + in.Type().String())
 	}
 	}
 }
 }
 
 
@@ -215,9 +216,9 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
 			tag = rtag
 			tag = rtag
 			s = rs.(string)
 			s = rs.(string)
 		} else if tag == yaml_BINARY_TAG {
 		} else if tag == yaml_BINARY_TAG {
-			fail("explicitly tagged !!binary data must be base64-encoded")
+			failf("explicitly tagged !!binary data must be base64-encoded")
 		} else {
 		} else {
-			fail("cannot marshal invalid UTF-8 data as " + shortTag(tag))
+			failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
 		}
 		}
 	}
 	}
 	if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {
 	if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {

+ 24 - 42
encode_test.go

@@ -243,9 +243,6 @@ var marshalTests = []struct {
 	}, {
 	}, {
 		map[string]string{"a": strings.Repeat("\x90", 54)},
 		map[string]string{"a": strings.Repeat("\x90", 54)},
 		"a: !!binary |\n  " + strings.Repeat("kJCQ", 17) + "kJ\n  CQ\n",
 		"a: !!binary |\n  " + strings.Repeat("kJCQ", 17) + "kJ\n  CQ\n",
-	}, {
-		map[string]interface{}{"a": typeWithGetter{"!!str", "\x80\x81\x82"}},
-		"a: !!binary gIGC\n",
 	},
 	},
 
 
 	// Ordered maps.
 	// Ordered maps.
@@ -253,12 +250,6 @@ var marshalTests = []struct {
 		&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
 		&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
 		"b: 2\na: 1\nd: 4\nc: 3\nsub:\n  e: 5\n",
 		"b: 2\na: 1\nd: 4\nc: 3\nsub:\n  e: 5\n",
 	},
 	},
-
-	// Escaping of tags.
-	{
-		map[string]interface{}{"a": typeWithGetter{"foo!bar", 1}},
-		"a: !<foo%21bar> 1\n",
-	},
 }
 }
 
 
 func (s *S) TestMarshal(c *C) {
 func (s *S) TestMarshal(c *C) {
@@ -279,12 +270,6 @@ var marshalErrorTests = []struct {
 		inlineB ",inline"
 		inlineB ",inline"
 	}{1, inlineB{2, inlineC{3}}},
 	}{1, inlineB{2, inlineC{3}}},
 	panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
 	panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
-}, {
-	value: typeWithGetter{"!!binary", "\x80"},
-	error: "YAML error: explicitly tagged !!binary data must be base64-encoded",
-}, {
-	value: typeWithGetter{"!!float", "\x80"},
-	error: `YAML error: cannot marshal invalid UTF-8 data as !!float`,
 }}
 }}
 
 
 func (s *S) TestMarshalErrors(c *C) {
 func (s *S) TestMarshalErrors(c *C) {
@@ -298,28 +283,6 @@ func (s *S) TestMarshalErrors(c *C) {
 	}
 	}
 }
 }
 
 
-var marshalTaggedIfaceTest interface{} = &struct{ A string }{"B"}
-
-var getterTests = []struct {
-	data, tag string
-	value     interface{}
-}{
-	{"_:\n  hi: there\n", "", map[interface{}]interface{}{"hi": "there"}},
-	{"_:\n- 1\n- A\n", "", []interface{}{1, "A"}},
-	{"_: 10\n", "", 10},
-	{"_: null\n", "", nil},
-	{"_: !foo BAR!\n", "!foo", "BAR!"},
-	{"_: !foo 1\n", "!foo", "1"},
-	{"_: !foo '\"1\"'\n", "!foo", "\"1\""},
-	{"_: !foo 1.1\n", "!foo", 1.1},
-	{"_: !foo 1\n", "!foo", 1},
-	{"_: !foo 1\n", "!foo", uint(1)},
-	{"_: !foo true\n", "!foo", true},
-	{"_: !foo\n- A\n- B\n", "!foo", []string{"A", "B"}},
-	{"_: !foo\n  A: B\n", "!foo", map[string]string{"A": "B"}},
-	{"_: !foo\n  a: B\n", "!foo", &marshalTaggedIfaceTest},
-}
-
 func (s *S) TestMarshalTypeCache(c *C) {
 func (s *S) TestMarshalTypeCache(c *C) {
 	var data []byte
 	var data []byte
 	var err error
 	var err error
@@ -336,13 +299,23 @@ func (s *S) TestMarshalTypeCache(c *C) {
 	c.Assert(string(data), Equals, "b: 0\n")
 	c.Assert(string(data), Equals, "b: 0\n")
 }
 }
 
 
+var getterTests = []struct {
+	data  string
+	value interface{}
+}{
+	{"_:\n  hi: there\n", map[interface{}]interface{}{"hi": "there"}},
+	{"_:\n- 1\n- A\n", []interface{}{1, "A"}},
+	{"_: 10\n", 10},
+	{"_: null\n", nil},
+	{"_: BAR!\n", "BAR!"},
+}
+
 type typeWithGetter struct {
 type typeWithGetter struct {
-	tag   string
 	value interface{}
 	value interface{}
 }
 }
 
 
-func (o typeWithGetter) GetYAML() (tag string, value interface{}) {
-	return o.tag, o.value
+func (o typeWithGetter) MarshalYAML() (interface{}, error) {
+	return o.value, nil
 }
 }
 
 
 type typeWithGetterField struct {
 type typeWithGetterField struct {
@@ -352,7 +325,6 @@ type typeWithGetterField struct {
 func (s *S) TestMashalWithGetter(c *C) {
 func (s *S) TestMashalWithGetter(c *C) {
 	for _, item := range getterTests {
 	for _, item := range getterTests {
 		obj := &typeWithGetterField{}
 		obj := &typeWithGetterField{}
-		obj.Field.tag = item.tag
 		obj.Field.value = item.value
 		obj.Field.value = item.value
 		data, err := yaml.Marshal(obj)
 		data, err := yaml.Marshal(obj)
 		c.Assert(err, IsNil)
 		c.Assert(err, IsNil)
@@ -362,13 +334,23 @@ func (s *S) TestMashalWithGetter(c *C) {
 
 
 func (s *S) TestUnmarshalWholeDocumentWithGetter(c *C) {
 func (s *S) TestUnmarshalWholeDocumentWithGetter(c *C) {
 	obj := &typeWithGetter{}
 	obj := &typeWithGetter{}
-	obj.tag = ""
 	obj.value = map[string]string{"hello": "world!"}
 	obj.value = map[string]string{"hello": "world!"}
 	data, err := yaml.Marshal(obj)
 	data, err := yaml.Marshal(obj)
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	c.Assert(string(data), Equals, "hello: world!\n")
 	c.Assert(string(data), Equals, "hello: world!\n")
 }
 }
 
 
+type failingMarshaler struct{}
+
+func (ft *failingMarshaler) MarshalYAML() (interface{}, error) {
+	return nil, failingErr
+}
+
+func (s *S) TestMarshalerError(c *C) {
+	_, err := yaml.Marshal(&failingMarshaler{})
+	c.Assert(err, Equals, failingErr)
+}
+
 func (s *S) TestSortedOutput(c *C) {
 func (s *S) TestSortedOutput(c *C) {
 	order := []interface{}{
 	order := []interface{}{
 		false,
 		false,

+ 1 - 2
resolve.go

@@ -2,7 +2,6 @@ package yaml
 
 
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
-	"fmt"
 	"math"
 	"math"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -93,7 +92,7 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
 		case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
 		case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
 			return
 			return
 		}
 		}
-		fail(fmt.Sprintf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)))
+		failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
 	}()
 	}()
 
 
 	// Any data is accepted as a !!str or !!binary.
 	// Any data is accepted as a !!str or !!binary.

+ 37 - 18
yaml.go

@@ -14,22 +14,36 @@ import (
 	"sync"
 	"sync"
 )
 )
 
 
-type yamlError string
+type yamlError struct {
+	err error
+}
+
+func fail(err error) {
+	panic(yamlError{err})
+}
 
 
-func fail(msg string) {
-	panic(yamlError(msg))
+func failf(format string, args ...interface{}) {
+	panic(yamlError{fmt.Errorf("yaml: " + format, args...)})
 }
 }
 
 
 func handleErr(err *error) {
 func handleErr(err *error) {
-	if r := recover(); r != nil {
-		if e, ok := r.(yamlError); ok {
-			*err = errors.New("YAML error: " + string(e))
+	if v := recover(); v != nil {
+		if e, ok := v.(yamlError); ok {
+			*err = e.err
 		} else {
 		} else {
-			panic(r)
+			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.
 // MapSlice encodes and decodes as a YAML map.
 // The order of keys is preserved when encoding and decoding.
 // The order of keys is preserved when encoding and decoding.
 type MapSlice []MapItem
 type MapSlice []MapItem
@@ -39,19 +53,24 @@ type MapItem struct {
 	Key, Value interface{}
 	Key, Value interface{}
 }
 }
 
 
-// The Setter interface may be implemented by types to do their own custom
-// unmarshalling of YAML values, rather than being implicitly assigned by
-// the yaml package machinery. If setting the value works, the method should
-// return true.  If it returns false, the value is considered unsupported
-// and is omitted from maps and slices.
-type Setter interface {
-	SetYAML(tag string, value interface{}) bool
+// The Unmarshaler interface may be implemented by types to customize their
+// behavior when being unmarshaled from a YAML document. The UnmarshalYAML
+// method receives a function that may be called to unmarshal the original
+// YAML value into a field or variable. It is safe to call the unmarshal
+// function parameter more than once if necessary.
+type Unmarshaler interface {
+	UnmarshalYAML(unmarshal func(interface{}) error) error
 }
 }
 
 
-// The Getter interface is implemented by types to do their own custom
-// marshalling into a YAML tag and value.
-type Getter interface {
-	GetYAML() (tag string, value interface{})
+
+// The Marshaler interface may be implemented by types to customize their
+// behavior when being marshaled into a YAML document. The returned value
+// is marshaled in place of the original value implementing Marshaler.
+//
+// If an error is returned by MarshalYAML, the marshaling procedure stops
+// and returns with the provided error.
+type Marshaler interface {
+	MarshalYAML() (interface{}, error)
 }
 }
 
 
 // Unmarshal decodes the first document found within the in byte slice
 // Unmarshal decodes the first document found within the in byte slice