mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-22 13:00:19 +00:00
168 lines
4.3 KiB
Go
168 lines
4.3 KiB
Go
|
package merry
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
type errKey int
|
||
|
|
||
|
const (
|
||
|
errKeyNone errKey = iota
|
||
|
errKeyStack
|
||
|
errKeyMessage
|
||
|
errKeyHTTPCode
|
||
|
errKeyUserMessage
|
||
|
errKeyForceCapture
|
||
|
errKeyHooked
|
||
|
)
|
||
|
|
||
|
func (e errKey) String() string {
|
||
|
switch e {
|
||
|
case errKeyNone:
|
||
|
return "none"
|
||
|
case errKeyStack:
|
||
|
return "stack"
|
||
|
case errKeyMessage:
|
||
|
return "message"
|
||
|
case errKeyHTTPCode:
|
||
|
return "http status code"
|
||
|
case errKeyUserMessage:
|
||
|
return "user message"
|
||
|
case errKeyForceCapture:
|
||
|
return "force stack capture"
|
||
|
default:
|
||
|
return ""
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type errWithValue struct {
|
||
|
err error
|
||
|
key, value interface{}
|
||
|
}
|
||
|
|
||
|
// Format implements fmt.Formatter
|
||
|
func (e *errWithValue) Format(s fmt.State, verb rune) {
|
||
|
Format(s, verb, e)
|
||
|
}
|
||
|
|
||
|
// Error implements golang's error interface
|
||
|
// returns the message value if set, otherwise
|
||
|
// delegates to inner error
|
||
|
func (e *errWithValue) Error() string {
|
||
|
if e.key == errKeyMessage {
|
||
|
if s, ok := e.value.(string); ok {
|
||
|
return s
|
||
|
}
|
||
|
}
|
||
|
return e.err.Error()
|
||
|
}
|
||
|
|
||
|
// String implements fmt.Stringer
|
||
|
func (e *errWithValue) String() string {
|
||
|
return e.Error()
|
||
|
}
|
||
|
|
||
|
// Unwrap returns the next wrapped error.
|
||
|
func (e *errWithValue) Unwrap() error {
|
||
|
return e.err
|
||
|
}
|
||
|
|
||
|
// isMerryError is a marker method for identifying error types implemented by this package.
|
||
|
func (e *errWithValue) isMerryError() {}
|
||
|
|
||
|
type errWithCause struct {
|
||
|
err error
|
||
|
cause error
|
||
|
}
|
||
|
|
||
|
func (e *errWithCause) Unwrap() error {
|
||
|
// skip through any directly nested errWithCauses.
|
||
|
// our implementation of Is/As already recursed through them,
|
||
|
// so we want to dig down to the first non-errWithCause.
|
||
|
|
||
|
nextErr := e.err
|
||
|
for {
|
||
|
if e, ok := nextErr.(*errWithCause); ok {
|
||
|
nextErr = e.err
|
||
|
} else {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// errWithCause.Is/As() also already checked nextErr, so we want to
|
||
|
// unwrap it and get to the next error down.
|
||
|
nextErr = errors.Unwrap(nextErr)
|
||
|
|
||
|
// we've reached the end of this wrapper chain. Return the cause.
|
||
|
if nextErr == nil {
|
||
|
return e.cause
|
||
|
}
|
||
|
|
||
|
// return a new errWithCause wrapper, wrapping next error, but bundling
|
||
|
// it will our cause, ignoring the causes of the errWithCauses we skip
|
||
|
// over above. This is how we carry the latest cause along as we unwrap
|
||
|
// the chain. When we get to the end of the chain, we'll return this latest
|
||
|
// cause.
|
||
|
return &errWithCause{err: nextErr, cause: e.cause}
|
||
|
}
|
||
|
|
||
|
func (e *errWithCause) String() string {
|
||
|
return e.Error()
|
||
|
}
|
||
|
|
||
|
func (e *errWithCause) Error() string {
|
||
|
return e.err.Error()
|
||
|
}
|
||
|
|
||
|
func (e *errWithCause) Format(f fmt.State, verb rune) {
|
||
|
Format(f, verb, e)
|
||
|
}
|
||
|
|
||
|
// errWithCause needs to provide custome implementations of Is and As.
|
||
|
// errors.Is() doesn't work on errWithCause because error.Is() uses errors.Unwrap() to traverse the error
|
||
|
// chain. But errWithCause.Unwrap() doesn't return the next error in the chain. Instead,
|
||
|
// it wraps the next error in a shim. The standard Is/As tests would compare the shim to the target.
|
||
|
// We need to override Is/As to compare the target to the error inside the shim.
|
||
|
|
||
|
func (e *errWithCause) Is(target error) bool {
|
||
|
// This does most of what errors.Is() does, by delegating
|
||
|
// to the nested error. But it does not use Unwrap to recurse
|
||
|
// any further. This just compares target with next error in the stack.
|
||
|
isComparable := reflect.TypeOf(target).Comparable()
|
||
|
if isComparable && e.err == target {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// since errWithCause implements Is(), this will effectively recurse through
|
||
|
// any directly nested errWithCauses.
|
||
|
if x, ok := e.err.(interface{ Is(error) bool }); ok && x.Is(target) {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (e *errWithCause) As(target interface{}) bool {
|
||
|
// This does most of what errors.As() does, by delegating
|
||
|
// to the nested error. But it does not use Unwrap to recurse
|
||
|
// any further. This just compares target with next error in the stack.
|
||
|
val := reflect.ValueOf(target)
|
||
|
typ := val.Type()
|
||
|
targetType := typ.Elem()
|
||
|
if reflect.TypeOf(e.err).AssignableTo(targetType) {
|
||
|
val.Elem().Set(reflect.ValueOf(e.err))
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// since errWithCause implements As(), this will effectively recurse through
|
||
|
// any directly nested errWithCauses.
|
||
|
if x, ok := e.err.(interface{ As(interface{}) bool }); ok && x.As(target) {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// isMerryError is a marker method for identifying error types implemented by this package.
|
||
|
func (e *errWithCause) isMerryError() {}
|