rclone/vendor/github.com/spacemonkeygo/errors/errors.go
2020-05-12 15:56:50 +00:00

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
}
}