Home

Awesome

errors.v3

Go GitHub tag (latest SemVer) GoDoc Go Report Card Coverage Status FOSSA Status

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.

Features

Others

History

Compatibilities

These features are supported for compatibilities.

stdlib `errors' compatibilities

pkg/errors compatibilities

Some Enhancements

Best Practices

Basics

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)
    }
  }
}

Error Container (Inner/Nested)

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)
}

Error Template

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)")
  }
}

Better format for a nested error

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)
PASS

LICENSE

MIT

Scan

FOSSA Status