项目作者: stackus

项目描述 :
Builds on Go 1.13 errors by adding HTTP statuses and GRPC codes to them.
高级语言: Go
项目地址: git://github.com/stackus/errors.git
创建时间: 2021-05-22T05:55:37Z
项目社区:https://github.com/stackus/errors

开源协议:MIT License

下载



Go Report Card

errors

Builds on Go 1.13 errors by adding HTTP statuses and GRPC codes to them.

Installation

  1. go get -u github.com/stackus/errors

Prerequisites

Go 1.20 or later is required to use this package.

Embeddable codes

This library allows the use and helps facilitate the embedding of a type code, HTTP status, and GRPC code into errors
that can then be shared between services.

Type Codes

Type codes are strings that are returned by any error that implements errors.TypeCoder.

  1. type TypeCoder interface {
  2. error
  3. TypeCode() string
  4. }

HTTP Statuses

HTTP statuses are integer values that have defined in the net/http package and are returned by any error that
implements errors.HTTPCoder.

  1. type HTTPCoder interface {
  2. error
  3. HTTPCode() int
  4. }

GRPC Codes

GRPC codes are codes.Code are int64 values defined in the google.golang.org/grpc/codes package and are returned by
any error that implements errors.GRPCCoder.

  1. type GRPCCoder interface {
  2. error
  3. GRPCCode() codes.Code
  4. }

Packaged Error Types

The package also comes with many defined errors that are named in a way to reflect the GRPC code or HTTP status they
represent. The list of embeddable errors.Error types can be
found here.

Wrapping errors

The errors.Wrap(error, string) error function is used to wrap errors combining messages in most cases. However, when
the function is used with an error that has implemented errors.TypeCoder the message is not altered, and the error is
embedded instead.

  1. // Wrapping normal errors appends the error message
  2. err := errors.Wrap(fmt.Errorf("sql error"), "error message")
  3. fmt.Println(err) // Outputs: "error message: sql error"
  4. // Wrapping errors.TypeCoder errors embeds the type
  5. err := errors.Wrap(errors.ErrNotFound, "error message")
  6. fmt.Println(err) // Outputs: "error message"

Wrapping multiple times will add additional prefixes to the error message.

  1. // Wrapping multiple times
  2. err := errors.Wrap(errors.ErrNotFound, "error message")
  3. err = errors.Wrap(err, "prefix")
  4. err = errors.Wrap(err, "another")
  5. fmt.Println(err) // Outputs: "another: prefix: error message"

Wrapping using the errors.Err* errors

It is possible to use the package errors to wrap existing errors to add or override Type, HTTP code, or GRPC status codes.

  1. // Err will use the wrapped error .Error() output as the message
  2. err := errors.ErrBadRequest.Err(fmt.Errorf("some error"))
  3. // Msg and Msgf returns the Error with just the custom message applied
  4. err = errors.ErrBadRequest.Msgf("%d total reasons", 7)
  5. // Wrap and Wrapf will accept messages and simple wrap the error
  6. err = errors.ErrUnauthorized.Wrap(err, "some message")

Both errors can be checked for using the Is() and As() methods when you wrap errors with the package errors this way.

Getting type, HTTP status, or GRPC code

The Go 1.13 errors.As(error, interface{}) bool function from the standard errors package can be used to turn an
error into any of the three “Coder” interfaces documented above.

  1. err := errors.Wrap(errors.NotFound, "error message")
  2. var coder errors.TypeCoder
  3. if errors.As(err, &coder) {
  4. fmt.Println(coder.TypeCode()) // Outputs: "NOT_FOUND"
  5. }

The functions Is(), As(), and Unwrap() from the standard errors package have all been made available in this package as proxies for convenience.

The functions errors.TypeCode(error) string, errors.HTTPCode(error) int, and errors.GRPCCode(error) codes.Code can
be used to fetch specific code. They’re more convenient to use than the interfaces directly. The catch is they have
defined rules for the values they return.

The function errors.Join(errs ...error) error, made available in Go 1.20 has been added to this package as an additional convenience.

errors.TypeCode(error) string

If the error implements or has wrapped an error that implements errors.TypeCoder it will return the code from that
error. If no error is found to support the interface then the string "UNKNOWN" is returned. Nil errors result in a
blank string being returned.

  1. fmt.Println(errors.TypeCode(errors.ErrNotFound)) // Outputs: "NOT_FOUND"
  2. fmt.Println(errors.TypeCode(fmt.Errorf("an error"))) // Outputs: "UNKNOWN"
  3. fmt.Println(errors.TypeCode(nil)) // Outputs: ""

errors.HTTPCode(error) int

If the error implements or has wrapped an error that implements errors.HTTPCoder it will return the status from that
error. If no error is found to support the interface then http.StatusNotExtended is returned. Nil errors result
in http.StatusOK being returned.

  1. fmt.Println(errors.HTTPCode(errors.ErrNotFound)) // Outputs: 404
  2. fmt.Println(errors.HTTPCode(fmt.Errorf("an error"))) // Outputs: 510
  3. fmt.Println(errors.HTTPCode(nil)) // Outputs: 200

errors.GRPCCode(error) codes.Code

If the error implements or has wrapped an error that implements errors.GRPCCoder it will return the code from that
error. If no error is found to support the interface then codes.Unknown is returned. Nil errors result in codes.OK
being returned.

  1. fmt.Println(errors.GRPCCode(errors.ErrNotFound)) // Outputs: 5
  2. fmt.Println(errors.GRPCCode(fmt.Errorf("an error"))) // Outputs: 2
  3. fmt.Println(errors.GRPCCode(nil)) // Outputs: 0

Why Unknown? Why not default to internal errors?

Part of the reason you’d want to use a library that adds code to your errors is because you want to better identify the
problems in your application. By marking un-coded errors as “Unknown” errors they’ll stand out from any errors you’ve
marked as codes.Internal for example.

Transmitting errors with GRPC

The functions SendGRPCError(error) error and ReceiveGRPCError(error) error provide a way to convert
a status.Status and its error into an error that provides codes and vice versa. You can use these in your server and
client handlers directly, or they can be used with GRPC interceptors.

Server Interceptor Example:

  1. // Unary only example
  2. func serverErrorUnaryInterceptor() grpc.UnaryServerInterceptor {
  3. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  4. return resp, errors.SendGRPCError(err)
  5. }
  6. }
  7. server := grpc.NewServer(grpc.ChainUnaryInterceptor(serverErrorUnaryInterceptor()), ...others)

Client Interceptor Example:

  1. // Unary only example
  2. func clientErrorUnaryInterceptor() grpc.UnaryClientInterceptor {
  3. return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  4. return errors.ReceiveGRPCError(invoker(ctx, method, req, reply, cc, opts...))
  5. }
  6. }
  7. cc, err := grpc.Dial(uri, grpc.WithChainUnaryInterceptor(clientErrorUnaryInterceptor()), ...others)

Comparing received errors

Servers and clients may not always use a shared library when exchanging errors. In fact there isn’t any requirement that
the server and client both use this library to exchange errors.

When comparing received errors with errors.Is(error, error) bool the checks are a little more loose. A received error
is considered to be the same if ANY of the codes are a match. This differs from a strict equality check for the
server 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 that
sent the error.

Contributing

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.

License

MIT