648 lines
18 KiB
Go
648 lines
18 KiB
Go
// Copyright (C) 2014 Space Monkey, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package errors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
logOnCreation = GenSym()
|
|
captureStack = GenSym()
|
|
disableInheritance = GenSym()
|
|
)
|
|
|
|
// ErrorClass is the basic hierarchical error type. An ErrorClass generates
|
|
// actual errors, but the error class controls properties of the errors it
|
|
// generates, such as where those errors are in the hierarchy, whether or not
|
|
// they capture the stack on instantiation, and so forth.
|
|
type ErrorClass struct {
|
|
parent *ErrorClass
|
|
name string
|
|
data map[DataKey]interface{}
|
|
}
|
|
|
|
var (
|
|
// HierarchicalError is the base class for all hierarchical errors generated
|
|
// through this class.
|
|
HierarchicalError = &ErrorClass{
|
|
parent: nil,
|
|
name: "Error",
|
|
data: map[DataKey]interface{}{captureStack: true}}
|
|
|
|
// SystemError is the base error class for errors not generated through this
|
|
// errors library. It is not expected that anyone would ever generate new
|
|
// errors from a SystemError type or make subclasses.
|
|
SystemError = &ErrorClass{
|
|
parent: nil,
|
|
name: "System Error",
|
|
data: map[DataKey]interface{}{}}
|
|
)
|
|
|
|
// An ErrorOption is something that controls behavior of specific error
|
|
// instances. They can be set on ErrorClasses or errors individually.
|
|
type ErrorOption func(map[DataKey]interface{})
|
|
|
|
// SetData will take the given value and store it with the error or error class
|
|
// and its descendents associated with the given DataKey. Be sure to check out
|
|
// the example. value can be nil to disable values for subhierarchies.
|
|
func SetData(key DataKey, value interface{}) ErrorOption {
|
|
return func(m map[DataKey]interface{}) {
|
|
m[key] = value
|
|
}
|
|
}
|
|
|
|
// LogOnCreation tells the error class and its descendents to log the stack
|
|
// whenever an error of this class is created.
|
|
func LogOnCreation() ErrorOption {
|
|
return SetData(logOnCreation, true)
|
|
}
|
|
|
|
// CaptureStack tells the error class and its descendents to capture the stack
|
|
// whenever an error of this class is created, and output it as part of the
|
|
// error's Error() method. This is the default.
|
|
func CaptureStack() ErrorOption {
|
|
return SetData(captureStack, true)
|
|
}
|
|
|
|
// NoLogOnCreation is the opposite of LogOnCreation and applies to the error,
|
|
// class, and its descendents. This is the default.
|
|
func NoLogOnCreation() ErrorOption {
|
|
return SetData(logOnCreation, false)
|
|
}
|
|
|
|
// NoCaptureStack is the opposite of CaptureStack and applies to the error,
|
|
// class, and its descendents.
|
|
func NoCaptureStack() ErrorOption {
|
|
return SetData(captureStack, false)
|
|
}
|
|
|
|
// If DisableInheritance is provided, the error or error class will belong to
|
|
// its ancestors, but will not inherit their settings and options. Use with
|
|
// caution, and may disappear in future releases.
|
|
func DisableInheritance() ErrorOption {
|
|
return SetData(disableInheritance, true)
|
|
}
|
|
|
|
func boolWrapper(val interface{}, default_value bool) bool {
|
|
rv, ok := val.(bool)
|
|
if ok {
|
|
return rv
|
|
}
|
|
return default_value
|
|
}
|
|
|
|
// NewClass creates an error class with the provided name and options. Classes
|
|
// generated from this method and not *ErrorClass.NewClass will descend from
|
|
// the root HierarchicalError base class.
|
|
func NewClass(name string, options ...ErrorOption) *ErrorClass {
|
|
return HierarchicalError.NewClass(name, options...)
|
|
}
|
|
|
|
// New is for compatibility with the default Go errors package. It simply
|
|
// creates an error from the HierarchicalError root class.
|
|
func New(text string) error {
|
|
// NewWith doesn't take a format string, even though we have no options.
|
|
return HierarchicalError.NewWith(text)
|
|
}
|
|
|
|
// NewClass creates an error class with the provided name and options. The new
|
|
// class will descend from the receiver.
|
|
func (parent *ErrorClass) NewClass(name string,
|
|
options ...ErrorOption) *ErrorClass {
|
|
|
|
ec := &ErrorClass{
|
|
parent: parent,
|
|
name: name,
|
|
data: make(map[DataKey]interface{})}
|
|
|
|
for _, option := range options {
|
|
option(ec.data)
|
|
}
|
|
|
|
if !boolWrapper(ec.data[disableInheritance], false) {
|
|
// hoist options for speed
|
|
for key, val := range parent.data {
|
|
_, exists := ec.data[key]
|
|
if !exists {
|
|
ec.data[key] = val
|
|
}
|
|
}
|
|
return ec
|
|
} else {
|
|
delete(ec.data, disableInheritance)
|
|
}
|
|
|
|
return ec
|
|
}
|
|
|
|
// MustAddData allows adding data key value pairs to error classes after they
|
|
// are created. This is useful for allowing external packages add namespaced
|
|
// values to errors defined outside of their package. It will panic if the
|
|
// key is already set in the error class.
|
|
func (e *ErrorClass) MustAddData(key DataKey, value interface{}) {
|
|
if _, ex := e.data[key]; ex {
|
|
panic("key already exists")
|
|
}
|
|
e.data[key] = value
|
|
}
|
|
|
|
// GetData will return any data set on the error class for the given key. It
|
|
// returns nil if there is no data set for that key.
|
|
func (e *ErrorClass) GetData(key DataKey) interface{} {
|
|
return e.data[key]
|
|
}
|
|
|
|
// Parent returns this error class' direct ancestor.
|
|
func (e *ErrorClass) Parent() *ErrorClass {
|
|
return e.parent
|
|
}
|
|
|
|
// String returns this error class' name
|
|
func (e *ErrorClass) String() string {
|
|
if e == nil {
|
|
return "nil"
|
|
}
|
|
return e.name
|
|
}
|
|
|
|
// Is returns true if the receiver class is or is a descendent of parent.
|
|
func (e *ErrorClass) Is(parent *ErrorClass) bool {
|
|
for check := e; check != nil; check = check.parent {
|
|
if check == parent {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// frame logs the pc at some point during execution.
|
|
type frame struct {
|
|
pc uintptr
|
|
}
|
|
|
|
// String returns a human readable form of the frame.
|
|
func (e frame) String() string {
|
|
if e.pc == 0 {
|
|
return "unknown.unknown:0"
|
|
}
|
|
f := runtime.FuncForPC(e.pc)
|
|
if f == nil {
|
|
return "unknown.unknown:0"
|
|
}
|
|
file, line := f.FileLine(e.pc)
|
|
return fmt.Sprintf("%s:%s:%d", f.Name(), filepath.Base(file), line)
|
|
}
|
|
|
|
// callerState records the pc into an frame for two callers up.
|
|
func callerState(depth int) frame {
|
|
pc, _, _, ok := runtime.Caller(depth)
|
|
if !ok {
|
|
return frame{pc: 0}
|
|
}
|
|
return frame{pc: pc}
|
|
}
|
|
|
|
// record will record the pc at the given depth into the error if it is
|
|
// capable of recording it.
|
|
func record(err error, depth int) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return err
|
|
}
|
|
cast.exits = append(cast.exits, callerState(depth))
|
|
return cast
|
|
}
|
|
|
|
// Record will record the current pc on the given error if possible, adding
|
|
// to the error's recorded exits list. Returns the given error argument.
|
|
func Record(err error) error {
|
|
return record(err, 3)
|
|
}
|
|
|
|
// RecordBefore will record the pc depth frames above the current stack frame
|
|
// on the given error if possible, adding to the error's recorded exits list.
|
|
// Record(err) is equivalent to RecordBefore(err, 0). Returns the given error
|
|
// argument.
|
|
func RecordBefore(err error, depth int) error {
|
|
return record(err, 3+depth)
|
|
}
|
|
|
|
// Error is the type that represents a specific error instance. It is not
|
|
// expected that you will work with *Error classes directly. Instead, you
|
|
// should use the 'error' interface and errors package methods that operate
|
|
// on errors instances.
|
|
type Error struct {
|
|
err error
|
|
class *ErrorClass
|
|
stacks [][]frame
|
|
exits []frame
|
|
data map[DataKey]interface{}
|
|
}
|
|
|
|
// GetData returns the value associated with the given DataKey on this error
|
|
// or any of its ancestors. Please see the example for SetData
|
|
func (e *Error) GetData(key DataKey) interface{} {
|
|
if e.data != nil {
|
|
val, ok := e.data[key]
|
|
if ok {
|
|
return val
|
|
}
|
|
if boolWrapper(e.data[disableInheritance], false) {
|
|
return nil
|
|
}
|
|
}
|
|
return e.class.data[key]
|
|
}
|
|
|
|
// GetData returns the value associated with the given DataKey on this error
|
|
// or any of its ancestors. Please see the example for SetData
|
|
func GetData(err error, key DataKey) interface{} {
|
|
cast, ok := err.(*Error)
|
|
if ok {
|
|
return cast.GetData(key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *ErrorClass) wrap(err error, classes []*ErrorClass,
|
|
options []ErrorOption) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if ec, ok := err.(*Error); ok {
|
|
if ec.Is(e) {
|
|
if len(options) == 0 {
|
|
return ec
|
|
}
|
|
// if we have options, we have to wrap it cause we don't want to
|
|
// mutate the existing error.
|
|
} else {
|
|
for _, class := range classes {
|
|
if ec.Is(class) {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rv := &Error{err: err, class: e}
|
|
if len(options) > 0 {
|
|
rv.data = make(map[DataKey]interface{})
|
|
for _, option := range options {
|
|
option(rv.data)
|
|
}
|
|
}
|
|
|
|
if boolWrapper(rv.GetData(captureStack), false) {
|
|
rv.stacks = [][]frame{getStack(3)}
|
|
}
|
|
if boolWrapper(rv.GetData(logOnCreation), false) {
|
|
LogWithStack(rv.Error())
|
|
}
|
|
return rv
|
|
}
|
|
|
|
func getStack(depth int) (stack []frame) {
|
|
var pcs [256]uintptr
|
|
amount := runtime.Callers(depth+1, pcs[:])
|
|
stack = make([]frame, amount)
|
|
for i := 0; i < amount; i++ {
|
|
stack[i] = frame{pcs[i]}
|
|
}
|
|
return stack
|
|
}
|
|
|
|
// AttachStack adds another stack to the current error's stack trace if it
|
|
// exists
|
|
func AttachStack(err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return
|
|
}
|
|
if len(cast.stacks) < 1 {
|
|
// only record stacks if this error was supposed to
|
|
return
|
|
}
|
|
cast.stacks = append(cast.stacks, getStack(2))
|
|
}
|
|
|
|
// WrapUnless wraps the given error in the receiver error class unless the
|
|
// error is already an instance of one of the provided error classes.
|
|
func (e *ErrorClass) WrapUnless(err error, classes ...*ErrorClass) error {
|
|
return e.wrap(err, classes, nil)
|
|
}
|
|
|
|
// Wrap wraps the given error in the receiver error class with the provided
|
|
// error-specific options.
|
|
func (e *ErrorClass) Wrap(err error, options ...ErrorOption) error {
|
|
return e.wrap(err, nil, options)
|
|
}
|
|
|
|
// New makes a new error type. It takes a format string.
|
|
func (e *ErrorClass) New(format string, args ...interface{}) error {
|
|
return e.wrap(fmt.Errorf(format, args...), nil, nil)
|
|
}
|
|
|
|
// NewWith makes a new error type with the provided error-specific options.
|
|
func (e *ErrorClass) NewWith(message string, options ...ErrorOption) error {
|
|
return e.wrap(errors.New(message), nil, options)
|
|
}
|
|
|
|
// Error conforms to the error interface. Error will return the backtrace if
|
|
// it was captured and any recorded exits.
|
|
func (e *Error) Error() string {
|
|
message := strings.TrimRight(e.err.Error(), "\n ")
|
|
if strings.Contains(message, "\n") {
|
|
message = fmt.Sprintf("%s:\n %s", e.class.String(),
|
|
strings.Replace(message, "\n", "\n ", -1))
|
|
} else {
|
|
message = fmt.Sprintf("%s: %s", e.class.String(), message)
|
|
}
|
|
if stack := e.Stack(); stack != "" {
|
|
message = fmt.Sprintf(
|
|
"%s\n\"%s\" backtrace:\n%s", message, e.class, stack)
|
|
}
|
|
if exits := e.Exits(); exits != "" {
|
|
message = fmt.Sprintf(
|
|
"%s\n\"%s\" exits:\n%s", message, e.class, exits)
|
|
}
|
|
return message
|
|
}
|
|
|
|
// Message returns just the error message without the backtrace or exits.
|
|
func (e *Error) Message() string {
|
|
message := strings.TrimRight(GetMessage(e.err), "\n ")
|
|
if strings.Contains(message, "\n") {
|
|
return fmt.Sprintf("%s:\n %s", e.class.String(),
|
|
strings.Replace(message, "\n", "\n ", -1))
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.class.String(), message)
|
|
}
|
|
|
|
// WrappedErr returns the wrapped error, if the current error is simply
|
|
// wrapping some previously returned error or system error. You probably want
|
|
// the package-level WrappedErr
|
|
func (e *Error) WrappedErr() error {
|
|
return e.err
|
|
}
|
|
|
|
// WrappedErr returns the wrapped error, if the current error is simply
|
|
// wrapping some previously returned error or system error. If the error isn't
|
|
// hierarchical it is just returned.
|
|
func WrappedErr(err error) error {
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return err
|
|
}
|
|
return cast.WrappedErr()
|
|
}
|
|
|
|
// Class will return the appropriate error class for the given error. You
|
|
// probably want the package-level GetClass.
|
|
func (e *Error) Class() *ErrorClass {
|
|
return e.class
|
|
}
|
|
|
|
// Name returns the name of the error: in this case the name of the class the
|
|
// error belongs to.
|
|
func (e *Error) Name() (string, bool) {
|
|
return e.class.name, true
|
|
}
|
|
|
|
// GetClass will return the appropriate error class for the given error.
|
|
// If the error is not nil, GetClass always returns a hierarchical error class,
|
|
// and even attempts to determine a class for common system error types.
|
|
func GetClass(err error) *ErrorClass {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return findSystemErrorClass(err)
|
|
}
|
|
return cast.class
|
|
}
|
|
|
|
// Stack will return the stack associated with the error if one is found. You
|
|
// probably want the package-level GetStack.
|
|
func (e *Error) Stack() string {
|
|
if len(e.stacks) > 0 {
|
|
var frames []string
|
|
for _, stack := range e.stacks {
|
|
if frames == nil {
|
|
frames = make([]string, 0, len(stack))
|
|
} else {
|
|
frames = append(frames, "----- attached stack -----")
|
|
}
|
|
for _, f := range stack {
|
|
frames = append(frames, f.String())
|
|
}
|
|
}
|
|
return strings.Join(frames, "\n")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetStack will return the stack associated with the error if one is found.
|
|
func GetStack(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return cast.Stack()
|
|
}
|
|
|
|
// Exits will return the exits recorded on the error if any are found. You
|
|
// probably want the package-level GetExits.
|
|
func (e *Error) Exits() string {
|
|
if len(e.exits) > 0 {
|
|
exits := make([]string, len(e.exits))
|
|
for i, ex := range e.exits {
|
|
exits[i] = ex.String()
|
|
}
|
|
return strings.Join(exits, "\n")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetExits will return the exits recorded on the error if any are found.
|
|
func GetExits(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return cast.Exits()
|
|
}
|
|
|
|
// GetMessage returns just the error message without the backtrace or exits.
|
|
func GetMessage(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return err.Error()
|
|
}
|
|
return cast.Message()
|
|
}
|
|
|
|
// EquivalenceOption values control behavior of determining whether or not an
|
|
// error belongs to a specific class.
|
|
type EquivalenceOption int
|
|
|
|
const (
|
|
// If IncludeWrapped is used, wrapped errors are also used for determining
|
|
// class membership.
|
|
IncludeWrapped EquivalenceOption = 1
|
|
)
|
|
|
|
func combineEquivOpts(opts []EquivalenceOption) (rv EquivalenceOption) {
|
|
for _, opt := range opts {
|
|
rv |= opt
|
|
}
|
|
return rv
|
|
}
|
|
|
|
// Is returns whether or not an error belongs to a specific class. Typically
|
|
// you should use Contains instead.
|
|
func (e *Error) Is(ec *ErrorClass, opts ...EquivalenceOption) bool {
|
|
return ec.Contains(e, opts...)
|
|
}
|
|
|
|
// Contains returns whether or not the receiver error class contains the given
|
|
// error instance.
|
|
func (e *ErrorClass) Contains(err error, opts ...EquivalenceOption) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
cast, ok := err.(*Error)
|
|
if !ok {
|
|
return findSystemErrorClass(err).Is(e)
|
|
}
|
|
if cast.class.Is(e) {
|
|
return true
|
|
}
|
|
if combineEquivOpts(opts)&IncludeWrapped == 0 {
|
|
return false
|
|
}
|
|
return e.Contains(cast.err, opts...)
|
|
}
|
|
|
|
var (
|
|
// Useful error classes
|
|
NotImplementedError = NewClass("Not Implemented Error", LogOnCreation())
|
|
ProgrammerError = NewClass("Programmer Error", LogOnCreation())
|
|
PanicError = NewClass("Panic Error", LogOnCreation())
|
|
|
|
// The following SystemError descendants are provided such that the GetClass
|
|
// method has something to return for standard library error types not
|
|
// defined through this class.
|
|
//
|
|
// It is not expected that anyone would create instances of these classes.
|
|
//
|
|
// from os
|
|
SyscallError = SystemError.NewClass("Syscall Error")
|
|
// from syscall
|
|
ErrnoError = SystemError.NewClass("Errno Error")
|
|
// from net
|
|
NetworkError = SystemError.NewClass("Network Error")
|
|
UnknownNetworkError = NetworkError.NewClass("Unknown Network Error")
|
|
AddrError = NetworkError.NewClass("Addr Error")
|
|
InvalidAddrError = AddrError.NewClass("Invalid Addr Error")
|
|
NetOpError = NetworkError.NewClass("Network Op Error")
|
|
NetParseError = NetworkError.NewClass("Network Parse Error")
|
|
DNSError = NetworkError.NewClass("DNS Error")
|
|
DNSConfigError = DNSError.NewClass("DNS Config Error")
|
|
// from io
|
|
IOError = SystemError.NewClass("IO Error")
|
|
EOF = IOError.NewClass("EOF")
|
|
ClosedPipeError = IOError.NewClass("Closed Pipe Error")
|
|
NoProgressError = IOError.NewClass("No Progress Error")
|
|
ShortBufferError = IOError.NewClass("Short Buffer Error")
|
|
ShortWriteError = IOError.NewClass("Short Write Error")
|
|
UnexpectedEOFError = IOError.NewClass("Unexpected EOF Error")
|
|
// from context
|
|
ContextError = SystemError.NewClass("Context Error")
|
|
ContextCanceled = ContextError.NewClass("Canceled")
|
|
ContextTimeout = ContextError.NewClass("Timeout")
|
|
)
|
|
|
|
func findSystemErrorClass(err error) *ErrorClass {
|
|
switch err {
|
|
case io.EOF:
|
|
return EOF
|
|
case io.ErrUnexpectedEOF:
|
|
return UnexpectedEOFError
|
|
case io.ErrClosedPipe:
|
|
return ClosedPipeError
|
|
case io.ErrNoProgress:
|
|
return NoProgressError
|
|
case io.ErrShortBuffer:
|
|
return ShortBufferError
|
|
case io.ErrShortWrite:
|
|
return ShortWriteError
|
|
case contextCanceled:
|
|
return ContextCanceled
|
|
case contextDeadlineExceeded:
|
|
return ContextTimeout
|
|
default:
|
|
break
|
|
}
|
|
if isErrnoError(err) {
|
|
return ErrnoError
|
|
}
|
|
switch err.(type) {
|
|
case *os.SyscallError:
|
|
return SyscallError
|
|
case net.UnknownNetworkError:
|
|
return UnknownNetworkError
|
|
case *net.AddrError:
|
|
return AddrError
|
|
case net.InvalidAddrError:
|
|
return InvalidAddrError
|
|
case *net.OpError:
|
|
return NetOpError
|
|
case *net.ParseError:
|
|
return NetParseError
|
|
case *net.DNSError:
|
|
return DNSError
|
|
case *net.DNSConfigError:
|
|
return DNSConfigError
|
|
case net.Error:
|
|
return NetworkError
|
|
default:
|
|
return SystemError
|
|
}
|
|
}
|