Wrapped errors and more for golang developing (not just for go1.11, go1.13, and go1.20+).
hedzr/errors provides the compatibilities to your old project up to go 1.20.
hedzr/errors provides some extra enhancements for better context environment saving on error occurred.
- Simple migrating way from std errors: all of standard functions have been copied to
- Better
New():- format message inline:
err := errors.New("hello %s", "world") - format with
WithXXX:err := errors.New(errors.WithErrors(errs...)) - cascade format:
err := errors.New().WithErrors(errs...) - Stacktrace awareness
- Container for canning errors: Error Container (Inner/Nested)
- error template: Format message instantly but the text template can be given at beginning
- format message inline:
- Codes: treat a number as an error object
- Unwrap inner canned errors one by one
- No mental burden
-
v3.3.5
- fixed Iss() - dead loop or break unexpected
-
v3.3.3
- ensure working on go 1.23+
-
v3.3.2
- fixed/improved Attach() - attaching itself is denied now
-
v3.3.1
- fixed Iss() couldn't test the others except the first error.
-
v3.3.0
- added
Iss(err, errs...)to test if any of errors are included in 'err'. - improved As/Is/Unwrap to fit for new joint error since go1.20
- added causes2.Clear, ...
- improved Error() string
- reviewed and re-published this repo from v3.3
- added
-
v3.1.9
- fixed error.Is deep test to check two errors' message text contents if matched
- fixed errors.v3.Join when msg is not empty in an err obj
- fixed causes.WithErrors(): err obj has been ignored even if its message is not empty
-
OLDER in CHANGELOG
These features are supported for compatibilities.
func As(err error, target interface{}) boolfunc Is(err, target error) boolfunc New(text string) errorfunc Unwrap(err error) errorfunc Join(errs ...error) error
func Wrap(err error, message string) errorfunc Cause(err error) error: unwraps recursively, just like Unwrap()-
func Cause1(err error) error: unwraps just one level func WithCause(cause error, message string, args ...interface{}) error, =Wrap- supports Stacktrace
- in an error by
Wrap(), stacktrace wrapped; - for your error, attached by
WithStack(cause error);
- in an error by
Iss(err error, errs ...error) boolAsSlice(errs []error, target interface{}) boolIsAnyOf(err error, targets ...error) boolIsSlice(errs []error, target error) boolTypeIs(err, target error) boolTypeIsSlice(errs []error, target error) boolJoin(errs ...error) errorDumpStacksAsString(allRoutines bool) stringCanAttach(err interface{}) (ok bool)CanCause(err interface{}) (ok bool)CanCauses(err interface{}) (ok bool)CanUnwrap(err interface{}) (ok bool)CanIs(err interface{}) (ok bool)CanAs(err interface{}) (ok bool)Causes(err error) (errs []error)
package test
import (
"gopkg.in/hedzr/errors.v3"
"io"
"reflect"
"testing"
)
func TestForExample(t *testing.T) {
fn := func() (err error) {
ec := errors.New("some tips %v", "here")
defer ec.Defer(&err)
// attaches much more errors
for _, e := range []error{io.EOF, io.ErrClosedPipe} {
ec.Attach(e)
}
}
err := fn()
t.Logf("failed: %+v", err)
// use another number different to default to skip the error frames
err = errors.
Skip(3). // from on Skip()
WithMessage("some tips %v", "here").Build()
t.Logf("failed: %+v", err)
err = errors.
Message("1"). // from Message() on
WithSkip(0).
WithMessage("bug msg").
Build()
t.Logf("failed: %+v", err)
err = errors.
NewBuilder(). // from NewBuilder() on
WithCode(errors.Internal). // add errors.Code
WithErrors(io.EOF). // attach inner errors
WithErrors(io.ErrShortWrite, io.ErrClosedPipe).
Build()
t.Logf("failed: %+v", err)
// As code
var c1 errors.Code
if errors.As(err, &c1) {
println(c1) // = Internal
}
// As inner errors
var a1 []error
if errors.As(err, &a1) {
println(len(a1)) // = 3, means [io.EOF, io.ErrShortWrite, io.ErrClosedPipe]
}
// Or use Causes() to extract them:
if reflect.DeepEqual(a1, errors.Causes(err)) {
t.Fatal("unexpected problem")
}
// As error, the first inner error will be extracted
var ee1 error
if errors.As(err, &ee1) {
println(ee1) // = io.EOF
}
series := []error{io.EOF, io.ErrShortWrite, io.ErrClosedPipe, errors.Internal}
var index int
for ; ee1 != nil; index++ {
ee1 = errors.Unwrap(err) // extract the inner errors one by one
if ee1 != nil && ee1 != series[index] {
t.Fatalf("%d. cannot extract '%v' error with As(), ee1 = %v", index, series[index], ee1)
}
}
}func TestContainer(t *testing.T) {
// as a inner errors container
child := func() (err error) {
ec := errors.New("multiple tasks have errors")
defer ec.Defer(&err) // package the attached errors as a new one and return it as `err`
for _, r := range []error{io.EOF, io.ErrShortWrite, io.ErrClosedPipe, errors.Internal} {
ec.Attach(r)
}
doWithItem := func(item Item) (err error) {
// ...
return
}
for _, item := range SomeItems {
// nil will be ignored safely, do'nt worry about invalid attaches.
ec.Attach(doWithItem(item))
}
return
}
err := child() // get the canned errors as a packaged one
t.Logf("failed: %+v", err)
}We could declare a message template at first and format it with live args to build an error instantly.
func TestErrorsTmpl(t *testing.T) {
errTmpl := errors.New("expecting %v but got %v")
var err error
err = errTmpl.FormatWith("789", "123")
t.Logf("The error is: %v", err)
err = errTmpl.FormatWith(true, false)
t.Logf("The error is: %v", err)
}FormatWith will make new clone from errTmpl so you can use multiple cloned errors thread-safely.
The derived error instance is the descendant of the error template.
This relation can be tested by errors.IsDescent(errTempl, err)
func TestIsDescended(t *testing.T) {
err3 := New("any error tmpl with %v")
err4 := err3.FormatWith("huahua")
if !IsDescended(err3, err4) {
t.Fatalf("bad test on IsDescended(err3, err4)")
}
}Since v3.1.1, the better message format will be formatted at Printf("%+v").
func TestAs_betterFormat(t *testing.T) {
var err = New("Have errors").WithErrors(io.EOF, io.ErrShortWrite, io.ErrNoProgress)
t.Logf("%v\n", err)
var nestNestErr = New("Errors FOUND:").WithErrors(err, io.EOF)
var nnnErr = New("Nested Errors:").WithErrors(nestNestErr, strconv.ErrRange)
t.Logf("%v\n", nnnErr)
t.Logf("%+v\n", nnnErr)
}The output is:
=== RUN TestAs_betterFormat
causes_test.go:23: Have errors [EOF | short write | multiple Read calls return no data or error]
causes_test.go:27: Nested Errors: [Errors FOUND: [Have errors [EOF | short write | multiple Read calls return no data or error] | EOF] | value out of range]
causes_test.go:28: Nested Errors:
- Errors FOUND:
- Have errors
- EOF
- short write
- multiple Read calls return no data or error
- EOF
- value out of range
gopkg.in/hedzr/errors%2ev3.TestAs_betterFormat
/Volumes/VolHack/work/godev/cmdr-series/libs/errors/causes_test.go:26
testing.tRunner
/usr/local/go/src/testing/testing.go:1576
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1598
--- PASS: TestAs_betterFormat (0.00s)
PASSMIT