Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

yaml: typeError implements PrettyPrinter interface #280

Merged
merged 2 commits into from May 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 35 additions & 10 deletions decode.go
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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also considered moving typeError to the internal/errors package, which is where the other PrettyPrinter errors are. I left it here for now, but am interested if you would prefer this or the alternative.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you say, it seems better to move to internal/errors so that the processing can be standardized. Could you please ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can!

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
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
Expand Up @@ -67,10 +67,10 @@ type wrapError struct {
frame xerrors.Frame
}

type myprinter struct {
type FormatErrorPrinter struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seemed like a nicer solution than declaring another identical type in decode.go for the printer struct, but I don't have to do it this way if that's preferred.

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