167 lines
3.8 KiB
Go
167 lines
3.8 KiB
Go
|
// Package merr extends the errors package with features like key-value
|
||
|
// attributes for errors, embedded stacktraces, and multi-errors.
|
||
|
package merr
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
var strBuilderPool = sync.Pool{
|
||
|
New: func() interface{} { return new(strings.Builder) },
|
||
|
}
|
||
|
|
||
|
func putStrBuilder(sb *strings.Builder) {
|
||
|
sb.Reset()
|
||
|
strBuilderPool.Put(sb)
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
type val struct {
|
||
|
visible bool
|
||
|
val interface{}
|
||
|
}
|
||
|
|
||
|
func (v val) String() string {
|
||
|
return fmt.Sprint(v.val)
|
||
|
}
|
||
|
|
||
|
type err struct {
|
||
|
err error
|
||
|
attr map[interface{}]val
|
||
|
}
|
||
|
|
||
|
// attr keys internal to this package
|
||
|
type attrKey string
|
||
|
|
||
|
func wrap(e error, cp bool, skip int) *err {
|
||
|
er, ok := e.(*err)
|
||
|
if !ok {
|
||
|
er := &err{err: e, attr: map[interface{}]val{}}
|
||
|
if skip >= 0 {
|
||
|
setStack(er, skip+1)
|
||
|
}
|
||
|
return er
|
||
|
} else if !cp {
|
||
|
return er
|
||
|
}
|
||
|
|
||
|
er2 := &err{
|
||
|
err: er.err,
|
||
|
attr: make(map[interface{}]val, len(er.attr)),
|
||
|
}
|
||
|
for k, v := range er.attr {
|
||
|
er2.attr[k] = v
|
||
|
}
|
||
|
if _, ok := er2.attr[attrKeyStack]; !ok && skip >= 0 {
|
||
|
setStack(er, skip+1)
|
||
|
}
|
||
|
|
||
|
return er2
|
||
|
}
|
||
|
|
||
|
// Wrap takes in an error and returns one wrapping it in merr's inner type,
|
||
|
// which embeds information like the stack trace.
|
||
|
func Wrap(e error) error {
|
||
|
return wrap(e, false, 1)
|
||
|
}
|
||
|
|
||
|
// New returns a new error with the given string as its error string. New
|
||
|
// automatically wraps the error in merr's inner type, which embeds information
|
||
|
// like the stack trace.
|
||
|
func New(str string) error {
|
||
|
return wrap(errors.New(str), false, 1)
|
||
|
}
|
||
|
|
||
|
// Errorf is like New, but allows for formatting of the string.
|
||
|
func Errorf(str string, args ...interface{}) error {
|
||
|
return wrap(fmt.Errorf(str, args...), false, 1)
|
||
|
}
|
||
|
|
||
|
func (er *err) visibleAttrs() [][2]string {
|
||
|
out := make([][2]string, 0, len(er.attr))
|
||
|
for k, v := range er.attr {
|
||
|
if !v.visible {
|
||
|
continue
|
||
|
}
|
||
|
out = append(out, [2]string{
|
||
|
strings.Trim(fmt.Sprintf("%q", k), `"`),
|
||
|
fmt.Sprint(v.val),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
sort.Slice(out, func(i, j int) bool {
|
||
|
return out[i][0] < out[j][0]
|
||
|
})
|
||
|
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func (er *err) Error() string {
|
||
|
visAttrs := er.visibleAttrs()
|
||
|
if len(visAttrs) == 0 {
|
||
|
return er.err.Error()
|
||
|
}
|
||
|
|
||
|
sb := strBuilderPool.Get().(*strings.Builder)
|
||
|
defer putStrBuilder(sb)
|
||
|
|
||
|
sb.WriteString(strings.TrimSpace(er.err.Error()))
|
||
|
for _, attr := range visAttrs {
|
||
|
k, v := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])
|
||
|
sb.WriteString("\n\t* ")
|
||
|
sb.WriteString(k)
|
||
|
sb.WriteString(": ")
|
||
|
|
||
|
// if there's no newlines then print v inline with k
|
||
|
if strings.Index(v, "\n") < 0 {
|
||
|
sb.WriteString(v)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for _, vLine := range strings.Split(v, "\n") {
|
||
|
sb.WriteString("\n\t\t")
|
||
|
sb.WriteString(strings.TrimSpace(vLine))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
// WithValue returns a copy of the original error, automatically wrapping it if
|
||
|
// the error is not from merr (see Wrap). The returned error has a value set on
|
||
|
// with for the given key.
|
||
|
//
|
||
|
// visible determines whether or not the value is visible in the output of
|
||
|
// Error.
|
||
|
func WithValue(e error, k, v interface{}, visible bool) error {
|
||
|
er := wrap(e, true, 1)
|
||
|
er.attr[k] = val{val: v, visible: visible}
|
||
|
return er
|
||
|
}
|
||
|
|
||
|
// GetValue returns the value embedded in the error for the given key, or nil if
|
||
|
// the error isn't from this package or doesn't have that key embedded.
|
||
|
func GetValue(e error, k interface{}) interface{} {
|
||
|
return wrap(e, false, -1).attr[k].val
|
||
|
}
|
||
|
|
||
|
// Base takes in an error and checks if it is merr's internal error type. If it
|
||
|
// is then the underlying error which is being wrapped is returned. If it's not
|
||
|
// then the passed in error is returned as-is.
|
||
|
func Base(e error) error {
|
||
|
if er, ok := e.(*err); ok {
|
||
|
return er.err
|
||
|
}
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
// Equal is a shortcut for Base(e1) == Base(e2).
|
||
|
func Equal(e1, e2 error) bool {
|
||
|
return Base(e1) == Base(e2)
|
||
|
}
|