errors

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 19, 2022 License: MIT Imports: 9 Imported by: 37

README

Go Report Card

errors

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

Installation

go get -u github.com/stackus/errors

Prerequisites

Go 1.13

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.

type TypeCoder interface {
    TypeCode() string
}
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.

type HTTPCoder interface {
    HTTPCode() int
}
GRCP 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.

type GRPCCoder interface {
    GRPCCode() codes.Code
}
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.

// Wrapping normal errors appends the error message
err := errors.Wrap(fmt.Errorf("sql error"), "error message")
fmt.Println(err) // Outputs: "error message: sql error"

// Wrapping errors.TypeCoder errors embeds the type
err := 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 times
err := errors.Wrap(errors.ErrNotFound, "error message")
err = errors.Wrap(err, "prefix")
err = errors.Wrap(err, "another")
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.

// Err will use the wrapped error .Error() output as the message
err := errors.ErrBadRequest.Err(fmt.Errorf("some error"))
// Msg and Msgf returns the Error with just the custom message applied
err = errors.ErrBadRequest.Msgf("%d total reasons", 7)
// Wrap and Wrapf will accept messages and simple wrap the error
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.

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

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.

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.

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: ""
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.

fmt.Println(errors.HTTPCode(errors.ErrNotFound)) // Outputs: 404
fmt.Println(errors.HTTPCode(fmt.Errorf("an error"))) // Outputs: 510
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.

fmt.Println(errors.GRPCCode(errors.ErrNotFound)) // Outputs: 5
fmt.Println(errors.GRPCCode(fmt.Errorf("an error"))) // Outputs: 2
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:

// Unary only example
func 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 example
func 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)
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

Documentation

Overview

Package errors builds on Go 1.13 errors adding HTTP and GRPC code to your errors.

Wrap() and Wrapf()

When the wrap functions are used with one of the defined Err* constants you get back an error that you're able to pass the error through a GRPC server and client or use to build HTTP error messages and set the HTTP status.

Wrapping any error other than an Error will return an error with the message formatted as "<message>: <error>".

Wrapping an Error will return an error with an unaltered error message.

Transmitting errors over GRPC

The errors produced with wrap, that have also been wrapped first with an Err* can be send with SendGRPCError() and received with ReceiveGRPCError().

You may want to create and use GRPC server and client interceptors to avoid having to call the Send/Receive methods in every handler.

The Err* constants are errors and can be used directly is desired.

Index

Examples

Constants

This section is empty.

Variables

View Source
var File_errorspb_proto protoreflect.FileDescriptor

Functions

func As

func As(err error, target interface{}) bool

As implements the standard errors.As for convenience

func GRPCCode added in v0.0.2

func GRPCCode(err error) codes.Code

GRPCCode returns the GRPC code for the given error or codes.OK when nil or codes.Unknown otherwise

func HTTPCode added in v0.0.2

func HTTPCode(err error) int

HTTPCode returns the HTTP status for the given error or http.StatusOK when nil or http.StatusNotExtended otherwise

func Is

func Is(err, target error) bool

Is implements the standard errors.Is for convenience

func ReceiveGRPCError

func ReceiveGRPCError(err error) error

ReceiveGRPCError recreates the error with the coded Error reapplied

Non-nil results can be used as both Error and *status.Status. Methods errors.Is()/errors.As(), and status.Convert()/status.FromError() will continue to work.

Use in the clients when receiving errors. If err is nil then ReceiveGRPCError returns nil.

func SendGRPCError

func SendGRPCError(err error) error

SendGRPCError ensures that the error being used is sent with the correct code applied

Use in the server when sending errors. If err is nil then SendGRPCError returns nil.

func TypeCode added in v0.0.2

func TypeCode(err error) string

TypeCode returns the embedded type for the given error or blank when nil or UNKNOWN otherwise

func Unwrap

func Unwrap(err error) error

Unwrap implements the standard errors.Wrap for convenience

func Wrap

func Wrap(err error, msg string) error

Wrap returns an error with msg wrapped with the supplied error If err is nil then Wrap returns nil

Example
err := Wrap(ErrNotFound, "message")
fmt.Println(err)
Output:

message
Example (Multiple)
err := Wrap(ErrNotFound, "original message")
err = Wrap(err, "prefixed message")
fmt.Println(err)
Output:

prefixed message: original message

func Wrapf

func Wrapf(err error, format string, args ...interface{}) error

Wrapf returns an error with a formatted msg wrapped with the supplied error If err is nil then Wrapf returns nil

Types

type Error

type Error string

Error base error

const (
	ErrOK                 Error = "OK"                  // HTTP: 200 GRPC: codes.OK
	ErrCanceled           Error = "CANCELED"            // HTTP: 408 GRPC: codes.Canceled
	ErrUnknown            Error = "UNKNOWN"             // HTTP: 510 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
	ErrUnavailable        Error = "UNAVAILABLE"         // HTTP: 503 GRPC: codes.Unavailable
	ErrDataLoss           Error = "DATA_LOSS"           // HTTP: 500 GRPC: codes.DataLoss
	ErrUnauthenticated    Error = "UNAUTHENTICATED"     // HTTP: 401 GRPC: codes.Unauthenticated
)

Errors named in line with GRPC codes and some that overlap with HTTP statuses

const (
	ErrBadRequest                 Error = "BAD_REQUEST"                   // HTTP: 400 GRPC: codes.InvalidArgument
	ErrUnauthorized               Error = "UNAUTHORIZED"                  // HTTP: 401 GRPC: codes.Unauthenticated
	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.AlreadyExists
	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
	ErrUnavailableForLegalReasons Error = "UNAVAILABLE_FOR_LEGAL_REASONS" // HTTP: 451 GRPC: codes.Unavailable
	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.Aborted
	ErrServiceUnavailable         Error = "SERVICE_UNAVAILABLE"           // HTTP: 503 GRPC: codes.Unavailable
	ErrGatewayTimeout             Error = "GATEWAY_TIMEOUT"               // HTTP: 504 GRPC: codes.DeadlineExceeded
)

Errors named in line with HTTP statuses

func (Error) Err added in v0.1.4

func (e Error) Err(err error) error

Err overrides or adds Type,HTTP,GRPC information for the passed in error while leaving Is() and As() functionality unchanged

func (Error) Error

func (e Error) Error() string

Error implements error

func (Error) GRPCCode

func (e Error) GRPCCode() codes.Code

func (Error) GRPCStatus added in v0.1.0

func (e Error) GRPCStatus() *status.Status

func (Error) HTTPCode

func (e Error) HTTPCode() int

func (Error) Msg added in v0.1.5

func (e Error) Msg(msg string) error

Msg sets a custom message for the Error

func (Error) Msgf added in v0.1.5

func (e Error) Msgf(format string, args ...interface{}) error

Msgf sets a custom message for formatting for the Error

func (Error) TypeCode added in v0.0.2

func (e Error) TypeCode() string

func (Error) Wrap added in v0.1.4

func (e Error) Wrap(err error, msg string) error

Wrap an error with message while overriding or adding Type,HTTP,GRPC information while leaving Is() and As() functionality unchanged

func (Error) Wrapf added in v0.1.4

func (e Error) Wrapf(err error, format string, args ...interface{}) error

Wrapf an error with message while overriding or adding Type,HTTP,GRPC information while leaving Is() and As() functionality unchanged

type ErrorType added in v0.0.2

type ErrorType struct {
	TypeCode string `protobuf:"bytes,1,opt,name=TypeCode,proto3" json:"TypeCode,omitempty"`
	HTTPCode int64  `protobuf:"varint,2,opt,name=HTTPCode,proto3" json:"HTTPCode,omitempty"`
	GRPCCode int64  `protobuf:"varint,3,opt,name=GRPCCode,proto3" json:"GRPCCode,omitempty"`
	// contains filtered or unexported fields
}

func (*ErrorType) Descriptor deprecated added in v0.0.2

func (*ErrorType) Descriptor() ([]byte, []int)

Deprecated: Use ErrorType.ProtoReflect.Descriptor instead.

func (*ErrorType) GetGRPCCode added in v0.0.2

func (x *ErrorType) GetGRPCCode() int64

func (*ErrorType) GetHTTPCode added in v0.0.2

func (x *ErrorType) GetHTTPCode() int64

func (*ErrorType) GetTypeCode added in v0.0.2

func (x *ErrorType) GetTypeCode() string

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

func (*ErrorType) Reset added in v0.0.2

func (x *ErrorType) Reset()

func (*ErrorType) String added in v0.0.2

func (x *ErrorType) String() string

type GRPCCoder

type GRPCCoder interface {
	GRPCCode() codes.Code
}

type HTTPCoder

type HTTPCoder interface {
	HTTPCode() int
}

type TypeCoder added in v0.0.2

type TypeCoder interface {
	TypeCode() string
}

TypeCoder interface to extract an errors embeddable type as a string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL