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

feat: expose syntax and type errors #424

Closed
wants to merge 10 commits into from
8 changes: 8 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ func (e *unknownFieldError) Error() string {
return e.err.Error()
}

func (e *unknownFieldError) Unwrap() error {
return e.err
}

func errUnknownField(msg string, tk *token.Token) *unknownFieldError {
return &unknownFieldError{err: errors.ErrSyntax(msg, tk)}
}
Expand All @@ -550,6 +554,10 @@ func (e *duplicateKeyError) Error() string {
return e.err.Error()
}

func (e *duplicateKeyError) Unwrap() error {
return e.err
}

func errDuplicateKey(msg string, tk *token.Token) *duplicateKeyError {
return &duplicateKeyError{err: errors.ErrSyntax(msg, tk)}
}
Expand Down
41 changes: 41 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package yaml

import (
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)

Expand Down Expand Up @@ -60,3 +62,42 @@ func IsInvalidAnchorNameError(err error) bool {
func IsInvalidAliasNameError(err error) bool {
return xerrors.Is(err, ast.ErrInvalidAliasName)
}

// TokenScopedError represents an error associated with a specific [token.Token].
type TokenScopedError struct {
// Msg is the underlying error message.
Msg string
// Token is the [token.Token] associated with this error.
Token *token.Token
// err is the underlying, unwraped error.
err error
}

// Error implements the error interface.
// It returns the unwraped error returned by go-yaml.
func (s TokenScopedError) Error() string {
return s.err.Error()
}

// AsTokenScopedError checks if the error is associated with a specific token.
// If so, it returns
// Otherwise, it returns nil.
func AsTokenScopedError(err error) *TokenScopedError {
var syntaxError *errors.SyntaxError
if xerrors.As(err, &syntaxError) {
return &TokenScopedError{
Msg: syntaxError.GetMessage(),
Token: syntaxError.GetToken(),
err: err,
}
}
var typeError *errors.TypeError
if xerrors.As(err, &typeError) {
return &TokenScopedError{
Msg: typeError.Error(),
Token: typeError.Token,
err: err,
}
}
return nil
}
78 changes: 78 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package yaml

import (
"reflect"
"testing"

"github.com/goccy/go-yaml/internal/errors"
"github.com/goccy/go-yaml/token"
"golang.org/x/xerrors"
)

func TestAsSyntaxError(t *testing.T) {
tests := []struct {
input error
expected *TokenScopedError
}{
{
input: nil,
expected: nil,
},
{
input: xerrors.New("dummy test"),
expected: nil,
},
{
input: errors.ErrSyntax("some error", &token.Token{Value: "123"}),
expected: &TokenScopedError{
Msg: "some error",
Token: &token.Token{Value: "123"},
},
},
{
input: xerrors.Errorf(
"something went wrong: %w",
errors.ErrSyntax("some error", &token.Token{Value: "123"})),
expected: &TokenScopedError{
Msg: "some error",
Token: &token.Token{Value: "123"},
},
},
{
input: errUnknownField("unknown field", &token.Token{Value: "123"}),
expected: &TokenScopedError{
Msg: "unknown field",
Token: &token.Token{Value: "123"},
},
},
{
input: errDuplicateKey("duplicate key", &token.Token{Value: "123"}),
expected: &TokenScopedError{
Msg: "duplicate key",
Token: &token.Token{Value: "123"},
},
},
{
input: errTypeMismatch(reflect.TypeOf("string"), reflect.TypeOf(0), &token.Token{Value: "123"}),
expected: &TokenScopedError{
Msg: "cannot unmarshal int into Go value of type string",
Token: &token.Token{Value: "123"},
},
},
}
for _, test := range tests {
syntaxErr := AsTokenScopedError(test.input)
if test.expected == nil {
if syntaxErr != nil {
t.Fatalf("wanted nil, but go %v", syntaxErr)
}
continue
}
if syntaxErr == nil {
t.Fatalf("must not be nil")
}
if *test.expected.Token != *syntaxErr.Token || test.expected.Msg != syntaxErr.Msg {
t.Fatalf("unexpected output.\nexpect:\n[%v]\nactual:\n[%v]", test.expected, syntaxErr)
}
}
}
22 changes: 15 additions & 7 deletions internal/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func Wrapf(err error, msg string, args ...interface{}) error {
}

// ErrSyntax create syntax error instance with message and token
func ErrSyntax(msg string, tk *token.Token) *syntaxError {
return &syntaxError{
func ErrSyntax(msg string, tk *token.Token) *SyntaxError {
return &SyntaxError{
baseError: &baseError{},
msg: msg,
token: tk,
Expand All @@ -54,7 +54,7 @@ func (e *baseError) chainStateAndVerb(err error) {
wrapErr.state = e.state
wrapErr.verb = e.verb
}
syntaxErr, ok := err.(*syntaxError)
syntaxErr, ok := err.(*SyntaxError)
if ok {
syntaxErr.state = e.state
syntaxErr.verb = e.verb
Expand Down Expand Up @@ -164,18 +164,18 @@ func (e *wrapError) Error() string {
return buf.String()
}

type syntaxError struct {
type SyntaxError struct {
*baseError
msg string
token *token.Token
frame xerrors.Frame
}

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

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

var colored, inclSource bool
Expand All @@ -199,6 +199,14 @@ func (e *syntaxError) FormatError(p xerrors.Printer) error {
return nil
}

func (e *SyntaxError) GetToken() *token.Token {
return e.token
}

func (e *SyntaxError) GetMessage() string {
return e.msg
}

type PrettyPrinter interface {
PrettyPrint(xerrors.Printer, bool, bool) error
}
Expand All @@ -216,7 +224,7 @@ func (es *Sink) Detail() bool {
return false
}

func (e *syntaxError) Error() string {
func (e *SyntaxError) Error() string {
var buf bytes.Buffer
e.PrettyPrint(&Sink{&buf}, defaultColorize, defaultIncludeSource)
return buf.String()
Expand Down
Loading
Loading