Skip to content

Commit

Permalink
feat: some copying (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
abyssparanoia authored Nov 20, 2024
1 parent e4ffec6 commit e162722
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 18 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Package `goerr` provides more contextual error handling in Go.
- Stack traces
- Compatible with `github.com/pkg/errors`.
- Structured stack traces with `goerr.Stack` is available.
- Contextual variables to errors using `With(key, value)`.
- Contextual variables to errors using `WithValue(key, value)`.
- `errors.Is` to identify errors and `errors.As` to unwrap errors.
- `slog.LogValuer` interface to output structured logs with `slog`.

Expand All @@ -35,6 +35,7 @@ func main() {
```

Output:

```
2024/04/06 10:30:27 failed to open file: open no_such_file.txt: no such file or directory
main.someAction
Expand Down Expand Up @@ -86,16 +87,15 @@ if err := someAction("no_such_file.txt"); err != nil {

### Add/Extract contextual variables


`goerr` provides the `With(key, value)` method to add contextual variables to errors. The standard way to handle errors in Go is by injecting values into error messages. However, this approach makes it difficult to aggregate various errors. On the other hand, `goerr`'s `With` method allows for adding contextual information to errors without changing error message, making it easier to aggregate error logs. Additionally, error handling services like Sentry.io can handle errors more accurately with this feature.
`goerr` provides the `WithValue(key, value)` method to add contextual variables to errors. The standard way to handle errors in Go is by injecting values into error messages. However, this approach makes it difficult to aggregate various errors. On the other hand, `goerr`'s `With` method allows for adding contextual information to errors without changing error message, making it easier to aggregate error logs. Additionally, error handling services like Sentry.io can handle errors more accurately with this feature.

```go
var errFormatMismatch = errors.New("format mismatch")

func someAction(tasks []task) error {
for _, t := range tasks {
if err := validateData(t.Data); err != nil {
return goerr.Wrap(err, "failed to validate data").With("name", t.Name)
return goerr.Wrap(err, "failed to validate data").WithValue("name", t.Name)
}
}
// ....
Expand All @@ -104,7 +104,7 @@ func someAction(tasks []task) error {

func validateData(data string) error {
if !strings.HasPrefix(data, "data:") {
return goerr.Wrap(errFormatMismatch).With("data", data)
return goerr.Wrap(errFormatMismatch).WithValue("data", data)
}
return nil
}
Expand Down Expand Up @@ -132,6 +132,7 @@ func main() {
```

Output:

```
2024/04/06 14:40:59 var: data => invalid
2024/04/06 14:40:59 var: name => task2
Expand Down Expand Up @@ -170,7 +171,7 @@ func someAction(input string) error {

func validate(input string) error {
if input != "OK" {
return goerr.Wrap(errRuntime, "invalid input").With("input", input)
return goerr.Wrap(errRuntime, "invalid input").WithValue("input", input)
}
return nil
}
Expand All @@ -184,6 +185,7 @@ func main() {
```

Output:

```json
{
"time": "2024-04-06T11:32:40.350873+09:00",
Expand Down Expand Up @@ -226,7 +228,7 @@ type object struct {
}

func (o *object) Validate() error {
eb := goerr.NewBuilder().With("id", o.id)
eb := goerr.NewBuilder().WithValue("id", o.id)

if o.color == "" {
return eb.New("color is empty")
Expand All @@ -245,6 +247,7 @@ func main() {
```

Output:

```
2024/10/19 14:19:54 ERROR Validation error err.message="color is empty" err.values.id=object-1 (snip)
```
Expand Down
5 changes: 4 additions & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (x *Error) copy(dst *Error) {
for k, v := range x.values {
dst.values[k] = v
}
dst.code = x.code
dst.category = x.category
dst.detail = x.detail
// st (stacktrace) is not copied
}

Expand Down Expand Up @@ -173,7 +176,7 @@ func (x *Error) Unwrap() error {
}

// With adds key and value related to the error event
func (x *Error) With(key string, value any) *Error {
func (x *Error) WithValue(key string, value any) *Error {
x.values[key] = value
return x
}
Expand Down
10 changes: 5 additions & 5 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestErrorCode(t *testing.T) {
baseErr1 := goerr.New("oops").ID("code1")
baseErr2 := goerr.New("oops").ID("code2")

newErr := baseErr1.Wrap(rootErr).With("v", 1)
newErr := baseErr1.Wrap(rootErr).WithValue("v", 1)

if !errors.Is(newErr, baseErr1) {
t.Error("Expected newErr to be based on baseErr1")
Expand All @@ -109,7 +109,7 @@ func TestErrorCode(t *testing.T) {

func TestPrintable(t *testing.T) {
cause := errors.New("test")
err := goerr.Wrap(cause, "oops").ID("E001").With("blue", "five")
err := goerr.Wrap(cause, "oops").ID("E001").WithValue("blue", "five")

p := err.Printable()
if p.Message != "oops" {
Expand All @@ -127,7 +127,7 @@ func TestPrintable(t *testing.T) {
}

func TestUnwrap(t *testing.T) {
err1 := goerr.New("omg").With("color", "five")
err1 := goerr.New("omg").WithValue("color", "five")
err2 := fmt.Errorf("oops: %w", err1)

err := goerr.Unwrap(err2)
Expand Down Expand Up @@ -155,8 +155,8 @@ func TestErrorString(t *testing.T) {
}

func TestLoggingNestedError(t *testing.T) {
err1 := goerr.New("e1").With("color", "orange")
err2 := goerr.Wrap(err1, "e2").With("number", "five")
err1 := goerr.New("e1").WithValue("color", "orange")
err2 := goerr.Wrap(err1, "e2").WithValue("number", "five")
out := &bytes.Buffer{}
logger := slog.New(slog.NewJSONHandler(out, nil))
logger.Error("fail", slog.Any("error", err2))
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func someAction(input string) error {
if input != "OK" {
return goerr.New("input is not OK").With("input", input).With("time", time.Now())
return goerr.New("input is not OK").WithValue("input", input).WithValue("time", time.Now())
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion examples/errors_is/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var errInvalidInput = errors.New("invalid input")

func someAction(input string) error {
if input != "OK" {
return goerr.Wrap(errInvalidInput, "input is not OK").With("input", input)
return goerr.Wrap(errInvalidInput, "input is not OK").WithValue("input", input)
}
// .....
return nil
Expand Down
2 changes: 1 addition & 1 deletion examples/logging/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func someAction(input string) error {

func validate(input string) error {
if input != "OK" {
return goerr.Wrap(errRuntime, "invalid input").With("input", input)
return goerr.Wrap(errRuntime, "invalid input").WithValue("input", input)
}
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions examples/variables/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var errFormatMismatch = errors.New("format mismatch")
func someAction(tasks []task) error {
for _, t := range tasks {
if err := validateData(t.Data); err != nil {
return goerr.Wrap(err, "failed to validate data").With("name", t.Name)
return goerr.Wrap(err, "failed to validate data").WithValue("name", t.Name)
}
}
// ....
Expand All @@ -22,7 +22,7 @@ func someAction(tasks []task) error {

func validateData(data string) error {
if !strings.HasPrefix(data, "data:") {
return goerr.Wrap(errFormatMismatch).With("data", data)
return goerr.Wrap(errFormatMismatch).WithValue("data", data)
}
return nil
}
Expand Down

0 comments on commit e162722

Please sign in to comment.