Documentation
¶
Overview ¶
Package errorsbp provides some error utilities for Baseplate.go project.
Batch ¶
Batch can be used to compile multiple errors into a single one.
An example of how to use it in your functions:
type worker func() error
func runWorksParallel(works []worker) error {
errChan := make(chan error, len(works))
var wg sync.WaitGroup
wg.Add(len(works))
for _, work := range works {
go func(work worker) {
defer wg.Done()
errChan <- work()
}(work)
}
wg.Wait()
close(errChan)
var batch errorsbp.Batch
for err := range errChan {
// nil errors will be auto skipped
batch.Add(err)
}
// If all works succeeded, Compile() returns nil.
// If only one work failed, Compile() returns that error directly
// instead of wrapping it inside BatchError.
return batch.Compile()
}
Batch is not thread-safe. The same batch should not be operated on different goroutines concurrently.
Suppressor ¶
Suppressor is a type defined to provide an unified way to allow certain functions/features to ignore certain errors.
It's currently used by thriftbp package in both server and client middlewares, to not treat certain errors defined in thrift IDL as span errors. Because of go's type system, we cannot reliably provide a Suppressor implementation to suppress all errors defined in all thrift IDLs, as a result we rely on service/client implementations to implement it for the middlewares.
An example of how to implement it for your thrift defined errors:
func MyThriftSuppressor(err error) bool {
return errors.As(err, new(*mythrift.MyThriftErrorType))
}
Index ¶
- func BatchSize(err error) int
- func Prefix(prefix string, err error) error
- func SuppressNone(err error) bool
- type Batchdeprecated
- func (be *Batch) Add(errs ...error)
- func (be *Batch) AddPrefix(prefix string, errs ...error)
- func (be Batch) As(v interface{}) bool
- func (be *Batch) Clear()
- func (be Batch) Compile() error
- func (be Batch) Error() string
- func (be Batch) GetErrors() []error
- func (be Batch) Is(target error) bool
- func (be Batch) Len() int
- func (be Batch) Unwrap() []error
- type Suppressor
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BatchSize ¶ added in v0.9.2
BatchSize returns the size of the batch for error err.
If err implements `Unwrap() []error` (optional interface defined in go 1.20), or it unwraps to an error that implements `Unwrap() []error` (which includes errorsbp.Batch and *errorsbp.Batch), BatchSize returns the total size of Unwrap'd errors recursively. Otherwise, it returns 1 if err is non-nil, and 0 if err is nil.
Note that for a Batch, it's possible that BatchSize returns a different (higher) size than its Len. That would only happen if the Batch contains batch errors generated by other implementation(s) (for example, errors.Join or fmt.Errorf).
It's useful in tests, for example to verify that a function indeed returns the exact number of errors as expected.
It's possible to construct an error to cause BatchSize to recurse infinitely and thus cause stack overflow, so in general BatchSize should only be used in test code and not in production code.
Example ¶
This example demonstrates how to use errorsbp.BatchSize in a unit test.
package main
import (
"errors"
"fmt"
"testing"
"github.com/reddit/baseplate.go/errorsbp"
)
var (
errOne = errors.New("dummy error #1")
errTwo = errors.New("dummy error #2")
errThree = errors.New("dummy error #3")
)
// MyFunction is the function to be tested by MyFunctionTest.
func MyFunction(i int) error {
var be errorsbp.Batch
switch i {
case 0:
// do nothing
case 1:
be.Add(errOne)
case 2:
be.Add(errOne)
be.Add(errTwo)
case 3:
be.Add(errOne)
be.Add(errTwo)
be.Add(errThree)
}
return be.Compile()
}
// NOTE: In real unit test code this function signature should be:
//
// func TestMyFunction(t *testing.T)
//
// But doing that will break this example.
func MyFunctionTest() {
var (
t *testing.T
)
for _, c := range []struct {
arg int
want []error
}{
{
arg: 0,
},
{
arg: 1,
want: []error{errOne},
},
{
arg: 2,
want: []error{errOne, errTwo},
},
{
arg: 3,
want: []error{errOne, errTwo, errThree},
},
} {
t.Run(fmt.Sprintf("%v", c.arg), func(t *testing.T) {
got := MyFunction(c.arg)
t.Logf("got error: %v", got)
if len(c.want) != errorsbp.BatchSize(got) {
t.Errorf("Expected %d errors, got %d", len(c.want), errorsbp.BatchSize(got))
}
for _, target := range c.want {
if !errors.Is(got, target) {
t.Errorf("Expected error %v to be returned but it's not", target)
}
}
})
}
}
// This example demonstrates how to use errorsbp.BatchSize in a unit test.
func main() {
// See MyFuncionTest above for the real example.
}
func Prefix ¶ added in v0.9.12
Prefix is a helper function to add prefix to a potential error.
If err is nil, it returns nil. If prefix is empty string, it returns err as-is. Otherwise it returns an error that can unwrap to err with message of "prefix: err.Error()".
It's useful to be used with errors.Join.
func SuppressNone ¶
SuppressNone is a Suppressor implementation that always return false, thus suppress none of the errors.
It's the default implementation nil Suppressor falls back into.
Types ¶
type Batch
deprecated
type Batch struct {
// contains filtered or unexported fields
}
Batch is an error that can contain multiple errors.
The zero value of Batch is valid (with no errors) and ready to use.
This type is not thread-safe. The same batch should not be operated on different goroutines concurrently.
Deprecated: Please use errors.Join or fmt.Errorf with multiple %w instead.
Example (IncrementalErrors) ¶
This example demonstrates how adding errors into a batch affects the single error returned by Compile().
package main
import (
"errors"
"fmt"
"github.com/reddit/baseplate.go/errorsbp"
)
func main() {
var batch errorsbp.Batch
var singleError error
singleError = batch.Compile()
fmt.Printf("0: %v\n", singleError)
err := errors.New("foo")
batch.AddPrefix("prefix", err)
singleError = batch.Compile()
fmt.Printf("1: %v\n", singleError)
batch.Add(nil)
singleError = batch.Compile()
fmt.Printf("Nil errors are skipped: %v\n", singleError)
err = errors.New("bar")
batch.Add(err)
singleError = batch.Compile()
fmt.Printf("2: %v\n", singleError)
var newBatch errorsbp.Batch
// Add multiple errors at once
newBatch.Add(
errors.New("fizz"),
errors.New("buzz"),
)
newBatch.Add(batch)
fmt.Printf("4: %v\n", newBatch.Compile())
}
Output: 0: <nil> 1: prefix: foo Nil errors are skipped: prefix: foo 2: errorsbp.Batch: total 2 error(s) in this batch: prefix: foo; bar 4: errorsbp.Batch: total 4 error(s) in this batch: fizz; buzz; prefix: foo; bar
func (*Batch) Add ¶
Add adds errors into the batch.
If an error is also a Batch, its underlying error(s) will be added instead of the Batch itself.
Nil errors will be skipped.
func (*Batch) AddPrefix ¶ added in v0.6.1
AddPrefix adds errors into the batch with given prefix.
If an error is also a Batch, its underlying error(s) will be added instead of the Batch itself, all with the same given prefix.
Nil errors will be skipped.
The actual error(s) added into the batch will produce the error message of:
"prefix: err.Error()"
It's useful in Closer implementations that need to call multiple Closers.
Example ¶
This example demonstrates how to use AddPrefix.
package main
import (
"errors"
"fmt"
"github.com/reddit/baseplate.go/errorsbp"
)
func main() {
operator1Failure := errors.New("unknown error")
operator2Failure := errors.New("permission denied")
operator0 := func() error {
return nil
}
operator1 := func() error {
return operator1Failure
}
operator2 := func() error {
return operator2Failure
}
var batch errorsbp.Batch
batch.AddPrefix("operator0", operator0())
batch.AddPrefix("operator1", operator1())
batch.AddPrefix("operator2", operator2())
err := batch.Compile()
fmt.Println(err)
fmt.Println(errors.Is(err, operator1Failure))
fmt.Println(errors.Is(err, operator2Failure))
}
Output: errorsbp.Batch: total 2 error(s) in this batch: operator1: unknown error; operator2: permission denied true true
func (Batch) As ¶
As implements helper interface for errors.As.
If v is pointer to either Batch or *Batch, *v will be set into this error. Otherwise, As will try errors.As against all errors in this batch, returning the first match.
See Is for the discussion of possiblity of infinite loop.
Example ¶
This example demonstrates how a BatchError can be inspected with errors.As.
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/reddit/baseplate.go/errorsbp"
)
func main() {
var batch errorsbp.Batch
var target *os.PathError
batch.Add(context.Canceled)
err := batch.Compile()
fmt.Println(errors.As(err, &target)) // false
batch.Add(fmt.Errorf("wrapped: %w", &os.PathError{}))
err = batch.Compile()
fmt.Println(errors.As(err, &target)) // true
batch.Add(fmt.Errorf("wrapped: %w", &os.LinkError{}))
err = batch.Compile()
fmt.Println(errors.As(err, &target)) // true
}
Output: false true true
func (Batch) Compile ¶
Compile compiles the batch.
If the batch contains zero errors, Compile returns nil.
If the batch contains exactly one error, that underlying error will be returned.
Otherwise, the batch itself will be returned.
func (Batch) Is ¶
Is implements helper interface for errors.Is.
It calls errors.Is against all errors in this batch, until a match is found.
If an error in the batch is the Batch itself, calling its Is (and As) could cause an infinite loop. But there's a special handling in Add function, that if you try to add a Batch into the batch, we add the underlying errors instead the Batch itself. As a result it should be impossible to cause infinite loops in Is and As.
Example ¶
This example demonstrates how a BatchError can be inspected with errors.Is.
package main
import (
"context"
"errors"
"fmt"
"io"
"github.com/reddit/baseplate.go/errorsbp"
)
func main() {
var batch errorsbp.Batch
batch.Add(context.Canceled)
err := batch.Compile()
fmt.Println(errors.Is(err, context.Canceled)) // true
fmt.Println(errors.Is(err, io.EOF)) // false
batch.Add(fmt.Errorf("wrapped: %w", io.EOF))
err = batch.Compile()
fmt.Println(errors.Is(err, context.Canceled)) // true
fmt.Println(errors.Is(err, io.EOF)) // true
}
Output: true false true true
type Suppressor ¶
Suppressor defines a type of function can be used to suppress certain errors.
The implementation shall return true on the errors they want to suppress, and false on all other errors.
Example ¶
This example demonstrates how to implement a Suppressor.
package main
import (
"errors"
"github.com/reddit/baseplate.go/errorsbp"
"github.com/reddit/baseplate.go/internal/gen-go/reddit/baseplate"
)
// BEGIN THRIFT GENERATED CODE
//
// In real code this part should come from thrift compiled go code.
// Here we just write some placeholders to use as an example.
type MyThriftException struct{}
func (*MyThriftException) Error() string {
return "my thrift exception"
}
// END THRIFT GENERATED CODE
func MyThriftExceptionSuppressor(err error) bool {
return errors.As(err, new(*MyThriftException))
}
func BaseplateErrorSuppressor(err error) bool {
// baseplate.Error is from a thrift exception defined in baspleate.thrift,
// then compiled to go code by thrift compiler.
// We use that type as an example here.
// In real code you usually also should add (Or) additional exceptions defined
// in your thrift files.
return errors.As(err, new(*baseplate.Error))
}
// This example demonstrates how to implement a Suppressor.
func main() {
// This constructs the Suppressor you could fill into
// thriftbp.ServerConfig.ErrorSpanSuppressor field.
errorsbp.OrSuppressors(BaseplateErrorSuppressor, MyThriftExceptionSuppressor)
}
func OrSuppressors ¶
func OrSuppressors(suppressors ...Suppressor) Suppressor
OrSuppressors combines the given suppressors.
If any of the suppressors return true on an error, the combined Suppressor would returns true on that error.
func (Suppressor) Suppress ¶
func (s Suppressor) Suppress(err error) bool
Suppress is the nil-safe way of calling a Suppressor.
func (Suppressor) Wrap ¶
func (s Suppressor) Wrap(err error) error
Wrap wraps the error based on the decision of Suppressor.
If the error shall be suppressed, Wrap returns nil. Otherwise Wrap returns the error as-is.
Like Suppress, Wrap is also nil-safe.