Skip to content

Commit

Permalink
joinederr package for unwrapping after errors.Join (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvndaai authored Sep 29, 2023
1 parent 1d8b4dc commit e849e02
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.18.x]
go-version: [1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
13 changes: 8 additions & 5 deletions ctxerr.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ import (
"path/filepath"
"runtime"
"strings"

"github.com/mvndaai/ctxerr/joinederr"
)

var global Instance
Expand Down Expand Up @@ -378,7 +380,9 @@ func (in Instance) AllFields(err error) map[string]any {
fieldFuncs = append(fieldFuncs, DefaultFieldsFunc)
}

iter := joinederr.NewDepthFirstIterator(err)
for {
err = iter.Next()
if err == nil {
return f
}
Expand All @@ -403,7 +407,6 @@ func (in Instance) AllFields(err error) map[string]any {
}
f[k] = v
}
err = errors.Unwrap(err)
}
}

Expand All @@ -415,7 +418,9 @@ func (in Instance) HasField(err error, field string) bool {
fieldFuncs = append(fieldFuncs, DefaultFieldsFunc)
}

iter := joinederr.NewDepthFirstIterator(err)
for {
err = iter.Next()
if err == nil {
return false
}
Expand All @@ -430,8 +435,6 @@ func (in Instance) HasField(err error, field string) bool {
if _, ok := fields[field]; ok {
return true
}

err = errors.Unwrap(err)
}
}

Expand All @@ -457,7 +460,9 @@ func (in Instance) HasCategory(err error, category any) bool {
fieldFuncs = append(fieldFuncs, DefaultFieldsFunc)
}

iter := joinederr.NewDepthFirstIterator(err)
for {
err = iter.Next()
if err == nil {
return false
}
Expand All @@ -474,8 +479,6 @@ func (in Instance) HasCategory(err error, category any) bool {
return true
}
}

err = errors.Unwrap(err)
}
}

Expand Down
45 changes: 45 additions & 0 deletions ctxerr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1084,3 +1084,48 @@ func TestAddFieldsFuncs(t *testing.T) {
func TestGlobaTestAddFieldsFuncs(t *testing.T) {
ctxerr.AddFieldsFunc(func(_ error) map[string]any { return nil })
}

func TestJoined(t *testing.T) {
actx := ctxerr.SetField(context.Background(), "a", "a")
actx = ctxerr.SetCategory(actx, "cat_a")
a := ctxerr.New(actx, "CODE_A", "msg_a")

bctx := ctxerr.SetField(context.Background(), "b", "b")
bctx = ctxerr.SetCategory(bctx, "cat_b")
b := ctxerr.New(bctx, "CODE_B", "msg_b")

cctx := ctxerr.SetField(context.Background(), "c", "c")
c := ctxerr.Wrap(cctx, errors.Join(a, b), "CODE_C", "msg_c")

if !ctxerr.HasCategory(c, "cat_a") {
t.Error("missing category cat_a")
}
if !ctxerr.HasCategory(c, "cat_b") {
t.Error("missing category cat_b")
}

if !ctxerr.HasField(c, "a") {
t.Error("missing field a")
}
if !ctxerr.HasField(c, "b") {
t.Error("missing field b")
}

f := ctxerr.AllFields(c)
expectedFields := map[string]any{
"a": "a",
"b": "b",
"c": "c",
"error_code": "CODE_B",
"error_category": "cat_b",
"error_location": []any{
"ctxerr_test.TestJoined",
"ctxerr_test.TestJoined",
"ctxerr_test.TestJoined",
},
}

if !reflect.DeepEqual(f, expectedFields) {
t.Errorf("fields didn't match \n%#v\n%#v", f, expectedFields)
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/mvndaai/ctxerr

go 1.18
go 1.20
54 changes: 54 additions & 0 deletions joinederr/joinederr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package joinederr

type ErrorIterator interface {
Next() error
HasNext() bool
}

type depthFirstUnwrapper struct {
next error
nextParent []error
}

func (bfu *depthFirstUnwrapper) Next() error {
if bfu.next == nil {
return nil
}

// Split joined errors
if x, ok := bfu.next.(interface{ Unwrap() []error }); ok {
errs := x.Unwrap()
if len(errs) > 0 {
bfu.next = errs[0]
bfu.nextParent = append(errs[1:], bfu.nextParent...)
}
}

// Set return value
r := bfu.next

// Setup next return value
bfu.next = nil

if x, ok := r.(interface{ Unwrap() error }); ok {
bfu.next = x.Unwrap()
}

if bfu.next == nil {
if len(bfu.nextParent) > 0 {
bfu.next = bfu.nextParent[0]
bfu.nextParent = bfu.nextParent[1:]
}
}

// Return
return r
}

func (bfu *depthFirstUnwrapper) HasNext() bool {
return bfu.next != nil
}

func NewDepthFirstIterator(err error) ErrorIterator {
return &depthFirstUnwrapper{next: err}
}
71 changes: 71 additions & 0 deletions joinederr/joinederr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package joinederr_test

import (
"errors"
"fmt"
"strings"
"testing"

"github.com/mvndaai/ctxerr/joinederr"
)

func TestBreadthFirst(t *testing.T) {
/*
a
/ \
/ \
b f
/ \ |
c e g
| / \
d h i
*/

i := errors.New("i")
h := errors.New("h")
g := fmt.Errorf("g\n%w", errors.Join(h, i))
f := fmt.Errorf("f\n%w", g)
d := errors.New("d")
c := fmt.Errorf("c\n%w", d)
e := errors.New("e")
b := fmt.Errorf("b\n%w", errors.Join(c, e))
a := fmt.Errorf("a\n%w", errors.Join(b, f))

msgs := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}
actualMsg := a.Error()
expectedMessage := strings.Join(msgs, "\n")
if actualMsg != expectedMessage {
t.Errorf("message did not match\n%#v\n%#v", actualMsg, expectedMessage)
}

expectedTrees := [][]string{
{"a", "b", "c", "d", "e", "f", "g", "h", "i"},
{"b", "c", "d", "e"},
{"c", "d"},
{"d"},
{"e"},
{"f", "g", "h", "i"},
{"g", "h", "i"},
{"h"},
{"i"},
}
var expectTreeCount int

iter := joinederr.NewDepthFirstIterator(a)
for iter.HasNext() {
actualMsg := strings.ReplaceAll(iter.Next().Error(), "\n", ":")
expectedMessage := strings.Join(expectedTrees[expectTreeCount], ":")
if actualMsg != expectedMessage {
t.Errorf("message [%v] did not match\n%#v\n%#v", expectTreeCount, actualMsg, expectedMessage)
}
expectTreeCount++
}

if expectTreeCount != len(expectedTrees) {
t.Errorf("stopped iterating [%v] setps early", len(expectedTrees)-expectTreeCount)
}

if iter.Next() != nil {
t.Error("this there should be nothing left")
}
}

0 comments on commit e849e02

Please sign in to comment.