Skip to content

Commit

Permalink
Add errors package with metadata support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravi Atluri committed Nov 29, 2024
1 parent 599750f commit dbfa350
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
5 changes: 5 additions & 0 deletions errors/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Package errors extends the standard library errors package
// with additional APIs to:
// - wrap errors with additional metadata
// - read attached metadata from errors
package errors
69 changes: 69 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package errors

import (
"errors"
"strings"
)

// Aliases to the standard error types
var (
New = errors.New
Is = errors.Is
As = errors.As
Unwrap = errors.Unwrap
Join = errors.Join
)

// ErrorData is an error that has key-value metadata attached to it.
type ErrorData struct {
err error
data map[string]string
}

// Error returns the error message and any attached metadata.
func (e *ErrorData) Error() string {
md := []string{}

for k, v := range e.data {
md = append(md, k+"="+v)
}

return e.err.Error() + " [" + strings.Join(md, ", ") + "]"
}

// Unwrap returns the underlying error.
func (e *ErrorData) Unwrap() error {
return e.err
}

// Is returns true if the error is the same as the target error.
func (e *ErrorData) Is(target error) bool {
return Is(e.err, target)
}

// Data returns the attached metadata.
func (e *ErrorData) Data() map[string]string {
return e.data
}

// Wrap attaches additional metadata to an error.
func Wrap(err error, attrs ...string) error {
if err == nil {
return nil
}

if len(attrs)%2 != 0 {
panic("[xtools/errors] attrs must be key/value pairs")
}

e := &ErrorData{
err: err,
data: map[string]string{},
}

for i := 0; i < len(attrs); i += 2 {
e.data[attrs[i]] = attrs[i+1]
}

return e
}
55 changes: 55 additions & 0 deletions errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package errors_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/gojekfarm/xtools/errors"
)

func TestWrap(t *testing.T) {
err := errors.New("test error")

wrapped := errors.Wrap(err, "foo", "bar")

assert.Equal(t, "test error [foo=bar]", wrapped.Error())
assert.ErrorIs(t, wrapped, err)
assert.EqualError(t, errors.Unwrap(wrapped), err.Error())

var data *errors.ErrorData
assert.True(t, errors.As(wrapped, &data))
assert.EqualValues(t, map[string]string{"foo": "bar"}, data.Data())
}

func TestWrap_PanicsOnOddNumberOfAttrs(t *testing.T) {
assert.Panics(t, func() {
errors.Wrap(errors.New("test error"), "foo")
})
}

func TestWrap_ReturnsNilIfErrIsNil(t *testing.T) {
assert.Nil(t, errors.Wrap(nil, "foo", "bar"))
}

func ExampleWrap() {
err := errors.New("test error")

// Wrap the error with some key-value pairs
wrapped := errors.Wrap(
err,
"foo", "bar",
"baz", "qux",
)

// errors.Is will check for the original error
fmt.Println(errors.Is(wrapped, err))

// Use errors.As to read attached metadata
var errData *errors.ErrorData

errors.As(wrapped, &errData)

fmt.Println(errData.Data())
}
11 changes: 11 additions & 0 deletions errors/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/gojekfarm/xtools/errors

go 1.18

require github.com/stretchr/testify v1.10.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions errors/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit dbfa350

Please sign in to comment.