- Notifications
You must be signed in to change notification settings - Fork3
Builds on Go 1.13 errors by adding HTTP statuses and GRPC codes to them.
License
stackus/errors
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Builds on Go 1.13 errors by adding HTTP statuses and GRPC codes to them.
go get -u github.com/stackus/errorsGo 1.20 or later is required to use this package.
This library allows the use and helps facilitate the embedding of a type code, HTTP status, and GRPC code into errorsthat can then be shared between services.
Type codes are strings that are returned by any error that implementserrors.TypeCoder.
type TypeCoder interface { error TypeCode() string}HTTP statuses are integer values that have defined in thenet/http package and are returned by any error thatimplementserrors.HTTPCoder.
type HTTPCoder interface { error HTTPCode() int}GRPC codes arecodes.Code are int64 values defined in thegoogle.golang.org/grpc/codes package and are returned byany error that implementserrors.GRPCCoder.
type GRPCCoder interface { error GRPCCode() codes.Code}The package also comes with many defined errors that are named in a way to reflect the GRPC code or HTTP status theyrepresent. The list of embeddableerrors.Error types can befoundhere.
Theerrors.Wrap(error, string) error function is used to wrap errors combining messages in most cases. However, whenthe function is used with an error that has implementederrors.TypeCoder the message is not altered, and the error isembedded instead.
// Wrapping normal errors appends the error messageerr:=errors.Wrap(fmt.Errorf("sql error"),"error message")fmt.Println(err)// Outputs: "error message: sql error"// Wrapping errors.TypeCoder errors embeds the typeerr:=errors.Wrap(errors.ErrNotFound,"error message")fmt.Println(err)// Outputs: "error message"
Wrapping multiple times will add additional prefixes to the error message.
// Wrapping multiple timeserr:=errors.Wrap(errors.ErrNotFound,"error message")err=errors.Wrap(err,"prefix")err=errors.Wrap(err,"another")fmt.Println(err)// Outputs: "another: prefix: error message"
It is possible to use the package errors to wrap existing errors to add or override Type, HTTP code, or GRPC status codes.
// Err will use the wrapped error .Error() output as the messageerr:=errors.ErrBadRequest.Err(fmt.Errorf("some error"))// Msg and Msgf returns the Error with just the custom message appliederr=errors.ErrBadRequest.Msgf("%d total reasons",7)// Wrap and Wrapf will accept messages and simple wrap the errorerr=errors.ErrUnauthorized.Wrap(err,"some message")
Both errors can be checked for using theIs() andAs() methods when you wrap errors with the package errors this way.
The Go 1.13errors.As(error, interface{}) bool function from the standarderrors package can be used to turn anerror into any of the three "Coder" interfaces documented above.
err := errors.Wrap(errors.NotFound, "error message")var coder errors.TypeCoderif errors.As(err, &coder) { fmt.Println(coder.TypeCode()) // Outputs: "NOT_FOUND"}The functions
Is(),As(), andUnwrap()from the standarderrorspackage have all been made available in this package as proxies for convenience.
The functionserrors.TypeCode(error) string,errors.HTTPCode(error) int, anderrors.GRPCCode(error) codes.Code canbe used to fetch specific code. They're more convenient to use than the interfaces directly. The catch is they havedefined rules for the values they return.
The functionerrors.Join(errs ...error) error, made available in Go 1.20 has been added to this package as an additional convenience.
If the error implements or has wrapped an error that implementserrors.TypeCoder it will return the code from thaterror. If no error is found to support the interface then the string"UNKNOWN" is returned. Nil errors result in ablank string being returned.
fmt.Println(errors.TypeCode(errors.ErrNotFound)) // Outputs: "NOT_FOUND"fmt.Println(errors.TypeCode(fmt.Errorf("an error"))) // Outputs: "UNKNOWN"fmt.Println(errors.TypeCode(nil)) // Outputs: ""If the error implements or has wrapped an error that implementserrors.HTTPCoder it will return the status from thaterror. If no error is found to support the interface thenhttp.StatusNotExtended is returned. Nil errors resultinhttp.StatusOK being returned.
fmt.Println(errors.HTTPCode(errors.ErrNotFound)) // Outputs: 404fmt.Println(errors.HTTPCode(fmt.Errorf("an error"))) // Outputs: 510fmt.Println(errors.HTTPCode(nil)) // Outputs: 200If the error implements or has wrapped an error that implementserrors.GRPCCoder it will return the code from thaterror. If no error is found to support the interface thencodes.Unknown is returned. Nil errors result incodes.OKbeing returned.
fmt.Println(errors.GRPCCode(errors.ErrNotFound)) // Outputs: 5fmt.Println(errors.GRPCCode(fmt.Errorf("an error"))) // Outputs: 2fmt.Println(errors.GRPCCode(nil)) // Outputs: 0Part of the reason you'd want to use a library that adds code to your errors is because you want to better identify theproblems in your application. By marking un-coded errors as "Unknown" errors they'll stand out from any errors you'vemarked ascodes.Internal for example.
The functionsSendGRPCError(error) error andReceiveGRPCError(error) error provide a way to convertastatus.Status and its error into an error that provides codes and vice versa. You can use these in your server andclient handlers directly, or they can be used with GRPC interceptors.
Server Interceptor Example:
// Unary only examplefunc serverErrorUnaryInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { return resp, errors.SendGRPCError(err)}}server := grpc.NewServer(grpc.ChainUnaryInterceptor(serverErrorUnaryInterceptor()), ...others)Client Interceptor Example:
// Unary only examplefunc clientErrorUnaryInterceptor() grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return errors.ReceiveGRPCError(invoker(ctx, method, req, reply, cc, opts...))}}cc, err := grpc.Dial(uri, grpc.WithChainUnaryInterceptor(clientErrorUnaryInterceptor()), ...others)Servers and clients may not always use a shared library when exchanging errors. In fact there isn't any requirement thatthe server and client both use this library to exchange errors.
When comparing received errors witherrors.Is(error, error) bool the checks are a little more loose. A received erroris considered to be the same ifANY of the codes are a match. This differs from a strict equality check for theserver before the error was sent.
The "Code" functions and the "Coder" interfaces continue to work the same on a client as they did on the server thatsent the error.
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
MIT
About
Builds on Go 1.13 errors by adding HTTP statuses and GRPC codes to them.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.