Documentation
¶
Overview ¶
Package errors gives Go errors a type code, an HTTP status, and a gRPC code, so one error value can drive logs, HTTP responses, and gRPC statuses. It works alongside the standard library's errors package. Is, As, AsType, Unwrap, and Join are provided so it can replace that import.
Categories ¶
The Err* constants, such as ErrNotFound and ErrInvalidArgument, are built-in categories of type Error. Each has a type code and HTTP and gRPC mappings (see the constants' comments). A category can be returned directly, matched with Is, or used to classify another error:
return errors.ErrNotFound.Wrap(err, "find user")
Defining application errors ¶
NewKind creates a sentinel error with its own type code inside a category. Declare kinds as package-level variables:
var ErrUserNotFound = errors.NewKind("USER_NOT_FOUND", errors.ErrNotFound, "user not found")
An error created from a kind matches both the kind and its category with Is. A kind inherits its category's HTTP and gRPC codes; WithHTTPCode and WithGRPCCode override them, and WithPublicMessage sets the message shown to clients.
Classifying and adding context ¶
Error and *Kind have the same set of constructors:
- Msg and Msgf create a new error with the given message and no cause.
- WithCause classifies an existing error and keeps its message.
- Wrap and Wrapf classify an existing error and prefix its message.
Classify errors where they enter your application, for example when a database driver returns sql.ErrNoRows. After that, add context with Wrap, Wrapf, or fmt.Errorf with %w. Each keeps the classification and the original cause.
Reading codes ¶
TypeCode, HTTPCode, and GRPCCode return an error's codes. They follow these rules:
- The outermost classification in the chain wins, so wrapping a classified error with a different category reclassifies it.
- context.Canceled and context.DeadlineExceeded are classified as ErrCanceled and ErrDeadlineExceeded.
- A joined error takes the classification its children share. When they differ, or one is unclassified, the join is UNKNOWN.
- An unclassified error is UNKNOWN, HTTP 500, gRPC Unknown. Unknown errors are kept separate from Internal ones on purpose: they show which failures have not been classified yet.
- A nil error is OK, HTTP 200, gRPC OK, the codes named by ErrOK.
- A non-nil error never reports success. ErrOK returned or wrapped as an error is UNKNOWN, HTTP 500, gRPC Unknown.
Custom error types ¶
An error type of your own is classified when it implements TypeCoder, HTTPCoder, or GRPCCoder. Codes it does not provide use the UNKNOWN defaults.
Public messages ¶
An error's text often contains details that clients should not see. PublicMessage returns a client-safe message, and Public returns a code and message pair that can be serialized. To set the message for one error, use WrapPublicMessage.
HTTP ¶
Set the response status from HTTPCode and write Public as the body. See the httpHandler example.
gRPC ¶
On the server, SendGRPCError converts an error into a gRPC status. The status carries the public message, the classification, and any details from a status error in the chain. On the client, ReceiveGRPCError rebuilds a classified error with the sender's type code, category, HTTP code, and public message. To also restore an application Kind's identity, pass a Registry that contains it.
The github.com/stackus/errors/grpcerrs package provides server and client interceptors that apply these functions to every call.
gRPC status errors, such as those made with status.Error, are classified by their code wherever they appear in a chain.
Example ¶
This example follows an error from a repository up through a service. The repository classifies the driver error at the boundary, the service adds context, and the caller reads the codes and checks the error's identity.
package main
import (
"fmt"
"github.com/stackus/errors"
)
// ErrUserNotFound is an application sentinel error. It is a distinct error
// identity that also belongs to the built-in ErrNotFound category, so it maps
// to HTTP 404 and gRPC NotFound.
var ErrUserNotFound = errors.NewKind(
"USER_NOT_FOUND",
errors.ErrNotFound,
"user not found",
errors.WithPublicMessage("The user could not be found"),
)
// errNoRows stands in for a driver error such as sql.ErrNoRows.
var errNoRows = errors.New("sql: no rows in result set")
func main() {
findUser := func(id string) error {
// Classify the driver error where it enters the application.
return ErrUserNotFound.Wrap(errNoRows, "find user "+id)
}
getProfile := func(id string) error {
// Add context on the way up; the classification is preserved.
return errors.Wrap(findUser(id), "get profile")
}
err := getProfile("42")
fmt.Println(err)
fmt.Println(errors.TypeCode(err))
fmt.Println(errors.HTTPCode(err))
fmt.Println(errors.GRPCCode(err))
fmt.Println(errors.Is(err, ErrUserNotFound))
fmt.Println(errors.Is(err, errors.ErrNotFound))
fmt.Println(errors.Is(err, errNoRows))
fmt.Println(errors.PublicMessage(err))
}
Output: get profile: find user 42: sql: no rows in result set USER_NOT_FOUND 404 NotFound true true true The user could not be found
Example (HttpHandler) ¶
This example writes an error as an HTTP response. The status comes from HTTPCode, and the body is the client-safe code and message from Public.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"github.com/stackus/errors"
)
// ErrUserNotFound is an application sentinel error. It is a distinct error
// identity that also belongs to the built-in ErrNotFound category, so it maps
// to HTTP 404 and gRPC NotFound.
var ErrUserNotFound = errors.NewKind(
"USER_NOT_FOUND",
errors.ErrNotFound,
"user not found",
errors.WithPublicMessage("The user could not be found"),
)
// errNoRows stands in for a driver error such as sql.ErrNoRows.
var errNoRows = errors.New("sql: no rows in result set")
func main() {
writeError := func(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(errors.HTTPCode(err))
_ = json.NewEncoder(w).Encode(errors.Public(err))
}
handle := func(err error) {
rec := httptest.NewRecorder()
writeError(rec, err)
fmt.Print(rec.Code, " ", rec.Body.String())
}
handle(ErrUserNotFound.Wrap(errNoRows, "find user 42"))
handle(errors.ErrInternal.Wrap(errors.New("dial tcp 10.0.0.5:5432: connection refused"), "load user"))
handle(errors.ErrUnprocessableEntity.Msg("email: missing @"))
}
Output: 404 {"code":"USER_NOT_FOUND","message":"The user could not be found"} 500 {"code":"INTERNAL","message":"Internal Server Error"} 422 {"code":"UNPROCESSABLE_ENTITY","message":"Unprocessable Entity"}
Index ¶
- Variables
- func As(err error, target interface{}) bool
- func AsType[E error](err error) (E, bool)
- func GRPCCode(err error) codes.Code
- func HTTPCode(err error) int
- func Is(err, target error) bool
- func Join(errs ...error) error
- func New(message string) error
- func PublicMessage(err error) string
- func ReceiveGRPCError(err error, registries ...*Registry) error
- func SendGRPCError(err error) error
- func TypeCode(err error) string
- func Unwrap(err error) error
- func WithKind(err error, kind *Kind) error
- func Wrap(err error, msg string) error
- func WrapPublicMessage(err error, message string) error
- func Wrapf(err error, format string, args ...any) error
- type Error
- func (e Error) Error() string
- func (e Error) GRPCCode() codes.Code
- func (e Error) HTTPCode() int
- func (e Error) Msg(msg string) error
- func (e Error) Msgf(format string, args ...any) error
- func (e Error) TypeCode() string
- func (e Error) WithCause(cause error) error
- func (e Error) Wrap(cause error, msg string) error
- func (e Error) Wrapf(cause error, format string, args ...any) error
- type ErrorType
- func (*ErrorType) Descriptor() ([]byte, []int)deprecated
- func (x *ErrorType) GetCategory() string
- func (x *ErrorType) GetGRPCCode() int64
- func (x *ErrorType) GetHTTPCode() int64
- func (x *ErrorType) GetTypeCode() string
- func (*ErrorType) ProtoMessage()
- func (x *ErrorType) ProtoReflect() protoreflect.Message
- func (x *ErrorType) Reset()
- func (x *ErrorType) String() string
- type GRPCCoder
- type HTTPCoder
- type Kind
- func (k *Kind) Error() string
- func (k *Kind) GRPCCode() codes.Code
- func (k *Kind) HTTPCode() int
- func (k *Kind) Is(target error) bool
- func (k *Kind) Msg(msg string) error
- func (k *Kind) Msgf(format string, args ...any) error
- func (k *Kind) PublicError() string
- func (k *Kind) TypeCode() string
- func (k *Kind) WithCause(cause error) error
- func (k *Kind) Wrap(cause error, msg string) error
- func (k *Kind) Wrapf(cause error, format string, args ...any) error
- type KindOption
- type PublicError
- type Registry
- type TypeCoder
Examples ¶
- Package
- Package (HttpHandler)
- AsType
- Error.Msg
- Error.Msgf
- Error.WithCause
- Error.Wrap
- GRPCCode
- HTTPCode
- HTTPCoder
- Join
- Kind.Msgf
- Kind.Wrap
- NewKind
- NewKind (Options)
- NewRegistry
- Public
- PublicMessage
- ReceiveGRPCError
- SendGRPCError
- SendGRPCError (StatusError)
- TypeCode
- WithKind
- Wrap
- Wrap (Multiple)
- Wrap (OrdinaryError)
- WrapPublicMessage
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupported = stderrors.ErrUnsupported
ErrUnsupported is the standard library's errors.ErrUnsupported sentinel.
var File_errorspb_proto protoreflect.FileDescriptor
Functions ¶
func AsType ¶ added in v0.2.0
AsType returns the first error in err's chain assignable to E, following the standard library's errors.AsType behavior.
Example ¶
AsType finds the first error in the chain that implements an interface, such as TypeCoder.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := fmt.Errorf("checkout: %w", errors.ErrConflict.Msg("cart changed"))
if coder, ok := errors.AsType[errors.TypeCoder](err); ok {
fmt.Println(coder.TypeCode())
}
}
Output: CONFLICT
func GRPCCode ¶ added in v0.0.2
GRPCCode returns err's gRPC code, using the same rules as TypeCode. It returns codes.OK for nil, codes.Unknown for an unclassified error, and never codes.OK for a non-nil error.
Example ¶
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
fmt.Println(errors.GRPCCode(errors.ErrNotFound))
fmt.Println(errors.GRPCCode(errors.ErrUnauthorized.Msg("token expired")))
fmt.Println(errors.GRPCCode(errors.New("something failed")))
fmt.Println(errors.GRPCCode(nil))
}
Output: NotFound Unauthenticated Unknown OK
func HTTPCode ¶ added in v0.0.2
HTTPCode returns err's HTTP status, using the same rules as TypeCode. It returns 200 for nil, 500 for an unclassified error, and a status in 400–599 for any non-nil error.
Example ¶
package main
import (
"context"
"fmt"
"github.com/stackus/errors"
)
func main() {
fmt.Println(errors.HTTPCode(errors.ErrNotFound))
fmt.Println(errors.HTTPCode(errors.ErrBadRequest.Msg("missing name")))
fmt.Println(errors.HTTPCode(errors.New("something failed")))
fmt.Println(errors.HTTPCode(fmt.Errorf("query: %w", context.Canceled)))
fmt.Println(errors.HTTPCode(nil))
}
Output: 404 400 500 408 200
func Join ¶ added in v0.1.7
Join calls the standard library's errors.Join.
Example ¶
When a joined error contains different classifications, the join is UNKNOWN. Classify the join itself to choose the result.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
joined := errors.Join(
errors.ErrNotFound.Msg("user not found"),
errors.ErrForbidden.Msg("project is private"),
)
fmt.Println(errors.TypeCode(joined))
err := errors.ErrInvalidArgument.WithCause(joined)
fmt.Println(errors.TypeCode(err))
fmt.Println(errors.Is(err, errors.ErrForbidden))
}
Output: UNKNOWN INVALID_ARGUMENT true
func PublicMessage ¶ added in v0.2.0
PublicMessage returns a message for err that is safe to show clients. It never returns err.Error(). It uses the first of these that applies:
- A message set with WrapPublicMessage.
- The message set with WithPublicMessage on err's Kind.
- "Internal Server Error" for a 5xx HTTP code or an unclassified error.
- The HTTP status text for err's code, such as "Not Found".
It returns an empty string for nil.
Example ¶
PublicMessage never returns an error's internal text. Server errors become "Internal Server Error", client errors use their HTTP status text, and a kind can supply its own message.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrAccountLocked := errors.NewKind(
"ACCOUNT_LOCKED",
errors.ErrPermissionDenied,
"account locked after failed logins",
errors.WithPublicMessage("Your account is locked"),
)
fmt.Println(errors.PublicMessage(errors.ErrInternal.Wrap(errors.New("password authentication failed for user app"), "connect")))
fmt.Println(errors.PublicMessage(errors.ErrNotFound.Msg("row 42 missing from users")))
fmt.Println(errors.PublicMessage(ErrAccountLocked.Msg("5 failed logins from 10.0.0.9")))
fmt.Println(errors.PublicMessage(errors.New("unclassified")))
}
Output: Internal Server Error Not Found Your account is locked Internal Server Error
func ReceiveGRPCError ¶
ReceiveGRPCError rebuilds a classified error from a gRPC status error, such as one returned by a client call. Call it on every error a client call returns, usually from a client interceptor such as the ones in the grpcerrs subpackage.
The result is classified from the status code and the ErrorType detail that SendGRPCError attaches:
- A built-in category, such as ErrBadRequest, is restored as sent, with the same codes and a match with Is.
- A Kind found in one of the registries is restored as sent. The result matches the Kind and its category, and has the local Kind's codes. Registries are searched in order, and the first match is used.
- A Kind that isn't registered keeps its type code, category, and HTTP code. The result matches the category, but not any Kind.
PublicMessage of the result is the message the sender sent. The status code decides the result. When a detail disagrees with it, the detail is ignored. A status without a detail, for example from a server that does not use this package, is classified by its status code alone, and its message is not treated as public.
The result's Error text is the status error's text, and its GRPCStatus method returns the received status, details included.
It returns nil for nil and returns an error that isn't a gRPC status unchanged.
Example ¶
With a registry, the received error matches the same Kind the server sent. Without one, it keeps the type code, category, HTTP code, and public message, but it doesn't match the Kind.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrCartExpired := errors.NewKind("CART_EXPIRED", errors.ErrFailedPrecondition, "cart expired")
registry, _ := errors.NewRegistry(ErrCartExpired)
sent := errors.SendGRPCError(ErrCartExpired.Msg("cart 9 expired at 12:00"))
withRegistry := errors.ReceiveGRPCError(sent, registry)
fmt.Println(errors.Is(withRegistry, ErrCartExpired), errors.TypeCode(withRegistry), errors.HTTPCode(withRegistry))
withoutRegistry := errors.ReceiveGRPCError(sent)
fmt.Println(errors.Is(withoutRegistry, ErrCartExpired), errors.Is(withoutRegistry, errors.ErrFailedPrecondition), errors.TypeCode(withoutRegistry))
fmt.Println(errors.PublicMessage(withRegistry))
}
Output: true CART_EXPIRED 400 false true CART_EXPIRED Bad Request
func SendGRPCError ¶
SendGRPCError converts err into a gRPC status error for a server to return. The status code is GRPCCode(err), and the message is PublicMessage(err), so internal text never leaves the server. The type code, HTTP code, and category are attached as an ErrorType status detail, which ReceiveGRPCError reads on the client.
Call it on every error a handler returns, usually from a server interceptor such as the ones in the grpcerrs subpackage. It returns nil for a nil err.
A gRPC status error in err's chain, whether made with google.golang.org/grpc/status or received from another service, is classified by its code and detail. Unless an outer category or kind reclassifies it, its other status details are sent along too.
Example ¶
SendGRPCError turns an error into a gRPC status error. The status message is the public message, so internal details stay on the server.
package main
import (
"fmt"
"github.com/stackus/errors"
"google.golang.org/grpc/status"
)
func main() {
err := errors.ErrInternal.Wrap(errors.New("redis: connection pool exhausted"), "load session")
s, _ := status.FromError(errors.SendGRPCError(err))
fmt.Println(s.Code())
fmt.Println(s.Message())
}
Output: Internal Internal Server Error
Example (StatusError) ¶
A status error from google.golang.org/grpc/status is classified by its code, so it can be returned from a handler or inspected directly.
package main
import (
"fmt"
"github.com/stackus/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func main() {
err := fmt.Errorf("get order: %w", status.Error(codes.NotFound, "order 7 not found"))
fmt.Println(errors.TypeCode(err), errors.HTTPCode(err))
s, _ := status.FromError(errors.SendGRPCError(err))
fmt.Println(s.Code(), s.Message())
}
Output: NOT_FOUND 404 NotFound Not Found
func TypeCode ¶ added in v0.0.2
TypeCode returns err's type code. It uses the outermost classification in err's chain; a joined error with different classifications is "UNKNOWN". It returns "UNKNOWN" for an unclassified error and "OK" for nil. A non-nil error never returns "OK", even ErrOK itself. See the package documentation for the full rules.
Example ¶
TypeCode finds the classification anywhere in an error's chain. Errors without a classification are UNKNOWN, and context errors map to their matching categories.
package main
import (
"context"
"fmt"
"github.com/stackus/errors"
)
func main() {
fmt.Println(errors.TypeCode(errors.ErrNotFound))
fmt.Println(errors.TypeCode(fmt.Errorf("load: %w", errors.ErrForbidden.Msg("no access"))))
fmt.Println(errors.TypeCode(errors.New("something failed")))
fmt.Println(errors.TypeCode(context.DeadlineExceeded))
fmt.Println(errors.TypeCode(nil))
}
Output: NOT_FOUND FORBIDDEN UNKNOWN DEADLINE_EXCEEDED OK
func WithKind ¶ added in v0.2.0
WithKind classifies err with kind and keeps err's text and its place in the chain. It is the same as kind.WithCause(err), except that it returns nil for a nil err before checking kind, so it can wrap a call's result directly. It panics if kind is nil and err is not.
Example ¶
WithKind classifies an error with a kind and keeps its message. It returns nil for a nil error, so it can wrap a call's result directly.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrStorage := errors.NewKind("STORAGE_UNAVAILABLE", errors.ErrUnavailable, "storage unavailable")
save := func(fail bool) error {
if fail {
return errors.New("s3: request timed out")
}
return nil
}
fmt.Println(errors.WithKind(save(false), ErrStorage))
err := errors.WithKind(save(true), ErrStorage)
fmt.Println(err)
fmt.Println(errors.TypeCode(err))
}
Output: <nil> s3: request timed out STORAGE_UNAVAILABLE
func Wrap ¶
Wrap adds msg to err. The result keeps err's classification, and Is and As can still reach err.
If err is a category such as ErrNotFound, msg becomes the complete error text, so errors.Wrap(errors.ErrNotFound, "user 42 not found") reads "user 42 not found". Any other error, including a Kind, reads "msg: err". Wrap returns err unchanged when msg is empty, and nil when err is nil.
To classify or reclassify err, use a category's or a kind's Wrap method instead.
Example ¶
Wrapping a built-in category directly uses the message as the complete error text. The result still matches the category.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.Wrap(errors.ErrNotFound, "user 42 not found")
fmt.Println(err)
fmt.Println(errors.Is(err, errors.ErrNotFound))
}
Output: user 42 not found true
Example (Multiple) ¶
Each additional Wrap adds another prefix.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.Wrap(errors.ErrNotFound, "original message")
err = errors.Wrap(err, "prefixed message")
fmt.Println(err)
}
Output: prefixed message: original message
Example (OrdinaryError) ¶
Wrapping any other error produces "msg: cause" and keeps the cause and its classification reachable.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.Wrap(errors.ErrForbidden.Msg("project is archived"), "update project")
fmt.Println(err)
fmt.Println(errors.TypeCode(err))
}
Output: update project: project is archived FORBIDDEN
func WrapPublicMessage ¶ added in v0.2.0
WrapPublicMessage sets the message that PublicMessage returns for err, for cases where one error needs a message its kind or category doesn't provide. err's text, codes, and identity don't change. If err is later wrapped by a category or kind, that outer classification's message is used instead. WrapPublicMessage returns nil for a nil err and panics when message is empty for a non-nil err.
Example ¶
WrapPublicMessage sets a client-facing message for a single error. The error text and identity don't change.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.ErrInvalidArgument.Msg("sku ABC-123 failed checksum")
err = errors.WrapPublicMessage(err, "The product code is not valid")
fmt.Println(err)
fmt.Println(errors.PublicMessage(err))
fmt.Println(errors.Is(err, errors.ErrInvalidArgument))
}
Output: sku ABC-123 failed checksum The product code is not valid true
Types ¶
type Error ¶
type Error string
Error is a built-in error category. Its string value is its type code.
Use the Err* constants in three ways:
- Return one directly: return errors.ErrNotFound
- Match against one: errors.Is(err, errors.ErrNotFound)
- Classify an error: errors.ErrNotFound.Wrap(err, "find user")
Errors built from a category match it with Is, including a Kind created in that category. Values that are not registered categories report HTTP 500 and gRPC Unknown.
const ( ErrOK Error = "OK" // HTTP: 200 GRPC: codes.OK ErrCanceled Error = "CANCELED" // HTTP: 408 GRPC: codes.Canceled ErrUnknown Error = "UNKNOWN" // HTTP: 500 GRPC: codes.Unknown ErrInvalidArgument Error = "INVALID_ARGUMENT" // HTTP: 400 GRPC: codes.InvalidArgument ErrDeadlineExceeded Error = "DEADLINE_EXCEEDED" // HTTP: 504 GRPC: codes.DeadlineExceeded ErrNotFound Error = "NOT_FOUND" // HTTP: 404 GRPC: codes.NotFound ErrAlreadyExists Error = "ALREADY_EXISTS" // HTTP: 409 GRPC: codes.AlreadyExists ErrPermissionDenied Error = "PERMISSION_DENIED" // HTTP: 403 GRPC: codes.PermissionDenied ErrResourceExhausted Error = "RESOURCE_EXHAUSTED" // HTTP: 429 GRPC: codes.ResourceExhausted ErrFailedPrecondition Error = "FAILED_PRECONDITION" // HTTP: 400 GRPC: codes.FailedPrecondition ErrAborted Error = "ABORTED" // HTTP: 409 GRPC: codes.Aborted ErrOutOfRange Error = "OUT_OF_RANGE" // HTTP: 422 GRPC: codes.OutOfRange ErrUnimplemented Error = "UNIMPLEMENTED" // HTTP: 501 GRPC: codes.Unimplemented ErrInternal Error = "INTERNAL" // HTTP: 500 GRPC: codes.Internal ErrDataLoss Error = "DATA_LOSS" // HTTP: 500 GRPC: codes.DataLoss ErrUnauthenticated Error = "UNAUTHENTICATED" // HTTP: 401 GRPC: codes.Unauthenticated )
Built-in categories named for gRPC codes, one per code. Choose these when gRPC is your main transport or when the gRPC meaning is the more precise one. Each constant is a sentinel for Is and can classify other errors with methods such as Error.Wrap.
ErrOK is not a registered category. It names the codes of a nil error: OK, HTTP 200, and codes.OK. It is not an error to return; a non-nil error never reports success, so ErrOK returned or wrapped as an error is classified as UNKNOWN. It cannot be used as a Kind's category or type code.
const ( ErrBadRequest Error = "BAD_REQUEST" // HTTP: 400 GRPC: codes.InvalidArgument ErrForbidden Error = "FORBIDDEN" // HTTP: 403 GRPC: codes.PermissionDenied ErrMethodNotAllowed Error = "METHOD_NOT_ALLOWED" // HTTP: 405 GRPC: codes.Unimplemented ErrRequestTimeout Error = "REQUEST_TIMEOUT" // HTTP: 408 GRPC: codes.DeadlineExceeded ErrConflict Error = "CONFLICT" // HTTP: 409 GRPC: codes.Aborted ErrGone Error = "GONE" // HTTP: 410 GRPC: codes.NotFound ErrUnsupportedMediaType Error = "UNSUPPORTED_MEDIA_TYPE" // HTTP: 415 GRPC: codes.InvalidArgument ErrImATeapot Error = "IM_A_TEAPOT" // HTTP: 418 GRPC: codes.Unknown ErrUnprocessableEntity Error = "UNPROCESSABLE_ENTITY" // HTTP: 422 GRPC: codes.InvalidArgument ErrTooManyRequests Error = "TOO_MANY_REQUESTS" // HTTP: 429 GRPC: codes.ResourceExhausted ErrInternalServerError Error = "INTERNAL_SERVER_ERROR" // HTTP: 500 GRPC: codes.Internal ErrNotImplemented Error = "NOT_IMPLEMENTED" // HTTP: 501 GRPC: codes.Unimplemented ErrBadGateway Error = "BAD_GATEWAY" // HTTP: 502 GRPC: codes.Unavailable ErrGatewayTimeout Error = "GATEWAY_TIMEOUT" // HTTP: 504 GRPC: codes.DeadlineExceeded )
Built-in categories named for common HTTP client and server statuses. Choose these when HTTP is your main transport. Each one maps to the closest gRPC code. They are separate categories from the gRPC-named ones, so ErrBadRequest does not match ErrInvalidArgument.
func (Error) GRPCCode ¶
GRPCCode returns the category's gRPC code, codes.OK for ErrOK, or Unknown if it is unregistered.
func (Error) HTTPCode ¶
HTTPCode returns the category's HTTP status, 200 for ErrOK, or 500 if it is unregistered.
func (Error) Msg ¶ added in v0.1.5
Msg returns a classified error with msg as its full error text and no cause.
Example ¶
Msg creates a classified error from scratch.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.ErrInvalidArgument.Msg("page size must be positive")
fmt.Println(err)
fmt.Println(errors.GRPCCode(err))
}
Output: page size must be positive InvalidArgument
func (Error) Msgf ¶ added in v0.1.5
Msgf formats a message and returns the same classified result as Error.Msg.
Example ¶
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.ErrTooManyRequests.Msgf("limit of %d requests per minute exceeded", 60)
fmt.Println(err)
fmt.Println(errors.HTTPCode(err))
}
Output: limit of 60 requests per minute exceeded 429
func (Error) WithCause ¶ added in v0.2.0
WithCause classifies cause and uses cause.Error() as the error text. It returns nil when cause is nil.
Example ¶
WithCause classifies an error without changing its text.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.ErrUnavailable.WithCause(errors.New("dial tcp: connection refused"))
fmt.Println(err)
fmt.Println(errors.TypeCode(err))
}
Output: dial tcp: connection refused UNAVAILABLE
func (Error) Wrap ¶ added in v0.1.4
Wrap classifies cause and prefixes its error text with msg. An empty msg leaves the cause's text unchanged. It returns nil when cause is nil.
Example ¶
Use a category's Wrap to classify an error that came from somewhere else, such as a database driver or another library.
package main
import (
"fmt"
"github.com/stackus/errors"
)
// errNoRows stands in for a driver error such as sql.ErrNoRows.
var errNoRows = errors.New("sql: no rows in result set")
func main() {
err := errors.ErrNotFound.Wrap(errNoRows, "find order 7")
fmt.Println(err)
fmt.Println(errors.HTTPCode(err))
fmt.Println(errors.Is(err, errors.ErrNotFound))
fmt.Println(errors.Is(err, errNoRows))
}
Output: find order 7: sql: no rows in result set 404 true true
type ErrorType ¶ added in v0.0.2
type ErrorType struct {
// TypeCode identifies a built-in category or application kind.
TypeCode string `protobuf:"bytes,1,opt,name=TypeCode,proto3" json:"TypeCode,omitempty"`
// HTTPCode is the HTTP status associated with TypeCode.
HTTPCode int64 `protobuf:"varint,2,opt,name=HTTPCode,proto3" json:"HTTPCode,omitempty"`
// GRPCCode is the gRPC code associated with TypeCode.
GRPCCode int64 `protobuf:"varint,3,opt,name=GRPCCode,proto3" json:"GRPCCode,omitempty"`
// Category is the built-in category associated with the classification.
Category string `protobuf:"bytes,4,opt,name=Category,proto3" json:"Category,omitempty"`
// contains filtered or unexported fields
}
ErrorType carries classification metadata in a gRPC status detail. Receivers check its gRPC code against the actual status and validate known types against local definitions.
func (*ErrorType) Descriptor
deprecated
added in
v0.0.2
func (*ErrorType) GetCategory ¶ added in v0.2.0
func (*ErrorType) GetGRPCCode ¶ added in v0.0.2
func (*ErrorType) GetHTTPCode ¶ added in v0.0.2
func (*ErrorType) GetTypeCode ¶ added in v0.0.2
func (*ErrorType) ProtoMessage ¶ added in v0.0.2
func (*ErrorType) ProtoMessage()
func (*ErrorType) ProtoReflect ¶ added in v0.0.2
func (x *ErrorType) ProtoReflect() protoreflect.Message
type HTTPCoder ¶
HTTPCoder is implemented by errors that report an HTTP status code. See TypeCoder.
Example ¶
Your own error types are classified by implementing the coder interfaces. Codes a type does not provide use the defaults; here the gRPC code is Unknown because ValidationError does not implement GRPCCoder.
package main
import (
"fmt"
"net/http"
"github.com/stackus/errors"
)
// ValidationError is an application error type. It implements TypeCoder and
// HTTPCoder, so the package can classify it without wrapping.
type ValidationError struct {
Field string
}
func (e ValidationError) Error() string { return "invalid field: " + e.Field }
func (e ValidationError) TypeCode() string { return "VALIDATION_FAILED" }
func (e ValidationError) HTTPCode() int { return http.StatusUnprocessableEntity }
func main() {
err := fmt.Errorf("create account: %w", ValidationError{Field: "email"})
fmt.Println(errors.TypeCode(err))
fmt.Println(errors.HTTPCode(err))
fmt.Println(errors.GRPCCode(err))
fmt.Println(errors.PublicMessage(err))
}
Output: VALIDATION_FAILED 422 Unknown Unprocessable Entity
type Kind ¶ added in v0.2.0
type Kind struct {
// contains filtered or unexported fields
}
Kind is an application-specific sentinel error inside a built-in Error category. Create kinds with NewKind and declare them as package-level variables:
var ErrUserNotFound = errors.NewKind("USER_NOT_FOUND", errors.ErrNotFound, "user not found")
Kinds are compared by pointer, so two kinds with the same type code are still different errors. Errors created from a kind (with Msg, Wrap, WithCause, and so on) match both the kind and its category with Is. Its type code, HTTP code, gRPC code, and public message are set when the kind is created and cannot change.
func NewKind ¶ added in v0.2.0
func NewKind(typeCode string, category Error, message string, options ...KindOption) *Kind
NewKind creates an application-specific error with a distinct type code. The type code must start with an uppercase letter and contain only uppercase letters, digits, underscores, or periods. Its category supplies default HTTP and gRPC codes; options can override them. message is the error text of the kind itself and of errors made with its WithCause method.
NewKind panics for an invalid type code or one that matches a built-in category or ErrOK, an unregistered category (including ErrOK), an empty message, an HTTP code outside 400–599, or a gRPC code of OK. Kinds are normally declared as package-level variables, so these mistakes surface when the program starts.
Example ¶
Declare kinds as package-level variables and use them like any other sentinel error. Each kind is its own identity, and it also matches the category it belongs to.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
var (
ErrOrderNotFound = errors.NewKind("ORDER_NOT_FOUND", errors.ErrNotFound, "order not found")
ErrInvoiceNotFound = errors.NewKind("INVOICE_NOT_FOUND", errors.ErrNotFound, "invoice not found")
)
err := ErrOrderNotFound.Msgf("order %d not found", 7)
fmt.Println(errors.Is(err, ErrOrderNotFound))
fmt.Println(errors.Is(err, ErrInvoiceNotFound))
fmt.Println(errors.Is(err, errors.ErrNotFound))
fmt.Println(errors.TypeCode(err), errors.HTTPCode(err), errors.GRPCCode(err))
}
Output: true false true ORDER_NOT_FOUND 404 NotFound
Example (Options) ¶
Options override the codes a kind inherits from its category and set the message that clients see.
package main
import (
"fmt"
"net/http"
"github.com/stackus/errors"
"google.golang.org/grpc/codes"
)
func main() {
ErrPaymentRequired := errors.NewKind(
"PAYMENT_REQUIRED",
errors.ErrFailedPrecondition,
"account has no active subscription",
errors.WithHTTPCode(http.StatusPaymentRequired),
errors.WithPublicMessage("A subscription is required"),
)
ErrQuotaExceeded := errors.NewKind(
"QUOTA_EXCEEDED",
errors.ErrForbidden,
"storage quota exceeded",
errors.WithGRPCCode(codes.ResourceExhausted),
)
fmt.Println(errors.HTTPCode(ErrPaymentRequired), errors.GRPCCode(ErrPaymentRequired))
fmt.Println(errors.PublicMessage(ErrPaymentRequired))
fmt.Println(errors.HTTPCode(ErrQuotaExceeded), errors.GRPCCode(ErrQuotaExceeded))
fmt.Println(errors.PublicMessage(ErrQuotaExceeded))
}
Output: 402 FailedPrecondition A subscription is required 403 ResourceExhausted Forbidden
func (*Kind) Msg ¶ added in v0.2.0
Msg returns this Kind with msg as its full error text and no cause.
func (*Kind) Msgf ¶ added in v0.2.0
Msgf formats a message and calls Kind.Msg.
Example ¶
Msgf creates an error of the kind with a detailed message and no cause.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrInsufficientFunds := errors.NewKind("INSUFFICIENT_FUNDS", errors.ErrFailedPrecondition, "insufficient funds")
err := ErrInsufficientFunds.Msgf("balance %d is less than %d", 30, 45)
fmt.Println(err)
fmt.Println(errors.TypeCode(err))
}
Output: balance 30 is less than 45 INSUFFICIENT_FUNDS
func (*Kind) PublicError ¶ added in v0.2.0
PublicError returns the message set with WithPublicMessage, or an empty string if none was set. Use PublicMessage to get a client message for any error.
func (*Kind) WithCause ¶ added in v0.2.0
WithCause wraps cause with this Kind and uses cause.Error() as the error text. It returns nil when cause is nil.
func (*Kind) Wrap ¶ added in v0.2.0
Wrap wraps cause with this Kind and prefixes its error text with msg. It returns nil when cause is nil.
Example ¶
Wrap classifies a cause with the kind and adds context to its message.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrEmailTaken := errors.NewKind("EMAIL_TAKEN", errors.ErrAlreadyExists, "email already registered")
cause := errors.New(`pq: duplicate key value violates unique constraint "users_email_key"`)
err := ErrEmailTaken.Wrap(cause, "register user")
fmt.Println(err)
fmt.Println(errors.Is(err, ErrEmailTaken))
fmt.Println(errors.HTTPCode(err))
}
Output: register user: pq: duplicate key value violates unique constraint "users_email_key" true 409
type KindOption ¶ added in v0.2.0
type KindOption func(*Kind)
KindOption configures a Kind during NewKind.
func WithGRPCCode ¶ added in v0.2.0
func WithGRPCCode(grpcCode codes.Code) KindOption
WithGRPCCode overrides the category's gRPC code for a new Kind. NewKind panics if the resulting code is OK.
func WithHTTPCode ¶ added in v0.2.0
func WithHTTPCode(httpCode int) KindOption
WithHTTPCode overrides the category's HTTP status for a new Kind. NewKind panics if the resulting status is outside 400–599.
func WithPublicMessage ¶ added in v0.2.0
func WithPublicMessage(publicMessage string) KindOption
WithPublicMessage sets the message that PublicMessage and SendGRPCError show clients for this Kind. Without it, clients see the HTTP status text for 4xx codes and "Internal Server Error" for 5xx codes. It panics if publicMessage is empty.
type PublicError ¶ added in v0.2.0
type PublicError struct {
Code string `json:"code" xml:"code" yaml:"code" msgpack:"code"`
Message string `json:"message" xml:"message" yaml:"message" msgpack:"message"`
}
PublicError is the type code and client message for an error, as returned by Public. It serializes without the error's internal text, for example as JSON:
{"code":"USER_NOT_FOUND","message":"The user could not be found"}
func Public ¶ added in v0.2.0
func Public(err error) PublicError
Public returns err's type code and client-facing message. For nil, the code is "OK" and the message is empty.
Example ¶
Public returns a value that can be serialized directly in a response.
package main
import (
"encoding/json"
"fmt"
"github.com/stackus/errors"
)
func main() {
err := errors.ErrConflict.Msg("version 3 does not match 4")
b, _ := json.Marshal(errors.Public(err))
fmt.Println(string(b))
}
Output: {"code":"CONFLICT","message":"Conflict"}
type Registry ¶ added in v0.2.0
type Registry struct {
// contains filtered or unexported fields
}
Registry maps type codes to the Kind values a gRPC client expects to receive. ReceiveGRPCError uses it so that an error sent as a Kind matches the client's copy of that Kind with Is.
A client that shares error definitions with its servers, for example through a common package, registers those kinds once at startup. A received kind is matched by type code, gRPC code, and category. The local Kind's definition supplies the HTTP code.
A registry is not needed to match built-in categories, or to keep an unregistered kind's type code, category, and HTTP code. A Registry is safe for concurrent use.
func NewRegistry ¶ added in v0.2.0
NewRegistry returns a registry that contains kinds. It returns an error for a nil Kind or when two kinds share a type code.
Example ¶
Register the kinds a client expects to receive, typically once at startup.
package main
import (
"fmt"
"github.com/stackus/errors"
)
func main() {
ErrOrderNotFound := errors.NewKind("ORDER_NOT_FOUND", errors.ErrNotFound, "order not found")
ErrOrderShipped := errors.NewKind("ORDER_SHIPPED", errors.ErrFailedPrecondition, "order already shipped")
registry, err := errors.NewRegistry(ErrOrderNotFound, ErrOrderShipped)
if err != nil {
panic(err)
}
kind, ok := registry.Lookup("ORDER_SHIPPED")
fmt.Println(ok, kind == ErrOrderShipped)
}
Output: true true
type TypeCoder ¶ added in v0.0.2
TypeCoder is implemented by errors that report a type code.
Every error in this package implements TypeCoder, HTTPCoder, and GRPCCoder. Your own error types can implement any of them to be classified without wrapping. Codes a type does not provide use the UNKNOWN defaults. Because a non-nil error never reports success, a type code of "OK" is treated as "UNKNOWN", an HTTP code outside 400–599 as 500, and a gRPC code of OK as Unknown.