diff --git a/error.go b/error.go index 0b3942fb..d9463583 100644 --- a/error.go +++ b/error.go @@ -6,7 +6,6 @@ package errors import ( "fmt" "reflect" - "runtime" ) // Err holds a description of an error along with information about @@ -33,6 +32,43 @@ type Err struct { line int } +// Locationer is an interface that represents a certain class of errors that +// contain the location information from where they were raised. +type Locationer interface { + // Location returns the path-qualified function name where the error was + // created and the line number + Location() (function string, line int) +} + +// locationError is the internal implementation of the Locationer interface. +type locationError struct { + error + + // function is the package path-qualified function name where the + // error was created. + function string + + // line is the line number the error was created on inside of function + line int +} + +// newLocationError constructs a new Locationer error from the supplied error +// with the location set to callDepth in the stack. +func newLocationError(err error, callDepth int) *locationError { + le := &locationError{error: err} + le.function, le.line = getLocation(callDepth + 1) + return le +} + +// *locationError implements Locationer.Location interface +func (l *locationError) Location() (string, int) { + return l.function, l.line +} + +func (l *locationError) Unwrap() error { + return l.error +} + // NewErr is used to return an Err for the purpose of embedding in other // structures. The location is not specified, and needs to be set with a call // to SetLocation. @@ -160,14 +196,7 @@ func (unformatter) Format() { /* break the fmt.Formatter interface */ } // SetLocation records the package path-qualified function name of the error at // callDepth stack frames above the call. func (e *Err) SetLocation(callDepth int) { - rpc := make([]uintptr, 1) - n := runtime.Callers(callDepth+2, rpc[:]) - if n < 1 { - return - } - frame, _ := runtime.CallersFrames(rpc).Next() - e.function = frame.Function - e.line = frame.Line + e.function, e.line = getLocation(callDepth + 1) } // StackTrace returns one string for each location recorded in the stack of diff --git a/errortypes.go b/errortypes.go index 8647c7ae..a8121fe4 100644 --- a/errortypes.go +++ b/errortypes.go @@ -4,379 +4,460 @@ package errors import ( + stderror "errors" "fmt" + "strings" ) -// wrap is a helper to construct an *wrapper. -func wrap(err error, format, suffix string, args ...interface{}) Err { - newErr := Err{ - message: fmt.Sprintf(format+suffix, args...), - previous: err, +// a ConstError is a prototype for a certain type of error +type ConstError string + +// ConstError implements error +func (e ConstError) Error() string { + return string(e) +} + +// Different types of errors +const ( + // Timeout represents an error on timeout. + Timeout = ConstError("timeout") + // NotFound represents an error when something has not been found. + NotFound = ConstError("not found") + // UserNotFound represents an error when a non-existent user is looked up. + UserNotFound = ConstError("user not found") + // Unauthorized represents an error when an operation is unauthorized. + Unauthorized = ConstError("unauthorized") + // NotImplemented represents an error when something is not + // implemented. + NotImplemented = ConstError("not implemented") + // AlreadyExists represents and error when something already exists. + AlreadyExists = ConstError("already exists") + // NotSupported represents an error when something is not supported. + NotSupported = ConstError("not supported") + // NotValid represents an error when something is not valid. + NotValid = ConstError("not valid") + // NotProvisioned represents an error when something is not yet provisioned. + NotProvisioned = ConstError("not provisioned") + // NotAssigned represents an error when something is not yet assigned to + // something else. + NotAssigned = ConstError("not assigned") + // BadRequest represents an error when a request has bad parameters. + BadRequest = ConstError("bad request") + // MethodNotAllowed represents an error when an HTTP request + // is made with an inappropriate method. + MethodNotAllowed = ConstError("method not allowed") + // Forbidden represents an error when a request cannot be completed because of + // missing privileges. + Forbidden = ConstError("forbidden") + // QuotaLimitExceeded is emitted when an action failed due to a quota limit check. + QuotaLimitExceeded = ConstError("quota limit exceeded") + // NotYetAvailable is the error returned when a resource is not yet available + // but it might be in the future. + NotYetAvailable = ConstError("not yet available") +) + +// constSuppressor is a small type wrapper for ConstError to surpress the error +// value from returning an error value. This allows us to maintain backwards +// compatibility. +type constSuppressor ConstError + +func (c constSuppressor) Error() string { return "" } + +func (c constSuppressor) Unwrap() error { return ConstError(c) } + +// errWithType is an Err bundled with its error type (a ConstError) +type errWithType struct { + error + errType ConstError +} + +// Is compares `target` with e's error type +func (e *errWithType) Is(target error) bool { + if &e.errType == nil { + return false + } + return target == e.errType +} + +// Unwrap an errWithType gives the underlying Err +func (e *errWithType) Unwrap() error { + return e.error +} + +func wrapErrorWithMsg(err error, msg string) error { + if msg == "" { + return err } - newErr.SetLocation(2) - return newErr + if err == nil { + return stderror.New(msg) + } + return fmt.Errorf("%s: %w", msg, err) } -// timeout represents an error on timeout. -type timeout struct { - Err +func makeWrappedConstError(err error, format string, args ...interface{}) error { + separator := " " + if err.Error() == "" { + separator = "" + } + return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...) } -// Timeoutf returns an error which satisfies IsTimeout(). +// Timeoutf returns an error which satisfies Is(err, Timeout) and the Locationer +// interface. func Timeoutf(format string, args ...interface{}) error { - return &timeout{wrap(nil, format, " timeout", args...)} + return newLocationError( + makeWrappedConstError(Timeout, format, args...), + 1, + ) } -// NewTimeout returns an error which wraps err that satisfies -// IsTimeout(). +// NewTimeout returns an error which wraps err and satisfies Is(err, Timeout) +// and the Locationer interface. func NewTimeout(err error, msg string) error { - return &timeout{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: Timeout, + } } -// IsTimeout reports whether err was created with Timeoutf() or -// NewTimeout(). +// Deprecated: IsTimeout reports whether err is a Timeout error. Use +// Is(err, Timeout). func IsTimeout(err error) bool { - err = Cause(err) - _, ok := err.(*timeout) - return ok -} - -// notFound represents an error when something has not been found. -type notFound struct { - Err + return Is(err, Timeout) } -// NotFoundf returns an error which satisfies IsNotFound(). +// NotFoundf returns an error which satisfies Is(err, NotFound) and the +// Locationer interface. func NotFoundf(format string, args ...interface{}) error { - return ¬Found{wrap(nil, format, " not found", args...)} + return newLocationError( + makeWrappedConstError(NotFound, format, args...), + 1, + ) } -// NewNotFound returns an error which wraps err that satisfies -// IsNotFound(). +// NewNotFound returns an error which wraps err and satisfies Is(err, NotFound) +// and the Locationer interface. func NewNotFound(err error, msg string) error { - return ¬Found{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotFound, + } } -// IsNotFound reports whether err was created with NotFoundf() or -// NewNotFound(). +// Deprecated: IsNotFound reports whether err is a NotFound error. Use +// Is(err, NotFound). func IsNotFound(err error) bool { - err = Cause(err) - _, ok := err.(*notFound) - return ok -} - -// userNotFound represents an error when an inexistent user is looked up. -type userNotFound struct { - Err + return Is(err, NotFound) } -// UserNotFoundf returns an error which satisfies IsUserNotFound(). +// UserNotFoundf returns an error which satisfies Is(err, UserNotFound) and the +// Locationer interface. func UserNotFoundf(format string, args ...interface{}) error { - return &userNotFound{wrap(nil, format, " user not found", args...)} + return newLocationError( + makeWrappedConstError(UserNotFound, format, args...), + 1, + ) } // NewUserNotFound returns an error which wraps err and satisfies -// IsUserNotFound(). +// Is(err, UserNotFound) and the Locationer interface. func NewUserNotFound(err error, msg string) error { - return &userNotFound{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: UserNotFound, + } } -// IsUserNotFound reports whether err was created with UserNotFoundf() or -// NewUserNotFound(). +// Deprecated: IsUserNotFound reports whether err is a UserNotFound error. Use +// Is(err, UserNotFound). func IsUserNotFound(err error) bool { - err = Cause(err) - _, ok := err.(*userNotFound) - return ok -} - -// unauthorized represents an error when an operation is unauthorized. -type unauthorized struct { - Err + return Is(err, UserNotFound) } -// Unauthorizedf returns an error which satisfies IsUnauthorized(). +// Unauthorizedf returns an error that satisfies Is(err, Unauthorized) and +// the Locationer interface. func Unauthorizedf(format string, args ...interface{}) error { - return &unauthorized{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(Unauthorized), format, args...), + 1, + ) } // NewUnauthorized returns an error which wraps err and satisfies -// IsUnauthorized(). +// Is(err, Unathorized) and the Locationer interface. func NewUnauthorized(err error, msg string) error { - return &unauthorized{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: Unauthorized, + } } -// IsUnauthorized reports whether err was created with Unauthorizedf() or -// NewUnauthorized(). +// Deprecated: IsUnauthorized reports whether err is a Unauthorized error. Use +// Is(err, Unauthorized). func IsUnauthorized(err error) bool { - err = Cause(err) - _, ok := err.(*unauthorized) - return ok + return Is(err, Unauthorized) } -// notImplemented represents an error when something is not -// implemented. -type notImplemented struct { - Err -} - -// NotImplementedf returns an error which satisfies IsNotImplemented(). +// NotImplementedf returns an error which satisfies Is(err, NotImplemented) and +// the Locationer interface. func NotImplementedf(format string, args ...interface{}) error { - return ¬Implemented{wrap(nil, format, " not implemented", args...)} + return newLocationError( + makeWrappedConstError(NotImplemented, format, args...), + 1, + ) } // NewNotImplemented returns an error which wraps err and satisfies -// IsNotImplemented(). +// Is(err, NotImplemented) and the Locationer interface. func NewNotImplemented(err error, msg string) error { - return ¬Implemented{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotImplemented, + } } -// IsNotImplemented reports whether err was created with -// NotImplementedf() or NewNotImplemented(). +// Deprecated: IsNotImplemented reports whether err is a NotImplemented error. +// Use Is(err, NotImplemented). func IsNotImplemented(err error) bool { - err = Cause(err) - _, ok := err.(*notImplemented) - return ok -} - -// alreadyExists represents and error when something already exists. -type alreadyExists struct { - Err + return Is(err, NotImplemented) } -// AlreadyExistsf returns an error which satisfies IsAlreadyExists(). +// AlreadyExistsf returns an error which satisfies Is(err, AlreadyExists) and +// the Locationer interface. func AlreadyExistsf(format string, args ...interface{}) error { - return &alreadyExists{wrap(nil, format, " already exists", args...)} + return newLocationError( + makeWrappedConstError(AlreadyExists, format, args...), + 1, + ) } // NewAlreadyExists returns an error which wraps err and satisfies -// IsAlreadyExists(). +// Is(err, AlreadyExists) and the Locationer interface. func NewAlreadyExists(err error, msg string) error { - return &alreadyExists{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: AlreadyExists, + } } -// IsAlreadyExists reports whether the error was created with -// AlreadyExistsf() or NewAlreadyExists(). +// Deprecated: IsAlreadyExists reports whether the err is a AlreadyExists +// error. Use Is(err, AlreadyExists). func IsAlreadyExists(err error) bool { - err = Cause(err) - _, ok := err.(*alreadyExists) - return ok -} - -// notSupported represents an error when something is not supported. -type notSupported struct { - Err + return Is(err, AlreadyExists) } -// NotSupportedf returns an error which satisfies IsNotSupported(). +// NotSupportedf returns an error which satisfies Is(err, NotSupported) and the +// Locationer interface. func NotSupportedf(format string, args ...interface{}) error { - return ¬Supported{wrap(nil, format, " not supported", args...)} + return newLocationError( + makeWrappedConstError(NotSupported, format, args...), + 1, + ) } -// NewNotSupported returns an error which wraps err and satisfies -// IsNotSupported(). +// NewNotSupported returns an error which satisfies Is(err, NotSupported) and +// the Locationer interface. func NewNotSupported(err error, msg string) error { - return ¬Supported{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotSupported, + } } -// IsNotSupported reports whether the error was created with -// NotSupportedf() or NewNotSupported(). +// Deprecated: IsNotSupported reports whether err is a NotSupported error. Use +// Is(err, NotSupported). func IsNotSupported(err error) bool { - err = Cause(err) - _, ok := err.(*notSupported) - return ok -} - -// notValid represents an error when something is not valid. -type notValid struct { - Err + return Is(err, NotSupported) } -// NotValidf returns an error which satisfies IsNotValid(). +// NotValidf returns an error which satisfies Is(err, NotValid) and the +// Locationer interface. func NotValidf(format string, args ...interface{}) error { - return ¬Valid{wrap(nil, format, " not valid", args...)} + return newLocationError( + makeWrappedConstError(NotValid, format, args...), + 1, + ) } -// NewNotValid returns an error which wraps err and satisfies IsNotValid(). +// NewNotValid returns an error which wraps err and satisfies Is(err, NotValid) +// and the Locationer interface. func NewNotValid(err error, msg string) error { - return ¬Valid{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotValid, + } } -// IsNotValid reports whether the error was created with NotValidf() or -// NewNotValid(). +// Deprecated: IsNotValid reports whether err is a NotValid error. Use +// Is(err, NotValid). func IsNotValid(err error) bool { - err = Cause(err) - _, ok := err.(*notValid) - return ok + return Is(err, NotValid) } -// notProvisioned represents an error when something is not yet provisioned. -type notProvisioned struct { - Err -} - -// NotProvisionedf returns an error which satisfies IsNotProvisioned(). +// NotProvisionedf returns an error which satisfies Is(err, NotProvisioned) and +// the Locationer interface. func NotProvisionedf(format string, args ...interface{}) error { - return ¬Provisioned{wrap(nil, format, " not provisioned", args...)} + return newLocationError( + makeWrappedConstError(NotProvisioned, format, args...), + 1, + ) } -// NewNotProvisioned returns an error which wraps err that satisfies -// IsNotProvisioned(). +// NewNotProvisioned returns an error which wraps err and satisfies +// Is(err, NotProvisioned) and the Locationer interface. func NewNotProvisioned(err error, msg string) error { - return ¬Provisioned{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotProvisioned, + } } -// IsNotProvisioned reports whether err was created with NotProvisionedf() or -// NewNotProvisioned(). +// Deprecated: IsNotProvisioned reports whether err is a NotProvisioned error. +// Use Is(err, NotProvisioned). func IsNotProvisioned(err error) bool { - err = Cause(err) - _, ok := err.(*notProvisioned) - return ok -} - -// notAssigned represents an error when something is not yet assigned to -// something else. -type notAssigned struct { - Err + return Is(err, NotProvisioned) } -// NotAssignedf returns an error which satisfies IsNotAssigned(). +// NotAssignedf returns an error which satisfies Is(err, NotAssigned) and the +// Locationer interface. func NotAssignedf(format string, args ...interface{}) error { - return ¬Assigned{wrap(nil, format, " not assigned", args...)} + return newLocationError( + makeWrappedConstError(NotAssigned, format, args...), + 1, + ) } -// NewNotAssigned returns an error which wraps err that satisfies -// IsNotAssigned(). +// NewNotAssigned returns an error which wraps err and satisfies +// Is(err, NotAssigned) and the Locationer interface. func NewNotAssigned(err error, msg string) error { - return ¬Assigned{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotAssigned, + } } -// IsNotAssigned reports whether err was created with NotAssignedf() or -// NewNotAssigned(). +// Deprecated: IsNotAssigned reports whether err is a NotAssigned error. +// Use Is(err, NotAssigned) func IsNotAssigned(err error) bool { - err = Cause(err) - _, ok := err.(*notAssigned) - return ok -} - -// badRequest represents an error when a request has bad parameters. -type badRequest struct { - Err + return Is(err, NotAssigned) } -// BadRequestf returns an error which satisfies IsBadRequest(). +// BadRequestf returns an error which satisfies Is(err, BadRequest) and the +// Locationer interface. func BadRequestf(format string, args ...interface{}) error { - return &badRequest{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(BadRequest), format, args...), + 1, + ) } -// NewBadRequest returns an error which wraps err that satisfies -// IsBadRequest(). +// NewBadRequest returns an error which wraps err and satisfies +// Is(err, BadRequest) and the Locationer interface. func NewBadRequest(err error, msg string) error { - return &badRequest{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: BadRequest, + } } -// IsBadRequest reports whether err was created with BadRequestf() or -// NewBadRequest(). +// Deprecated: IsBadRequest reports whether err is a BadRequest error. +// Use Is(err, BadRequest) func IsBadRequest(err error) bool { - err = Cause(err) - _, ok := err.(*badRequest) - return ok -} - -// methodNotAllowed represents an error when an HTTP request -// is made with an inappropriate method. -type methodNotAllowed struct { - Err + return Is(err, BadRequest) } -// MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed(). +// MethodNotAllowedf returns an error which satisfies Is(err, MethodNotAllowed) +// and the Locationer interface. func MethodNotAllowedf(format string, args ...interface{}) error { - return &methodNotAllowed{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(MethodNotAllowed), format, args...), + 1, + ) } -// NewMethodNotAllowed returns an error which wraps err that satisfies -// IsMethodNotAllowed(). +// NewMethodNotAllowed returns an error which wraps err and satisfies +// Is(err, MethodNotAllowed) and the Locationer interface. func NewMethodNotAllowed(err error, msg string) error { - return &methodNotAllowed{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: MethodNotAllowed, + } } -// IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or -// NewMethodNotAllowed(). +// Deprecated: IsMethodNotAllowed reports whether err is a MethodNotAllowed +// error. Use Is(err, MethodNotAllowed) func IsMethodNotAllowed(err error) bool { - err = Cause(err) - _, ok := err.(*methodNotAllowed) - return ok + return Is(err, MethodNotAllowed) } -// forbidden represents an error when a request cannot be completed because of -// missing privileges -type forbidden struct { - Err -} - -// Forbiddenf returns an error which satistifes IsForbidden() +// Forbiddenf returns an error which satistifes Is(err, Forbidden) and the +// Locationer interface. func Forbiddenf(format string, args ...interface{}) error { - return &forbidden{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(Forbidden), format, args...), + 1, + ) } -// NewForbidden returns an error which wraps err that satisfies -// IsForbidden(). +// NewForbidden returns an error which wraps err and satisfies +// Is(err, Forbidden) and the Locationer interface. func NewForbidden(err error, msg string) error { - return &forbidden{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: Forbidden, + } } -// IsForbidden reports whether err was created with Forbiddenf() or -// NewForbidden(). +// Deprecated: IsForbidden reports whether err is a Forbidden error. Use +// Is(err, Forbidden). func IsForbidden(err error) bool { - err = Cause(err) - _, ok := err.(*forbidden) - return ok -} - -// quotaLimitExceeded is emitted when an action failed due to a quota limit check. -type quotaLimitExceeded struct { - Err + return Is(err, Forbidden) } -// QuotaLimitExceededf returns an error which satisfies IsQuotaLimitExceeded. +// QuotaLimitExceededf returns an error which satisfies +// Is(err, QuotaLimitExceeded) and the Locationer interface. func QuotaLimitExceededf(format string, args ...interface{}) error { - return "aLimitExceeded{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(QuotaLimitExceeded), format, args...), + 1, + ) } // NewQuotaLimitExceeded returns an error which wraps err and satisfies -// IsQuotaLimitExceeded. +// Is(err, QuotaLimitExceeded) and the Locationer interface. func NewQuotaLimitExceeded(err error, msg string) error { - return "aLimitExceeded{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: QuotaLimitExceeded, + } } -// IsQuotaLimitExceeded returns true if the given error represents a -// QuotaLimitExceeded error. +// Deprecated: IsQuotaLimitExceeded reports whether err is a QuoteLimitExceeded +// err. Use Is(err, QuotaLimitExceeded). func IsQuotaLimitExceeded(err error) bool { - err = Cause(err) - _, ok := err.(*quotaLimitExceeded) - return ok -} - -// notYetAvailable is the error returned when a resource is not yet available -// but it might be in the future. -type notYetAvailable struct { - Err + return Is(err, QuotaLimitExceeded) } -// IsNotYetAvailable reports err was created with NotYetAvailableF or -// NewNotYetAvailable. -func IsNotYetAvailable(err error) bool { - err = Cause(err) - _, ok := err.(*notYetAvailable) - return ok -} - -// NotYetAvailablef returns an error which satisfies IsNotYetAvailable. +// NotYetAvailablef returns an error which satisfies Is(err, NotYetAvailable) +// and the Locationer interface. func NotYetAvailablef(format string, args ...interface{}) error { - return ¬YetAvailable{wrap(nil, format, "", args...)} + return newLocationError( + makeWrappedConstError(constSuppressor(NotYetAvailable), format, args...), + 1, + ) } // NewNotYetAvailable returns an error which wraps err and satisfies -// IsNotYetAvailable. +// Is(err, NotYetAvailable) and the Locationer interface. func NewNotYetAvailable(err error, msg string) error { - return ¬YetAvailable{wrap(err, msg, "")} + return &errWithType{ + error: newLocationError(wrapErrorWithMsg(err, msg), 1), + errType: NotYetAvailable, + } +} + +// Deprecated: IsNotYetAvailable reports whether err is a NotYetAvailable err. +// Use Is(err, NotYetAvailable) +func IsNotYetAvailable(err error) bool { + return Is(err, NotYetAvailable) } diff --git a/errortypes_test.go b/errortypes_test.go index 0fde9336..36e7f1af 100644 --- a/errortypes_test.go +++ b/errortypes_test.go @@ -6,18 +6,17 @@ package errors_test import ( stderrors "errors" "fmt" - "reflect" - "runtime" "github.com/juju/errors" gc "gopkg.in/check.v1" ) -// errorInfo holds information about a single error type: a satisfier -// function, wrapping and variable arguments constructors and message +// errorInfo holds information about a single error type: its type +// and name, wrapping and variable arguments constructors and message // suffix. type errorInfo struct { - satisfier func(error) bool + errType errors.ConstError + errName string argsConstructor func(string, ...interface{}) error wrapConstructor func(error, string) error suffix string @@ -26,38 +25,32 @@ type errorInfo struct { // allErrors holds information for all defined errors. When adding new // errors, add them here as well to include them in tests. var allErrors = []*errorInfo{ - {errors.IsTimeout, errors.Timeoutf, errors.NewTimeout, " timeout"}, - {errors.IsNotFound, errors.NotFoundf, errors.NewNotFound, " not found"}, - {errors.IsUserNotFound, errors.UserNotFoundf, errors.NewUserNotFound, " user not found"}, - {errors.IsUnauthorized, errors.Unauthorizedf, errors.NewUnauthorized, ""}, - {errors.IsNotImplemented, errors.NotImplementedf, errors.NewNotImplemented, " not implemented"}, - {errors.IsAlreadyExists, errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"}, - {errors.IsNotSupported, errors.NotSupportedf, errors.NewNotSupported, " not supported"}, - {errors.IsNotValid, errors.NotValidf, errors.NewNotValid, " not valid"}, - {errors.IsNotProvisioned, errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"}, - {errors.IsNotAssigned, errors.NotAssignedf, errors.NewNotAssigned, " not assigned"}, - {errors.IsMethodNotAllowed, errors.MethodNotAllowedf, errors.NewMethodNotAllowed, ""}, - {errors.IsBadRequest, errors.BadRequestf, errors.NewBadRequest, ""}, - {errors.IsForbidden, errors.Forbiddenf, errors.NewForbidden, ""}, - {errors.IsQuotaLimitExceeded, errors.QuotaLimitExceededf, errors.NewQuotaLimitExceeded, ""}, - {errors.IsNotYetAvailable, errors.NotYetAvailablef, errors.NewNotYetAvailable, ""}, + {errors.Timeout, "Timeout", errors.Timeoutf, errors.NewTimeout, " timeout"}, + {errors.NotFound, "NotFound", errors.NotFoundf, errors.NewNotFound, " not found"}, + {errors.UserNotFound, "UserNotFound", errors.UserNotFoundf, errors.NewUserNotFound, " user not found"}, + {errors.Unauthorized, "Unauthorized", errors.Unauthorizedf, errors.NewUnauthorized, ""}, + {errors.NotImplemented, "NotImplemented", errors.NotImplementedf, errors.NewNotImplemented, " not implemented"}, + {errors.AlreadyExists, "AlreadyExists", errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"}, + {errors.NotSupported, "NotSupported", errors.NotSupportedf, errors.NewNotSupported, " not supported"}, + {errors.NotValid, "NotValid", errors.NotValidf, errors.NewNotValid, " not valid"}, + {errors.NotProvisioned, "NotProvisioned", errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"}, + {errors.NotAssigned, "NotAssigned", errors.NotAssignedf, errors.NewNotAssigned, " not assigned"}, + {errors.MethodNotAllowed, "MethodNotAllowed", errors.MethodNotAllowedf, errors.NewMethodNotAllowed, ""}, + {errors.BadRequest, "BadRequest", errors.BadRequestf, errors.NewBadRequest, ""}, + {errors.Forbidden, "Forbidden", errors.Forbiddenf, errors.NewForbidden, ""}, + {errors.QuotaLimitExceeded, "QuotaLimitExceeded", errors.QuotaLimitExceededf, errors.NewQuotaLimitExceeded, ""}, + {errors.NotYetAvailable, "NotYetAvailable", errors.NotYetAvailablef, errors.NewNotYetAvailable, ""}, } type errorTypeSuite struct{} var _ = gc.Suite(&errorTypeSuite{}) -func (t *errorInfo) satisfierName() string { - value := reflect.ValueOf(t.satisfier) - f := runtime.FuncForPC(value.Pointer()) - return f.Name() -} - func (t *errorInfo) equal(t0 *errorInfo) bool { if t0 == nil { return false } - return t.satisfierName() == t0.satisfierName() + return t == t0 } type errorTest struct { @@ -73,15 +66,15 @@ func deferredAnnotatef(err error, format string, args ...interface{}) error { func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) { if errInfo != nil { - msg := fmt.Sprintf("%#v must satisfy %v", err, errInfo.satisfierName()) - c.Check(err, Satisfies, errInfo.satisfier, gc.Commentf(msg)) + msg := fmt.Sprintf("Is(err, %s) should be TRUE when err := %#v", errInfo.errName, err) + c.Check(errors.Is(err, errInfo.errType), gc.Equals, true, gc.Commentf(msg)) } } func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) { if errInfo != nil { - msg := fmt.Sprintf("%#v must not satisfy %v", err, errInfo.satisfierName()) - c.Check(err, gc.Not(Satisfies), errInfo.satisfier, gc.Commentf(msg)) + msg := fmt.Sprintf("Is(err, %s) should be FALSE when err := %#v", errInfo.errName, err) + c.Check(errors.Is(err, errInfo.errType), gc.Equals, false, gc.Commentf(msg)) } } diff --git a/functions.go b/functions.go index 7edb08a3..9622ae20 100644 --- a/functions.go +++ b/functions.go @@ -6,6 +6,7 @@ package errors import ( stderrors "errors" "fmt" + "runtime" "strings" ) @@ -33,6 +34,18 @@ func Errorf(format string, args ...interface{}) error { return err } +// getLocation records the package path-qualified function name of the error at +// callDepth stack frames above the call. +func getLocation(callDepth int) (string, int) { + rpc := make([]uintptr, 1) + n := runtime.Callers(callDepth+2, rpc[:]) + if n < 1 { + return "", 0 + } + frame, _ := runtime.CallersFrames(rpc).Next() + return frame.Function, frame.Line +} + // 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. @@ -212,13 +225,9 @@ type wrapper interface { Underlying() error } -type locationer interface { - Location() (string, int) -} - var ( _ wrapper = (*Err)(nil) - _ locationer = (*Err)(nil) + _ Locationer = (*Err)(nil) _ causer = (*Err)(nil) ) @@ -236,7 +245,7 @@ func Details(err error) string { s = append(s, '[') for { s = append(s, '{') - if err, ok := err.(locationer); ok { + if err, ok := err.(Locationer); ok { file, line := err.Location() if file != "" { s = append(s, fmt.Sprintf("%s:%d", file, line)...) @@ -287,7 +296,7 @@ func errorStack(err error) []string { var lines []string for { var buff []byte - if err, ok := err.(locationer); ok { + if err, ok := err.(Locationer); ok { file, line := err.Location() // Strip off the leading GOPATH/src path elements. if file != "" { diff --git a/functions_test.go b/functions_test.go index 49bed692..efc29a62 100644 --- a/functions_test.go +++ b/functions_test.go @@ -173,7 +173,7 @@ func (*functionSuite) TestCause(c *gc.C) { err = errors.Annotate(err, "annotated") c.Assert(errors.Cause(err), gc.Equals, fmtErr) - err = errors.Maskf(err, "maksed") + err = errors.Maskf(err, "masked") c.Assert(errors.Cause(err), gc.Equals, err) // Look for a file that we know isn't there.