Skip to content

Commit

Permalink
feature: joining errors
Browse files Browse the repository at this point in the history
  • Loading branch information
strider2038 committed Aug 5, 2023
1 parent d486efa commit 7fed68b
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 25 deletions.
8 changes: 7 additions & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,13 @@ func (e *stacked) Format(s fmt.State, verb rune) {
func (e *stacked) MarshalJSON() ([]byte, error) {
data := mapWriter{"error": e.Error()}
data.SetStackTrace(e.StackTrace())
e.LogFields(data)

var err error
for err = e; err != nil; err = Unwrap(err) {
if loggable, ok := err.(LoggableError); ok {
loggable.LogFields(data)
}
}

return json.Marshal(data)
}
Expand Down
40 changes: 20 additions & 20 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,129 +24,129 @@ func TestStackTrace(t *testing.T) {
err: errors.Errorf("ooh"),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:23",
"\t.+/errors/errors_test.go:24",
},
},
{
name: "Wrap(Error())",
err: errors.Wrap(errors.Errorf("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:31",
"\t.+/errors/errors_test.go:32",
},
},
{
name: "Wrap(New())",
err: errors.Wrap(errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:39",
"\t.+/errors/errors_test.go:40",
},
},
{
name: "Wrap(Wrap(New()))",
err: errors.Wrap(errors.Wrap(errors.New("ooh"))),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:47",
"\t.+/errors/errors_test.go:48",
},
},
{
name: "Errorf()",
err: errors.Errorf("ooh"),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:55",
"\t.+/errors/errors_test.go:56",
},
},
{
name: `Errorf("%w", New())`,
err: errors.Errorf("%v", errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:63",
"\t.+/errors/errors_test.go:64",
},
},
{
name: `Errorf("%w", Wrap(New()))`,
err: errors.Errorf("%w", errors.Wrap(errors.New("ooh"))),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:71",
"\t.+/errors/errors_test.go:72",
},
},
{
name: `Errorf("%%w %v", Wrap(New()))`,
err: errors.Errorf("%%w %v", errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:79",
"\t.+/errors/errors_test.go:80",
},
},
{
name: `Errorf("%s: %w", Wrap(New()))`,
err: errors.Errorf("%s: %w", "prefix", errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:87",
"\t.+/errors/errors_test.go:88",
},
},
{
name: `Errorf("%w", Errorf("%w", New()))`,
err: errors.Errorf("%w", errors.Errorf("%w", errors.New("ooh"))),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:95",
"\t.+/errors/errors_test.go:96",
},
},
{
name: `Errorf("%w", fmt.Errorf("%w", Error()))`,
err: errors.Errorf("%w", fmt.Errorf("%w", errors.New("ooh"))),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:103",
"\t.+/errors/errors_test.go:104",
},
},
{
name: `wrap with New()`,
err: wrap(errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.wrap\n" +
"\t.+/errors/errors_test.go:200",
"\t.+/errors/errors_test.go:201",
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:111",
"\t.+/errors/errors_test.go:112",
},
},
{
name: `wrap skip caller`,
err: wrapSkipCaller(errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:121",
"\t.+/errors/errors_test.go:122",
},
},
{
name: `wrap skip callers`,
err: wrapSkipCallers(errors.New("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:129",
"\t.+/errors/errors_test.go:130",
},
},
{
name: `errorf skip caller`,
err: errorfSkipCaller("ooh"),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:137",
"\t.+/errors/errors_test.go:138",
},
},
{
name: "join one std error",
err: errors.Join(fmt.Errorf("ooh")),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:145",
"\t.+/errors/errors_test.go:146",
},
},
{
Expand All @@ -157,7 +157,7 @@ func TestStackTrace(t *testing.T) {
),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:153",
"\t.+/errors/errors_test.go:154",
},
},
{
Expand All @@ -167,7 +167,7 @@ func TestStackTrace(t *testing.T) {
),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:165",
"\t.+/errors/errors_test.go:166",
},
},
{
Expand All @@ -178,7 +178,7 @@ func TestStackTrace(t *testing.T) {
),
want: []string{
"github.com/muonsoft/errors_test.TestStackTrace\n" +
"\t.+/errors/errors_test.go:174",
"\t.+/errors/errors_test.go:175",
},
},
}
Expand Down
17 changes: 16 additions & 1 deletion joining.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ type joinError struct {
errs []error
}

// todo: add marshal json?
func (e *joinError) LogFields(logger FieldLogger) {
logFieldsFromErrors(logger, e.errs)
}

func (e *joinError) Error() string {
var b []byte
Expand All @@ -69,3 +71,16 @@ func (e *joinError) Error() string {
func (e *joinError) Unwrap() []error {
return e.errs
}

func logFieldsFromErrors(logger FieldLogger, errs []error) {
for _, err := range errs {
for w := err; w != nil; w = Unwrap(w) {
if j, ok := w.(interface{ Unwrap() []error }); ok {
logFieldsFromErrors(logger, j.Unwrap())
}
if loggable, ok := w.(LoggableError); ok {
loggable.LogFields(logger)
}
}
}
}
64 changes: 62 additions & 2 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package errors_test

import (
"encoding/json"
stderrors "errors"
"testing"

"github.com/muonsoft/errors"
Expand All @@ -27,7 +28,7 @@ func TestStackedError_MarshalJSON(t *testing.T) {
{
Function: "github.com/muonsoft/errors_test.TestStackedError_MarshalJSON",
File: ".+/errors/json_test.go",
Line: 12,
Line: 13,
},
})
if jsonError.Key != "value" {
Expand Down Expand Up @@ -57,7 +58,7 @@ func TestWrappedError_MarshalJSON(t *testing.T) {
{
Function: "github.com/muonsoft/errors_test.TestWrappedError_MarshalJSON",
File: ".+/errors/json_test.go",
Line: 40,
Line: 41,
},
})
if jsonError.Key != "value" {
Expand All @@ -68,6 +69,65 @@ func TestWrappedError_MarshalJSON(t *testing.T) {
}
}

func TestJoinedError_MarshalJSON(t *testing.T) {
err := errors.Join(
errors.Join(
errors.Wrap(
errors.Errorf("error 1", errors.String("key1", "value1")),
errors.String("key2", "value2"),
),
errors.Errorf("error 2", errors.String("key3", "value3")),
stderrors.Join(
errors.Errorf("error 3", errors.String("key4", "value4")),
errors.Errorf("error 4", errors.String("key5", "value5")),
),
),
)
jsonData, e := json.Marshal(err)
if e != nil {
t.Fatalf("expected %#v to be marshalable into json: %v", err, e)
}
var jsonError struct {
Error string `json:"error"`
StackTrace errorstest.StackTrace `json:"stackTrace"`
Key1 string `json:"key1"`
Key2 string `json:"key2"`
Key3 string `json:"key3"`
Key4 string `json:"key4"`
Key5 string `json:"key5"`
}
e = json.Unmarshal(jsonData, &jsonError)
if e != nil {
t.Fatalf("failed to unmarshal json: %v", e)
}

if jsonError.Error != "error 1\nerror 2\nerror 3\nerror 4" {
t.Errorf("expected %#v to have error key", err)
}
assertStackRegexp(t, jsonError.StackTrace, errorstest.StackTrace{
{
Function: "github.com/muonsoft/errors_test.TestJoinedError_MarshalJSON",
File: ".+/errors/json_test.go",
Line: 74,
},
})
if jsonError.Key1 != "value1" {
t.Errorf(`expected %#v to have key "key1"`, err)
}
if jsonError.Key2 != "value2" {
t.Errorf(`expected %#v to have key "key2"`, err)
}
if jsonError.Key3 != "value3" {
t.Errorf(`expected %#v to have key "key3"`, err)
}
if jsonError.Key4 != "value4" {
t.Errorf(`expected %#v to have key "key4"`, err)
}
if jsonError.Key5 != "value5" {
t.Errorf(`expected %#v to have key "key5"`, err)
}
}

type JSONError struct {
Error string `json:"error"`
StackTrace errorstest.StackTrace `json:"stackTrace"`
Expand Down
2 changes: 1 addition & 1 deletion logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestLog_errorWithStack(t *testing.T) {
{
Function: "github.com/muonsoft/errors_test.TestLog_errorWithStack",
File: ".+errors/logging_test.go",
Line: 29,
Line: 30,
},
})
logger.AssertField(t, "key", "value")
Expand Down

0 comments on commit 7fed68b

Please sign in to comment.