Skip to content

Commit

Permalink
yaml: typeError implements PrettyPrinter interface
Browse files Browse the repository at this point in the history
When unmarshalling YAML and encountering an error in decoded that
results in a `yaml.typeError`, the error can't have the same nice
formatting because it doesn't implement the `errors.PrettyPrinter`
interface. This adds a field to the `yaml.typeError` struct that
specifies a token which is used in the implementation of the
PrettyPrinter interface. The tests were updated to instead match on
expected message substrings instead of full equality.
  • Loading branch information
braydonk committed Feb 9, 2022
1 parent e4f32a2 commit dad2ded
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 46 deletions.
45 changes: 35 additions & 10 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/parser"
"github.com/goccy/go-yaml/printer"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)
Expand Down Expand Up @@ -367,10 +368,10 @@ func (d *Decoder) fileToNode(f *ast.File) ast.Node {
return nil
}

func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type) (reflect.Value, error) {
func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) (reflect.Value, error) {
if typ.Kind() != reflect.String {
if !v.Type().ConvertibleTo(typ) {
return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken())
}
return v.Convert(typ), nil
}
Expand All @@ -386,7 +387,7 @@ func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type) (reflect.Value
return reflect.ValueOf(fmt.Sprint(v.Bool())), nil
}
if !v.Type().ConvertibleTo(typ) {
return reflect.Zero(typ), errTypeMismatch(typ, v.Type())
return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken())
}
return v.Convert(typ), nil
}
Expand All @@ -408,6 +409,7 @@ type typeError struct {
dstType reflect.Type
srcType reflect.Type
structFieldName *string
token *token.Token
}

func (e *typeError) Error() string {
Expand All @@ -417,8 +419,31 @@ func (e *typeError) Error() string {
return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.srcType, e.dstType)
}

func errTypeMismatch(dstType, srcType reflect.Type) *typeError {
return &typeError{dstType: dstType, srcType: srcType}
func (e *typeError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&errors.FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
}

func (e *typeError) FormatError(p xerrors.Printer) error {
var pp printer.Printer

var colored, inclSource bool
if fep, ok := p.(*errors.FormatErrorPrinter); ok {
colored = fep.Colored
inclSource = fep.InclSource
}

pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column)
msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.Error()), colored)
if inclSource {
msg += "\n" + pp.PrintErrorToken(e.token, colored)
}
p.Print(msg)

return nil
}

func errTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *typeError {
return &typeError{dstType: dstType, srcType: srcType, token: token}
}

type unknownFieldError struct {
Expand Down Expand Up @@ -709,7 +734,7 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
return nil
}
default:
return errTypeMismatch(valueType, reflect.TypeOf(v))
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
return errOverflow(valueType, fmt.Sprint(v))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
Expand All @@ -731,13 +756,13 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
return nil
}
default:
return errTypeMismatch(valueType, reflect.TypeOf(v))
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
}
return errOverflow(valueType, fmt.Sprint(v))
}
v := reflect.ValueOf(d.nodeToValue(src))
if v.IsValid() {
convertedValue, err := d.convertValue(v, dst.Type())
convertedValue, err := d.convertValue(v, dst.Type(), src)
if err != nil {
return errors.Wrapf(err, "failed to convert value")
}
Expand Down Expand Up @@ -905,7 +930,7 @@ func (d *Decoder) castToTime(src ast.Node) (time.Time, error) {
}
s, ok := v.(string)
if !ok {
return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v))
return time.Time{}, errTypeMismatch(reflect.TypeOf(time.Time{}), reflect.TypeOf(v), src.GetToken())
}
for _, format := range allowedTimestampFormats {
t, err := time.Parse(format, s)
Expand Down Expand Up @@ -937,7 +962,7 @@ func (d *Decoder) castToDuration(src ast.Node) (time.Duration, error) {
}
s, ok := v.(string)
if !ok {
return 0, errTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v))
return 0, errTypeMismatch(reflect.TypeOf(time.Duration(0)), reflect.TypeOf(v), src.GetToken())
}
t, err := time.ParseDuration(s)
if err != nil {
Expand Down
48 changes: 24 additions & 24 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,8 +1074,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field T.A of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
t.Run("string to bool", func(t *testing.T) {
Expand All @@ -1085,8 +1085,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field T.D of type bool"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
t.Run("string to int at inline", func(t *testing.T) {
Expand All @@ -1096,8 +1096,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go struct field U.T.A of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
})
Expand All @@ -1109,8 +1109,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go value of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if len(v) == 0 || len(v["v"]) == 0 {
t.Fatal("failed to decode value")
Expand All @@ -1126,8 +1126,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal string into Go value of type int"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if len(v) == 0 || len(v["v"]) == 0 {
t.Fatal("failed to decode value")
Expand All @@ -1145,8 +1145,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal -42 into Go value of type uint ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
Expand All @@ -1159,8 +1159,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal -4294967296 into Go value of type uint64 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
Expand All @@ -1173,8 +1173,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal 4294967297 into Go value of type int32 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
Expand All @@ -1187,8 +1187,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal 128 into Go value of type int8 ( overflow )"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
if v["v"] != 0 {
t.Fatal("failed to decode value")
Expand All @@ -1207,8 +1207,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal uint64 into Go struct field T.A of type time.Time"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
t.Run("string to duration", func(t *testing.T) {
Expand All @@ -1218,8 +1218,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := `time: invalid duration "str"`
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
t.Run("int to duration", func(t *testing.T) {
Expand All @@ -1229,8 +1229,8 @@ func TestDecoder_TypeConversionError(t *testing.T) {
t.Fatal("expected to error")
}
msg := "cannot unmarshal uint64 into Go struct field T.B of type time.Duration"
if err.Error() != msg {
t.Fatalf("unexpected error message: %s. expect: %s", err.Error(), msg)
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected error message: %s to contain: %s", err.Error(), msg)
}
})
})
Expand Down
24 changes: 12 additions & 12 deletions internal/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ type wrapError struct {
frame xerrors.Frame
}

type myprinter struct {
type FormatErrorPrinter struct {
xerrors.Printer
colored bool
inclSource bool
Colored bool
InclSource bool
}

func (e *wrapError) As(target interface{}) bool {
Expand All @@ -90,15 +90,15 @@ func (e *wrapError) Unwrap() error {
}

func (e *wrapError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&myprinter{Printer: p, colored: colored, inclSource: inclSource})
return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
}

func (e *wrapError) FormatError(p xerrors.Printer) error {
if _, ok := p.(*myprinter); !ok {
p = &myprinter{
if _, ok := p.(*FormatErrorPrinter); !ok {
p = &FormatErrorPrinter{
Printer: p,
colored: defaultColorize,
inclSource: defaultIncludeSource,
Colored: defaultColorize,
InclSource: defaultIncludeSource,
}
}
if e.verb == 'v' && e.state.Flag('+') {
Expand Down Expand Up @@ -171,16 +171,16 @@ type syntaxError struct {
}

func (e *syntaxError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
return e.FormatError(&myprinter{Printer: p, colored: colored, inclSource: inclSource})
return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
}

func (e *syntaxError) FormatError(p xerrors.Printer) error {
var pp printer.Printer

var colored, inclSource bool
if mp, ok := p.(*myprinter); ok {
colored = mp.colored
inclSource = mp.inclSource
if fep, ok := p.(*FormatErrorPrinter); ok {
colored = fep.Colored
inclSource = fep.InclSource
}

pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column)
Expand Down

0 comments on commit dad2ded

Please sign in to comment.