1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
package errorx
var (
// Most errors from this namespace are made private in order to disallow and direct type checks in the user code
syntheticErrors = NewNamespace("synthetic")
// Private error type for non-errors errors, used as a not-nil substitute that cannot be type-checked directly
foreignType = syntheticErrors.NewType("foreign")
// Private error type used as a universal wrapper, meant to add nothing at all to the error apart from some message
transparentWrapper = syntheticErrors.NewType("decorate").ApplyModifiers(TypeModifierTransparent)
// Private error type used as a densely opaque wrapper which hides both the original error and its own type
opaqueWrapper = syntheticErrors.NewType("wrap")
// Private error type used for stack trace capture
stackTraceWrapper = syntheticErrors.NewType("stacktrace").ApplyModifiers(TypeModifierTransparent)
)
// Decorate allows to pass some text info along with a message, leaving its semantics totally intact.
// Perceived type, traits and properties of the resulting error are those of the original.
// Without args, leaves the provided message intact, so a message may be generated or provided externally.
// With args, a formatting is performed, and it is therefore expected a format string to be constant.
func Decorate(err error, message string, args ...interface{}) *Error {
return NewErrorBuilder(transparentWrapper).
WithConditionallyFormattedMessage(message, args...).
WithCause(err).
Create()
}
// EnhanceStackTrace has all the properties of the Decorate() method
// and additionally extends the stack trace of the original error.
// Designed to be used when a original error is passed from another goroutine rather than from a direct method call.
// If, however, it is called in the same goroutine, formatter makes some moderated effort to remove duplication.
func EnhanceStackTrace(err error, message string, args ...interface{}) *Error {
return NewErrorBuilder(transparentWrapper).
WithConditionallyFormattedMessage(message, args...).
WithCause(err).
EnhanceStackTrace().
Create()
}
// EnsureStackTrace is a utility to ensure the stack trace is captured in provided error.
// If this is already true, it is returned unmodified.
// Otherwise, it is decorated with stack trace.
func EnsureStackTrace(err error) *Error {
if typedErr := Cast(err); typedErr != nil && typedErr.stackTrace != nil {
return typedErr
}
return NewErrorBuilder(stackTraceWrapper).
WithConditionallyFormattedMessage("").
WithCause(err).
EnhanceStackTrace().
Create()
}
// DecorateMany performs a transparent wrap of multiple errors with additional message.
// If there are no errors, or all errors are nil, returns nil.
// If all errors are of the same type (for example, if there is only one), wraps them transparently.
// Otherwise, an opaque wrap is performed, that is, IsOfType checks will fail on underlying error types.
func DecorateMany(message string, errs ...error) error {
errs = ignoreEmpty(errs)
if len(errs) == 0 {
return nil
}
if !areAllOfTheSameType(errs...) {
return WrapMany(opaqueWrapper, message, errs...)
}
return WrapMany(transparentWrapper, message, errs...)
}
// WrapMany is a utility to wrap multiple errors.
// If there are no errors, or all errors are nil, returns nil.
// Otherwise, the fist error is treated as an original cause, others are added as underlying.
func WrapMany(errorType *Type, message string, errs ...error) error {
errs = ignoreEmpty(errs)
if len(errs) == 0 {
return nil
}
cause := errs[0]
suppressed := errs[1:]
return errorType.Wrap(cause, message).WithUnderlyingErrors(suppressed...)
}
func ignoreEmpty(errs []error) []error {
result := make([]error, 0, len(errs))
for _, err := range errs {
if err != nil {
result = append(result, err)
}
}
return result
}
func areAllOfTheSameType(errs ...error) bool {
if len(errs) < 2 {
return true
}
var errorType *Type
for _, err := range errs {
typedError := Cast(err)
if typedError == nil {
return false
}
if errorType == nil {
errorType = typedError.Type()
} else if errorType != typedError.Type() {
return false
}
}
return true
}
|