Skip to content

Commit

Permalink
separate types for UnmarshalError
Browse files Browse the repository at this point in the history
  • Loading branch information
lovromazgon committed Dec 3, 2022
1 parent df93c53 commit 456dd73
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 41 deletions.
32 changes: 4 additions & 28 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,19 +341,7 @@ func (d *decoder) terror(n *Node, tag string, out reflect.Value) {
if n.Tag != "" {
tag = n.Tag
}
value := n.Value
if tag != seqTag && tag != mapTag {
if len(value) > 10 {
value = " `" + value[:7] + "...`"
} else {
value = " `" + value + "`"
}
}
d.terrors = append(d.terrors, UnmarshalError{
Message: fmt.Sprintf("cannot unmarshal %s%s into %s", shortTag(tag), value, out.Type()),
Line: n.Line,
Column: n.Column,
})
d.terrors = append(d.terrors, NewInvalidTypeError(n.Line, n.Column, tag, n.Value, out.Type()))
}

func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) {
Expand Down Expand Up @@ -767,11 +755,7 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
for j := i + 2; j < l; j += 2 {
nj := n.Content[j]
if ni.Kind == nj.Kind && ni.Value == nj.Value {
d.terrors = append(d.terrors, UnmarshalError{
Message: fmt.Sprintf("mapping key %#v already defined at line %d", nj.Value, ni.Line),
Line: nj.Line,
Column: nj.Column,
})
d.terrors = append(d.terrors, NewDuplicateMappingKeyError(nj.Line, nj.Column, nj.Value, ni.Line))
}
}
}
Expand Down Expand Up @@ -891,11 +875,7 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
if info, ok := sinfo.FieldsMap[name.String()]; ok {
if d.uniqueKeys {
if doneFields[info.Id] {
d.terrors = append(d.terrors, UnmarshalError{
Message: fmt.Sprintf("field %s already set in type %s", name.String(), out.Type()),
Line: ni.Line,
Column: ni.Column,
})
d.terrors = append(d.terrors, NewFieldAlreadySetError(ni.Line, ni.Column, name.String(), out.Type()))
continue
}
doneFields[info.Id] = true
Expand All @@ -915,11 +895,7 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
d.unmarshal(n.Content[i+1], value)
inlineMap.SetMapIndex(name, value)
} else if d.knownFields {
d.terrors = append(d.terrors, UnmarshalError{
Message: fmt.Sprintf("field %s not found in type %s", name.String(), out.Type()),
Line: ni.Line,
Column: ni.Column,
})
d.terrors = append(d.terrors, NewUnknownFieldError(ni.Line, ni.Column, name.String(), out.Type()))
}
}
return true
Expand Down
16 changes: 8 additions & 8 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1123,8 +1123,8 @@ func (s *S) TestUnmarshalerWholeDocument(c *C) {
}

func (s *S) TestUnmarshalerTypeError(c *C) {
unmarshalerResult[2] = &yaml.TypeError{[]yaml.UnmarshalError{{"foo", 1, 1}}}
unmarshalerResult[4] = &yaml.TypeError{[]yaml.UnmarshalError{{"bar", 1, 1}}}
unmarshalerResult[2] = &yaml.TypeError{[]yaml.UnmarshalError{yaml.NewInvalidTypeError(1, 2, "foo", "bar", reflect.TypeOf(0))}}
unmarshalerResult[4] = &yaml.TypeError{[]yaml.UnmarshalError{yaml.NewInvalidTypeError(1, 2, "bar", "baz", reflect.TypeOf(true))}}
defer func() {
delete(unmarshalerResult, 2)
delete(unmarshalerResult, 4)
Expand All @@ -1141,8 +1141,8 @@ func (s *S) TestUnmarshalerTypeError(c *C) {
c.Assert(err, ErrorMatches, ""+
"yaml: unmarshal errors:\n"+
" line 1: cannot unmarshal !!str `A` into int\n"+
" line 1: foo\n"+
" line 1: bar\n"+
" line 1: cannot unmarshal foo `bar` into int\n"+
" line 1: cannot unmarshal bar `baz` into bool\n"+
" line 1: cannot unmarshal !!str `B` into int")
c.Assert(v.M["abc"], NotNil)
c.Assert(v.M["def"], IsNil)
Expand All @@ -1154,8 +1154,8 @@ func (s *S) TestUnmarshalerTypeError(c *C) {
}

func (s *S) TestObsoleteUnmarshalerTypeError(c *C) {
unmarshalerResult[2] = &yaml.TypeError{[]yaml.UnmarshalError{{"foo", 1, 1}}}
unmarshalerResult[4] = &yaml.TypeError{[]yaml.UnmarshalError{{"bar", 1, 1}}}
unmarshalerResult[2] = &yaml.TypeError{[]yaml.UnmarshalError{yaml.NewInvalidTypeError(1, 2, "foo", "bar", reflect.TypeOf(0))}}
unmarshalerResult[4] = &yaml.TypeError{[]yaml.UnmarshalError{yaml.NewInvalidTypeError(1, 2, "bar", "baz", reflect.TypeOf(true))}}
defer func() {
delete(unmarshalerResult, 2)
delete(unmarshalerResult, 4)
Expand All @@ -1172,8 +1172,8 @@ func (s *S) TestObsoleteUnmarshalerTypeError(c *C) {
c.Assert(err, ErrorMatches, ""+
"yaml: unmarshal errors:\n"+
" line 1: cannot unmarshal !!str `A` into int\n"+
" line 1: foo\n"+
" line 1: bar\n"+
" line 1: cannot unmarshal foo `bar` into int\n"+
" line 1: cannot unmarshal bar `baz` into bool\n"+
" line 1: cannot unmarshal !!str `B` into int")
c.Assert(v.M["abc"], NotNil)
c.Assert(v.M["def"], IsNil)
Expand Down
146 changes: 141 additions & 5 deletions yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,146 @@ func (e *ParserError) Error() string {
}

// UnmarshalError is each error with a source position found by Unmarshal.
type UnmarshalError struct {
Message string
Line int
Column int
type UnmarshalError interface {
error
// Line returns the line number that triggered the error.
Line() int
// Column returns the column number that triggered the error.
Column() int
}

type baseUnmarshalError struct {
line int
column int
}

// Line returns the line number that triggered the error.
func (e *baseUnmarshalError) Line() int { return e.line }
// Column returns the column number that triggered the error.
func (e *baseUnmarshalError) Column() int { return e.column }

// InvalidTypeError is an UnmarshalError which is created when a yaml value
// could not be decoded into a Go type.
type InvalidTypeError struct {
baseUnmarshalError
tag string
value string
rtype reflect.Type
}

// NewInvalidTypeError creates an InvalidTypeError.
func NewInvalidTypeError(line, column int, tag string, value string, rtype reflect.Type) *InvalidTypeError {
return &InvalidTypeError{
baseUnmarshalError: baseUnmarshalError{
line: line,
column: column,
},
tag: tag,
value: value,
rtype: rtype,
}
}

// Tag returns the raw yaml tag.
func (e *InvalidTypeError) Tag() string { return e.tag }
// Value returns the raw yaml value.
func (e *InvalidTypeError) Value() string { return e.value }
// Type returns the Go type of the target field.
func (e *InvalidTypeError) Type() reflect.Type { return e.rtype }
func (e *InvalidTypeError) Error() string {
value := e.value
if e.tag != seqTag && e.tag != mapTag {
if len(value) > 10 {
value = " `" + value[:7] + "...`"
} else {
value = " `" + value + "`"
}
}
return fmt.Sprintf("cannot unmarshal %s%s into %s", shortTag(e.tag), value, e.rtype)
}

// DuplicateMappingKeyError is an UnmarshalError which is created when a yaml
// mapping contains duplicated keys.
type DuplicateMappingKeyError struct {
baseUnmarshalError
key string
duplicateLine int
}

// NewDuplicateMappingKeyError creates an DuplicateMappingKeyError.
func NewDuplicateMappingKeyError(line, column int, key string, duplicateLine int) *DuplicateMappingKeyError {
return &DuplicateMappingKeyError{
baseUnmarshalError: baseUnmarshalError{
line: line,
column: column,
},
key: key,
duplicateLine: duplicateLine,
}
}

// Key returns the duplicated key.
func (e *DuplicateMappingKeyError) Key() string { return e.key }
// DuplicateLine returns the line number where the key is duplicated.
func (e *DuplicateMappingKeyError) DuplicateLine() int { return e.duplicateLine }
func (e *DuplicateMappingKeyError) Error() string {
return fmt.Sprintf("mapping key %#v already defined at line %d", e.key, e.duplicateLine)
}

// FieldAlreadySetError is an UnmarshalError which is created when a yaml field
// was already mapped to the target Go type.
type FieldAlreadySetError struct {
baseUnmarshalError
field string
rtype reflect.Type
}

// NewFieldAlreadySetError creates a FieldAlreadySetError.
func NewFieldAlreadySetError(line, column int, field string, rtype reflect.Type) *FieldAlreadySetError {
return &FieldAlreadySetError{
baseUnmarshalError: baseUnmarshalError{
line: line,
column: column,
},
field: field,
rtype: rtype,
}
}

// Field returns the yaml field.
func (e *FieldAlreadySetError) Field() string { return e.field }
// Type returns the target Go type.
func (e *FieldAlreadySetError) Type() reflect.Type { return e.rtype }
func (e *FieldAlreadySetError) Error() string {
return fmt.Sprintf("field %s already set in type %s", e.field, e.rtype)
}

// UnknownFieldError is an UnmarshalError which is created when a yaml field is
// not found in the target Go type.
type UnknownFieldError struct {
baseUnmarshalError
field string
rtype reflect.Type
}

// NewUnknownFieldError creates a UnknownFieldError.
func NewUnknownFieldError(line, column int, field string, rtype reflect.Type) *UnknownFieldError {
return &UnknownFieldError{
baseUnmarshalError: baseUnmarshalError{
line: line,
column: column,
},
field: field,
rtype: rtype,
}
}

// Field returns the yaml field.
func (e *UnknownFieldError) Field() string { return e.field }
// Type returns the target Go type.
func (e *UnknownFieldError) Type() reflect.Type { return e.rtype }
func (e *UnknownFieldError) Error() string {
return fmt.Sprintf("field %s not found in type %s", e.field, e.rtype)
}

// A TypeError is returned by Unmarshal when one or more fields in
Expand All @@ -349,7 +485,7 @@ func (e *TypeError) Error() string {
var b strings.Builder
b.WriteString("yaml: unmarshal errors:")
for _, err := range e.Errors {
b.WriteString(fmt.Sprintf("\n line %d: %s", err.Line, err.Message))
b.WriteString(fmt.Sprintf("\n line %d: %s", err.Line(), err.Error()))
}
return b.String()
}
Expand Down

0 comments on commit 456dd73

Please sign in to comment.