Documentation ¶
Overview ¶
Package terrors implements an error wrapping library.
Terrors are used to provide context to an error, offering a stack trace and user defined error parameters.
Terrors can be used to wrap any object that satisfies the error interface:
terr := terrors.Wrap(err, map[string]string{"context": "my_context"})
Terrors can be instantiated directly:
err := terrors.New("not_found", "object not found", map[string]string{ "context": "my_context" })
Terrors offers built-in functions for instantiating Errors with common codes:
err := terrors.NotFound("config_file", "config file not found", map[string]string{ "context": my_context })
Index ¶
- Constants
- Variables
- func Augment(err error, context string, params map[string]string) error
- func Is(err error, code ...string) bool
- func IsRetryable(err error) bool
- func Marshal(e *Error) *pe.Error
- func Matches(err error, match string) bool
- func PrefixMatches(err error, prefixParts ...string) bool
- func Propagate(err error) error
- func StackStringWithMaxSize(p *Error, sizeLimit int) string
- func Wrap(err error, params map[string]string) error
- func WrapWithCode(err error, params map[string]string, code string) error
- type Error
- func BadRequest(code, message string, params map[string]string) *Error
- func BadResponse(code, message string, params map[string]string) *Error
- func Forbidden(code, message string, params map[string]string) *Error
- func InternalService(code, message string, params map[string]string) *Error
- func New(code string, message string, params map[string]string) *Error
- func NewInternalWithCause(err error, message string, params map[string]string, subCode string) *Error
- func NonRetryableInternalService(code, message string, params map[string]string) *Error
- func NotFound(code, message string, params map[string]string) *Error
- func PreconditionFailed(code, message string, params map[string]string) *Error
- func RateLimited(code, message string, params map[string]string) *Error
- func Timeout(code, message string, params map[string]string) *Error
- func Unauthorized(code, message string, params map[string]string) *Error
- func Unmarshal(p *pe.Error) *Error
- func (p *Error) Error() string
- func (p *Error) ErrorMessage() string
- func (p *Error) LogMetadata() map[string]string
- func (p *Error) Matches(match string) bool
- func (p *Error) PrefixMatches(prefixParts ...string) bool
- func (p *Error) Retryable() bool
- func (p *Error) SetIsRetryable(value bool)
- func (p *Error) SetIsUnexpected(value bool)
- func (p *Error) StackString() string
- func (p *Error) StackTrace() []uintptr
- func (p *Error) Unexpected() bool
- func (p *Error) Unwrap() error
- func (p *Error) VerboseString() string
Examples ¶
Constants ¶
const ( ErrBadRequest = "bad_request" ErrBadResponse = "bad_response" ErrForbidden = "forbidden" ErrInternalService = "internal_service" ErrNotFound = "not_found" ErrPreconditionFailed = "precondition_failed" ErrTimeout = "timeout" ErrUnknown = "unknown" ErrRateLimited = "rate_limited" )
Generic error codes. Each of these has their own constructor for convenience. You can use any string as a code, just use the `New` method. Warning: any new generic error code must be added to GenericErrorCodes.
Variables ¶
var GenericErrorCodes = []string{ ErrBadRequest, ErrBadResponse, ErrForbidden, ErrInternalService, ErrNotFound, ErrPreconditionFailed, ErrTimeout, ErrUnauthorized, ErrUnknown, ErrRateLimited, }
GenericErrorCodes is a list of all well known generic error codes.
Functions ¶
func Augment ¶
Augment adds context to an existing error. If the error given is not already a terror, a new terror is created.
func Is ¶
Is checks whether an error is a given code. Similarly to `errors.Is`, this unwinds the error stack and checks each underlying error for the code. If any match, this returns true. Note that Is only behaves differently to PrefixMatches when errors in the stack have different codes. For example, this is the case when errors are initialized with NewInternalWithCause, but not with Augment. We prefer this over using a method receiver on the terrors Error, as the function signature requires an error to test against, and checking against terrors would requite creating a new terror with the specific code.
func IsRetryable ¶
IsRetryable returns true if the error is a terror and whether the error was caused by an action which can be retried.
func Matches ¶
Matches returns true if the error is a terror error and the string returned from error.Error() contains the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param` or even on the more descriptive message Deprecated: Please use `Is` instead. Note that `Is` will attempt to match each error in the stack using `PrefixMatches`, so if you were previously matching against a part of the string returned from error.Error() that is _not_ the prefix, then this will be a breaking change. In this case you should update the string to match the prefix. If this is not possible, you can match against the entire error string explicitly, for example:
strings.Contains(err.Error(), "context deadline exceeded")
But we consider this bad practice and is part of the motivation for deprecating Matches in the first place.
Example ¶
err := NotFound("handler_missing", "Handler not found", nil) fmt.Println(Matches(err, "not_found.handler_missing"))
Output: true
func PrefixMatches ¶
PrefixMatches returns true if the error is a terror and the string returned from error.Error() starts with the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param`. Each dotted part can be passed as a separate argument e.g. `terrors.PrefixMatches(terr, terrors.ErrBadRequest, "missing_param")` is the same as terrors.PrefixMatches(terr, "bad_request.missing_param")` Deprecated: Please use `Is` instead.
func Propagate ¶
Propagate an error without changing it. This is equivalent to `return err` if the error is already a terror. If it is not a terror, this function will create one, and set the given error as the cause. This is a drop-in replacement for `terrors.Wrap(err, nil)` which adds causal chain functionality.
func StackStringWithMaxSize ¶
func Wrap ¶
Wrap takes any error interface and wraps it into an Error. This is useful because an Error contains lots of useful goodies, like the stacktrace of the error. NOTE: If `err` is already an `Error`, it will add the params passed in to the params of the Error Deprecated: Use Augment instead.
func WrapWithCode ¶
WrapWithCode wraps an error with a custom error code. If `err` is already an `Error`, it will add the params passed in to the params of the error Deprecated: Use Augment instead. If you need to set the code of the error, then you should return a new error instead. For example
terrors.WrapWithCode(err, map[string]string{"foo": "bar"}, "bad_request.failed")
would become
terrors.BadRequest("failed", err.Error(), map[string]string{"foo": "bar"})
Example ¶
fn := "not/a/file" _, err := os.Open(fn) if err != nil { errParams := map[string]string{ "filename": fn, } err = WrapWithCode(err, errParams, ErrNotFound) terr := err.(*Error) fmt.Println(terr.Error())
Output: not_found: open not/a/file: no such file or directory
Types ¶
type Error ¶
type Error struct { Code string `json:"code"` Message string `json:"message"` Params map[string]string `json:"params"` StackFrames stack.Stack `json:"stack"` // Exported for serialization, but you should use Retryable to read the value. IsRetryable *bool `json:"is_retryable"` // Exported for serialization, but you should use Unexpected to read the value. IsUnexpected *bool `json:"is_unexpected"` // Incremented each time the error is marshalled so that we can tell (approximately) how many services the error // has propagated through. Higher level code can use this to influence decisions, for example it may only be // desirable to retry on an error that's only been marshalled once to avoid retries on top of retries... ad nauseam MarshalCount int `json:"marshal_count"` // When errors are marshalled certain information is lost (e.g. the 'cause'). This means if an error travels through // a number of services (and it's potentially augmented at each hop) that the core error message may be lost. The // history of an error is often a helpful debugging aid, so MessageChain is used to track this. MessageChain []string `json:"message_chain"` // contains filtered or unexported fields }
Error is terror's error. It implements Go's error interface.
func BadRequest ¶
BadRequest creates a new error to represent an error caused by the client sending an invalid request. This is non-retryable unless the request is modified.
func BadResponse ¶
BadResponse creates a new error representing a failure to response with a valid response Examples of this would be a handler returning an invalid message format
func Forbidden ¶
Forbidden creates a new error representing a resource that cannot be accessed with the current authorisation credentials. The user may need authorising, or if authorised, may not be permitted to perform this action
func InternalService ¶
InternalService creates a new error to represent an internal service error. Only use internal service error if we know very little about the error. Most internal service errors will come from `Wrap`ing a vanilla `error` interface. Errors returned by this function are considered to be retryable by default. Consider using NonRetryableInternalService if retries are not desirable.
func New ¶
New creates a new error for you. Use this if you want to pass along a custom error code. Otherwise use the handy shorthand factories below
func NewInternalWithCause ¶
func NewInternalWithCause(err error, message string, params map[string]string, subCode string) *Error
NewInternalWithCause creates a new Terror from an existing error. The new error will always have the code `ErrInternalService`. The original error is attached as the `cause`, and can be tested with the `Is` function. You probably want to use the `Augment` func instead; only use this if you need to set a subcode on an error.
func NonRetryableInternalService ¶
NonRetryableInternalService creates a new error to represent an internal service error. Only use internal service error if we know very little about the error. Most internal service errors will come from `Wrap`ing a vanilla `error` interface. Errors returned by this function are not considered to be retryable by default.
func NotFound ¶
NotFound creates a new error representing a resource that cannot be found. In some cases this is not an error, and would be better represented by a zero length slice of elements
func PreconditionFailed ¶
PreconditionFailed creates a new error indicating that one or more conditions given in the request evaluated to false when tested on the server.
func RateLimited ¶
RateLimited creates a new error indicating that the request has been rate-limited, and that the caller should back-off.
func Unauthorized ¶
Unauthorized creates a new error indicating that authentication is required, but has either failed or not been provided.
func (*Error) Error ¶
Error returns a string message of the error. It will contain the code and error message. If there is a causal chain, the message from each error in the chain will be added to the output.
func (*Error) ErrorMessage ¶
ErrorMessage returns a string message of the error. It will contain the error message, but not the code. If there is a causal chain, the message from each error in the chain will be added to the output.
func (*Error) LogMetadata ¶
LogMetadata implements the logMetadataProvider interface in the slog library which means that the error params will automatically be merged with the slog metadata. Additionally we put stack data in here for slog use.
func (*Error) Matches ¶
Matches returns whether the string returned from error.Error() contains the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param` or even on the more descriptive message Deprecated: Please use `Is` instead. See docs for `Matches` for breaking change risks.
func (*Error) PrefixMatches ¶
PrefixMatches returns whether the string returned from error.Error() starts with the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param`. Each dotted part can be passed as a separate argument e.g. `terr.PrefixMatches(terrors.ErrBadRequest, "missing_param")` is the same as `terr.PrefixMatches("bad_request.missing_param")` Deprecated: Please use `Is` instead.
func (*Error) Retryable ¶
Retryable determines whether the error was caused by an action which can be retried.
func (*Error) SetIsRetryable ¶
func (*Error) SetIsUnexpected ¶
SetIsUnexpected can be used to explicitly mark an error as unexpected or not. In practice the vast majority of code should not need to use this. An example use case might be when returning a validation error that must mean there is a coding mistake somewhere (e.g. default statement in a switch that is never expected to be taken). By marking the error as unexpected there is a greater chance that an alert will be sent.
func (*Error) StackString ¶
StackString formats the stacks from the terror chain as a string. If we encounter more than one terror in the chain with a stack frame, we'll print each one, separated by three hyphens on their own line.
func (*Error) StackTrace ¶
StackTrace returns a slice of program counters taken from the stack frames. This adapts the terrors package to allow stacks to be reported to Sentry correctly.
func (*Error) Unexpected ¶
Unexpected states whether an error is not expected to occur. In many cases this will be due to a bug, e.g. due to a defensive check failing. Note that if the IsUnexpected flag has not been set at all, this will still return false.
func (*Error) VerboseString ¶
VerboseString returns the error message, stack trace and params