mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-14 00:42:44 +00:00
331 lines
8.2 KiB
Go
331 lines
8.2 KiB
Go
|
// Copyright 2014 Canonical Ltd.
|
||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||
|
|
||
|
package errors
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// New is a drop in replacement for the standard library errors module that records
|
||
|
// the location that the error is created.
|
||
|
//
|
||
|
// For example:
|
||
|
// return errors.New("validation failed")
|
||
|
//
|
||
|
func New(message string) error {
|
||
|
err := &Err{message: message}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Errorf creates a new annotated error and records the location that the
|
||
|
// error is created. This should be a drop in replacement for fmt.Errorf.
|
||
|
//
|
||
|
// For example:
|
||
|
// return errors.Errorf("validation failed: %s", message)
|
||
|
//
|
||
|
func Errorf(format string, args ...interface{}) error {
|
||
|
err := &Err{message: fmt.Sprintf(format, args...)}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Trace adds the location of the Trace call to the stack. The Cause of the
|
||
|
// resulting error is the same as the error parameter. If the other error is
|
||
|
// nil, the result will be nil.
|
||
|
//
|
||
|
// For example:
|
||
|
// if err := SomeFunc(); err != nil {
|
||
|
// return errors.Trace(err)
|
||
|
// }
|
||
|
//
|
||
|
func Trace(other error) error {
|
||
|
if other == nil {
|
||
|
return nil
|
||
|
}
|
||
|
err := &Err{previous: other, cause: Cause(other)}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Annotate is used to add extra context to an existing error. The location of
|
||
|
// the Annotate call is recorded with the annotations. The file, line and
|
||
|
// function are also recorded.
|
||
|
//
|
||
|
// For example:
|
||
|
// if err := SomeFunc(); err != nil {
|
||
|
// return errors.Annotate(err, "failed to frombulate")
|
||
|
// }
|
||
|
//
|
||
|
func Annotate(other error, message string) error {
|
||
|
if other == nil {
|
||
|
return nil
|
||
|
}
|
||
|
err := &Err{
|
||
|
previous: other,
|
||
|
cause: Cause(other),
|
||
|
message: message,
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Annotatef is used to add extra context to an existing error. The location of
|
||
|
// the Annotate call is recorded with the annotations. The file, line and
|
||
|
// function are also recorded.
|
||
|
//
|
||
|
// For example:
|
||
|
// if err := SomeFunc(); err != nil {
|
||
|
// return errors.Annotatef(err, "failed to frombulate the %s", arg)
|
||
|
// }
|
||
|
//
|
||
|
func Annotatef(other error, format string, args ...interface{}) error {
|
||
|
if other == nil {
|
||
|
return nil
|
||
|
}
|
||
|
err := &Err{
|
||
|
previous: other,
|
||
|
cause: Cause(other),
|
||
|
message: fmt.Sprintf(format, args...),
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// DeferredAnnotatef annotates the given error (when it is not nil) with the given
|
||
|
// format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
|
||
|
// does nothing. This method is used in a defer statement in order to annotate any
|
||
|
// resulting error with the same message.
|
||
|
//
|
||
|
// For example:
|
||
|
//
|
||
|
// defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
|
||
|
//
|
||
|
func DeferredAnnotatef(err *error, format string, args ...interface{}) {
|
||
|
if *err == nil {
|
||
|
return
|
||
|
}
|
||
|
newErr := &Err{
|
||
|
message: fmt.Sprintf(format, args...),
|
||
|
cause: Cause(*err),
|
||
|
previous: *err,
|
||
|
}
|
||
|
newErr.SetLocation(1)
|
||
|
*err = newErr
|
||
|
}
|
||
|
|
||
|
// Wrap changes the Cause of the error. The location of the Wrap call is also
|
||
|
// stored in the error stack.
|
||
|
//
|
||
|
// For example:
|
||
|
// if err := SomeFunc(); err != nil {
|
||
|
// newErr := &packageError{"more context", private_value}
|
||
|
// return errors.Wrap(err, newErr)
|
||
|
// }
|
||
|
//
|
||
|
func Wrap(other, newDescriptive error) error {
|
||
|
err := &Err{
|
||
|
previous: other,
|
||
|
cause: newDescriptive,
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Wrapf changes the Cause of the error, and adds an annotation. The location
|
||
|
// of the Wrap call is also stored in the error stack.
|
||
|
//
|
||
|
// For example:
|
||
|
// if err := SomeFunc(); err != nil {
|
||
|
// return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
|
||
|
// }
|
||
|
//
|
||
|
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error {
|
||
|
err := &Err{
|
||
|
message: fmt.Sprintf(format, args...),
|
||
|
previous: other,
|
||
|
cause: newDescriptive,
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Mask masks the given error with the given format string and arguments (like
|
||
|
// fmt.Sprintf), returning a new error that maintains the error stack, but
|
||
|
// hides the underlying error type. The error string still contains the full
|
||
|
// annotations. If you want to hide the annotations, call Wrap.
|
||
|
func Maskf(other error, format string, args ...interface{}) error {
|
||
|
if other == nil {
|
||
|
return nil
|
||
|
}
|
||
|
err := &Err{
|
||
|
message: fmt.Sprintf(format, args...),
|
||
|
previous: other,
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Mask hides the underlying error type, and records the location of the masking.
|
||
|
func Mask(other error) error {
|
||
|
if other == nil {
|
||
|
return nil
|
||
|
}
|
||
|
err := &Err{
|
||
|
previous: other,
|
||
|
}
|
||
|
err.SetLocation(1)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Cause returns the cause of the given error. This will be either the
|
||
|
// original error, or the result of a Wrap or Mask call.
|
||
|
//
|
||
|
// Cause is the usual way to diagnose errors that may have been wrapped by
|
||
|
// the other errors functions.
|
||
|
func Cause(err error) error {
|
||
|
var diag error
|
||
|
if err, ok := err.(causer); ok {
|
||
|
diag = err.Cause()
|
||
|
}
|
||
|
if diag != nil {
|
||
|
return diag
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
type causer interface {
|
||
|
Cause() error
|
||
|
}
|
||
|
|
||
|
type wrapper interface {
|
||
|
// Message returns the top level error message,
|
||
|
// not including the message from the Previous
|
||
|
// error.
|
||
|
Message() string
|
||
|
|
||
|
// Underlying returns the Previous error, or nil
|
||
|
// if there is none.
|
||
|
Underlying() error
|
||
|
}
|
||
|
|
||
|
type locationer interface {
|
||
|
Location() (string, int)
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
_ wrapper = (*Err)(nil)
|
||
|
_ locationer = (*Err)(nil)
|
||
|
_ causer = (*Err)(nil)
|
||
|
)
|
||
|
|
||
|
// Details returns information about the stack of errors wrapped by err, in
|
||
|
// the format:
|
||
|
//
|
||
|
// [{filename:99: error one} {otherfile:55: cause of error one}]
|
||
|
//
|
||
|
// This is a terse alternative to ErrorStack as it returns a single line.
|
||
|
func Details(err error) string {
|
||
|
if err == nil {
|
||
|
return "[]"
|
||
|
}
|
||
|
var s []byte
|
||
|
s = append(s, '[')
|
||
|
for {
|
||
|
s = append(s, '{')
|
||
|
if err, ok := err.(locationer); ok {
|
||
|
file, line := err.Location()
|
||
|
if file != "" {
|
||
|
s = append(s, fmt.Sprintf("%s:%d", file, line)...)
|
||
|
s = append(s, ": "...)
|
||
|
}
|
||
|
}
|
||
|
if cerr, ok := err.(wrapper); ok {
|
||
|
s = append(s, cerr.Message()...)
|
||
|
err = cerr.Underlying()
|
||
|
} else {
|
||
|
s = append(s, err.Error()...)
|
||
|
err = nil
|
||
|
}
|
||
|
s = append(s, '}')
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
s = append(s, ' ')
|
||
|
}
|
||
|
s = append(s, ']')
|
||
|
return string(s)
|
||
|
}
|
||
|
|
||
|
// ErrorStack returns a string representation of the annotated error. If the
|
||
|
// error passed as the parameter is not an annotated error, the result is
|
||
|
// simply the result of the Error() method on that error.
|
||
|
//
|
||
|
// If the error is an annotated error, a multi-line string is returned where
|
||
|
// each line represents one entry in the annotation stack. The full filename
|
||
|
// from the call stack is used in the output.
|
||
|
//
|
||
|
// first error
|
||
|
// github.com/juju/errors/annotation_test.go:193:
|
||
|
// github.com/juju/errors/annotation_test.go:194: annotation
|
||
|
// github.com/juju/errors/annotation_test.go:195:
|
||
|
// github.com/juju/errors/annotation_test.go:196: more context
|
||
|
// github.com/juju/errors/annotation_test.go:197:
|
||
|
func ErrorStack(err error) string {
|
||
|
return strings.Join(errorStack(err), "\n")
|
||
|
}
|
||
|
|
||
|
func errorStack(err error) []string {
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// We want the first error first
|
||
|
var lines []string
|
||
|
for {
|
||
|
var buff []byte
|
||
|
if err, ok := err.(locationer); ok {
|
||
|
file, line := err.Location()
|
||
|
// Strip off the leading GOPATH/src path elements.
|
||
|
file = trimGoPath(file)
|
||
|
if file != "" {
|
||
|
buff = append(buff, fmt.Sprintf("%s:%d", file, line)...)
|
||
|
buff = append(buff, ": "...)
|
||
|
}
|
||
|
}
|
||
|
if cerr, ok := err.(wrapper); ok {
|
||
|
message := cerr.Message()
|
||
|
buff = append(buff, message...)
|
||
|
// If there is a cause for this error, and it is different to the cause
|
||
|
// of the underlying error, then output the error string in the stack trace.
|
||
|
var cause error
|
||
|
if err1, ok := err.(causer); ok {
|
||
|
cause = err1.Cause()
|
||
|
}
|
||
|
err = cerr.Underlying()
|
||
|
if cause != nil && !sameError(Cause(err), cause) {
|
||
|
if message != "" {
|
||
|
buff = append(buff, ": "...)
|
||
|
}
|
||
|
buff = append(buff, cause.Error()...)
|
||
|
}
|
||
|
} else {
|
||
|
buff = append(buff, err.Error()...)
|
||
|
err = nil
|
||
|
}
|
||
|
lines = append(lines, string(buff))
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
// reverse the lines to get the original error, which was at the end of
|
||
|
// the list, back to the start.
|
||
|
var result []string
|
||
|
for i := len(lines); i > 0; i-- {
|
||
|
result = append(result, lines[i-1])
|
||
|
}
|
||
|
return result
|
||
|
}
|