EdgexAgent/device-ble-go/vendor/github.com/zeebo/errs/errs.go
2025-07-10 20:40:32 +08:00

299 lines
7.4 KiB
Go

// Package errs provides a simple error package with stack traces.
package errs
import (
"fmt"
"io"
"runtime"
)
// Namer is implemented by all errors returned in this package. It returns a
// name for the class of error it is, and a boolean indicating if the name is
// valid.
type Namer interface{ Name() (string, bool) }
// Causer is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying cause.
//
// Deprecated: check for the 'Unwrap()' interface from the stdlib errors package
// instead.
type Causer interface{ Cause() error }
// New returns an error not contained in any class. This is the same as calling
// fmt.Errorf(...) except it captures a stack trace on creation.
func New(format string, args ...interface{}) error {
return (*Class).create(nil, 3, fmt.Errorf(format, args...))
}
// Wrap returns an error not contained in any class. It just associates a stack
// trace with the error. Wrap returns nil if err is nil.
func Wrap(err error) error {
return (*Class).create(nil, 3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error not
// contained in any class. It just associates a stack trace with the error. WrapP
// does nothing if the pointer or pointed at error is nil.
func WrapP(err *error) {
if err != nil && *err != nil {
*err = (*Class).create(nil, 3, *err)
}
}
// Often, we call Unwrap as much as possible. Since comparing arbitrary
// interfaces with equality isn't panic safe, we only loop up to 100
// times to ensure that a poor implementation that causes a cycle does
// not run forever.
const maxUnwrap = 100
// Unwrap returns the final, most underlying error, if any, or just the error.
//
// Deprecated: Prefer errors.Is() and errors.As().
func Unwrap(err error) error {
for i := 0; err != nil && i < maxUnwrap; i++ {
var nerr error
switch e := err.(type) {
case Causer:
nerr = e.Cause()
case interface{ Unwrap() error }:
nerr = e.Unwrap()
case interface{ Ungroup() []error }:
// consider the first error to be the "main" error.
errs := e.Ungroup()
if len(errs) > 0 {
nerr = errs[0]
}
case interface{ Unwrap() []error }:
// consider the first error to be the "main" error.
errs := e.Unwrap()
if len(errs) > 0 {
nerr = errs[0]
}
}
if nerr == nil {
return err
}
err = nerr
}
return err
}
// Classes returns all the classes that have wrapped the error.
func Classes(err error) (classes []*Class) {
IsFunc(err, func(err error) bool {
if e, ok := err.(*errorT); ok {
classes = append(classes, e.class)
}
return false
})
return classes
}
// IsFunc checks if any of the underlying errors matches the func
func IsFunc(err error, is func(err error) bool) bool {
for {
if is(err) {
return true
}
switch u := err.(type) {
case interface{ Unwrap() error }:
err = u.Unwrap()
case Causer:
err = u.Cause()
case interface{ Ungroup() []error }:
for _, err := range u.Ungroup() {
if IsFunc(err, is) {
return true
}
}
return false
case interface{ Unwrap() []error }:
for _, err := range u.Unwrap() {
if IsFunc(err, is) {
return true
}
}
return false
default:
return false
}
}
}
//
// error classes
//
// Class represents a class of errors. You can construct errors, and check if
// errors are part of the class.
type Class string
// Has returns true if the passed in error (or any error wrapped by it) has
// this class.
func (c *Class) Has(err error) bool {
return IsFunc(err, func(err error) bool {
errt, ok := err.(*errorT)
return ok && errt.class == c
})
}
// New constructs an error with the format string that will be contained by
// this class. This is the same as calling Wrap(fmt.Errorf(...)).
func (c *Class) New(format string, args ...interface{}) error {
return c.create(3, fmt.Errorf(format, args...))
}
// Wrap returns a new error based on the passed in error that is contained in
// this class. Wrap returns nil if err is nil.
func (c *Class) Wrap(err error) error {
return c.create(3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error contained
// in this class. WrapP does nothing if the pointer or pointed at error is nil.
func (c *Class) WrapP(err *error) {
if err != nil && *err != nil {
*err = c.create(3, *err)
}
}
// Instance creates a class membership object which implements the error
// interface and allows errors.Is() to check whether given errors are
// (or contain) an instance of this class.
//
// This makes possible a construct like the following:
//
// if errors.Is(err, MyClass.Instance()) {
// fmt.Printf("err is an instance of MyClass")
// }
//
// ..without requiring the Class type to implement the error interface itself,
// as that would open the door to sundry misunderstandings and misusage.
func (c *Class) Instance() error {
return (*classMembershipChecker)(c)
}
// create constructs the error, or just adds the class to the error, keeping
// track of the stack if it needs to construct it.
func (c *Class) create(depth int, err error) error {
if err == nil {
return nil
}
var pcs []uintptr
if err, ok := err.(*errorT); ok {
if c == nil || err.class == c {
return err
}
pcs = err.pcs
}
errt := &errorT{
class: c,
err: err,
pcs: pcs,
}
if errt.pcs == nil {
errt.pcs = make([]uintptr, 64)
n := runtime.Callers(depth, errt.pcs)
errt.pcs = errt.pcs[:n:n]
}
return errt
}
type classMembershipChecker Class
func (cmc *classMembershipChecker) Error() string {
panic("classMembershipChecker used as concrete error! don't do that")
}
//
// errors
//
// errorT is the type of errors returned from this package.
type errorT struct {
class *Class
err error
pcs []uintptr
}
var ( // ensure *errorT implements the helper interfaces.
_ Namer = (*errorT)(nil)
_ Causer = (*errorT)(nil)
_ error = (*errorT)(nil)
)
// Stack returns the pcs for the stack trace associated with the error.
func (e *errorT) Stack() []uintptr { return e.pcs }
// errorT implements the error interface.
func (e *errorT) Error() string {
return fmt.Sprintf("%v", e)
}
// Format handles the formatting of the error. Using a "+" on the format string
// specifier will also write the stack trace.
func (e *errorT) Format(f fmt.State, c rune) {
sep := ""
if e.class != nil && *e.class != "" {
fmt.Fprintf(f, "%s", string(*e.class))
sep = ": "
}
if text := e.err.Error(); len(text) > 0 {
fmt.Fprintf(f, "%s%v", sep, text)
}
if f.Flag(int('+')) {
summarizeStack(f, e.pcs)
}
}
// Cause implements the interface wrapping errors were previously
// expected to implement to allow getting at underlying causes.
func (e *errorT) Cause() error {
return e.err
}
// Unwrap returns the immediate underlying error.
func (e *errorT) Unwrap() error {
return e.err
}
// Name returns the name for the error, which is the first wrapping class.
func (e *errorT) Name() (string, bool) {
if e.class == nil {
return "", false
}
return string(*e.class), true
}
// Is determines whether an error is an instance of the given error class.
//
// Use with (*Class).Instance().
func (e *errorT) Is(err error) bool {
cmc, ok := err.(*classMembershipChecker)
return ok && e.class == (*Class)(cmc)
}
// summarizeStack writes stack line entries to the writer.
func summarizeStack(w io.Writer, pcs []uintptr) {
frames := runtime.CallersFrames(pcs)
for {
frame, more := frames.Next()
if !more {
return
}
fmt.Fprintf(w, "\n\t%s:%d", frame.Function, frame.Line)
}
}