errors

package module
v2.1.0+incompatible Latest Latest
Warning

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

Go to latest
Published: May 23, 2019 License: MIT Imports: 6 Imported by: 0

README

[WIP] errors CircleCI codecov GoDoc Go Report Card

THIS DOCUMENT IS A WORK IN PROGRESS

Errors have always been a problem in rthe development of our servers in Go. This package aims to address the main problems, centralizing a solution.

Scenario

Let's start by creating our scenario: The user Astrobervaldo already signed in system S. Astrobervaldo tries to create a new TODO item which fails because of an invalid JSON was sent as input.

API

In APIs, the probable endpoints would be implemented in a POST to /v1/todo. On this endpoint, one of the first things that would happen is to parse the request body in order to extract what Astrobervaldo sent. As we can image, it probably would use a json.Unmarshal and it would return an error.

What should we say to the user? What structure will be used? Is it centralized?

GraphQL

TODO

Solution

The idea is to wrap errors with simple structures that will aggregate more and more little pieces of information about the error.

Consider the following code:

err := json.Unmarshal(data, &todoItemInput)
if err != nil {
    return errors.WrapHttp(err, 400) // BadRequest
}

The code above shows an error being wrapped as an HttpError. Upwards, it can be checked and the status code, inside of it, can be applied to a possible HTTP response structure.

To improve the usage, a Wrap function was created:

err := json.Unmarshal(data, &todoItemInput)
if err != nil {
    return errors.Wrap(err, Http(400)) // BadRequest
}

This example has the same effect of the example shown previously. The difference is that Wrap is a variadic function. Yes, it can receive multiple arguments and make multiple wraps:

err := json.Unmarshal(data, &todoItemInput)
if err != nil {
    return errors.Wrap(err, Http(400), Code("invalid-json")) // BadRequest
}

In this example, err was wrapped twice. With a HttpError and a ErrorWithCode. In other words, the error returned is a ErrorWithCode with a HttpError as Reason, this HttpError has the original err as Reason.

How do the pieces of info get together?

For that, an interface type ErrorResponse interface was implemented.

An ErrorResponse aggregates all extra information about an error. The idea is that an ErrorResponse will be serialized and sent whom is using the system.

How aggregation occurs?

Some errors, those that need to add information to the ErrorResponse, implement another interface ErrorResponseAggregator. This interface has a AppendData(ErrorResponse) which get called for each Reason recursively:

err := json.Unmarshal(data, &todoItemInput)
if err != nil {
    return errors.WrapHttp(err, 400) // BadRequest
}

Here, the returned error would have its AppendData method called, and only this one because its Reason is not an ErrorResponseAggregator (it is the original unmarshaling error).

err := json.Unmarshal(data, &todoItemInput)
if err != nil {
    return errors.Wrap(err, Http(400), Code("invalid-json")) // BadRequest
}

In this case, a ErrorWithCode would have its AppendData called, a HttpError would have its AppendData called afterwards, and then the original the unmarshaling error would be reached and nothing more would happen.

Error Types
ErrorWithMessage

This error adds the Message information to the error. The message should be humam readable.

Wrap: Message(message string): It returns a default ErrorWithMessage implementation which is also a ErrorResponseAggregator.

Usage:

// using plain string
errors.Wrap(err, "failed on such task")

// using wrapper
errors.Wrap(err, errors.Message("failed on such task"))
HttpError

This error adds the StatusCode information to the error.

Wrap: Http(statusCode int): It returns a default HttpError implementation which is also a ErrorResponseAggregator.

Usage:

// using plain int
errors.Wrap(err, http.StatusNotFound)

// using wrapper
errors.Wrap(err, errors.Http(http.StatusNotFound))
ModuleError

This error adds the Module information to the error. The module specifies what area of the system the error is emerging from.

Wrap: Module(module string): It returns a default ModuleError implementation which is also a ErrorResponseAggregator.

Usage:

// using predefined module
var (
    ModuleCatalog = errors.Module("catalog")
)

errors.Wrap(err, ModuleCatalog)
ErrorWithCode

This error adds the Code information to the error. The code refers to a (documented) known error.

Wrap: Code(code string): It returns a default CodeError implementation which is also a ErrorResponseAggregator.

Usage:

// using predefined code
var (
    CodeProductNotFound = errors.Code("product-not-found")
)

errors.Wrap(err, CodeProductNotFound)
ValidationError

A special case error for dealing with validation errors from the go-playground/validator package.

Wrap: Validation(): It returns a default ValidationError implementation which is also a ErrorResponseAggregator.

Usage:

// using wrapper
errors.Wrap(err, errors.Validation())

// add a custom message
errros.Wrap(err, "custom message", errors.Validation())
Combining Errors

In the following example, we show how combining is possible:

// modules.go
var (
    ModuleService = errors.Module("services")
    // another modules...
)

// services/codes.go
var (
    UserNotFound = errors.Combine(errors.Code("users.notFound"), myapp.ModuleService)
)

// services/users/find.go
func Find(id string) (*User, error) {
    // something goes here...

    if err != nil {
        // instead of `errors.Wrap(err, errors.Code("users.notFound"), errors.Module("services"))`
        return nil, errors.Wrap(err, codes.UserNotFound)
    }

    // more...
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var New = errors.New

New is an alias for the default `errors.New`.

Functions

func AggregateToResponse

func AggregateToResponse(data interface{}, errResponse ErrorResponse) bool

AggregateToResponse add the error information pieces to the errResponse. Then, it tries to check its `Reason` (if the error is an `ErrorWithReason`) for more data. It goes upward until `Reason` is not found.

If at least one `ErrorResponseAggregator` is found it returns true, otherwise it returns false.

func Is

func Is(err, target error) bool

func Reason

func Reason(err error) error

func Wrap

func Wrap(reason error, options ...interface{}) error

func WrapCode

func WrapCode(reason error, code string) error

WrapCode creates a new instance of a Reportable error with the given code.

func WrapHttp

func WrapHttp(reason error, status int) error

WrapHttp wraps a reason with an HttpError.

func WrapMessage

func WrapMessage(reason error, code string) error

WrapMessage creates a new instance of a Reportable error with the given code.

func WrapModule

func WrapModule(reason error, module string) error

WrapModule wraps an original error with the module information, returning a new instance with the reason set.

func WrapValidation

func WrapValidation(reason error) error

Types

type ErrorResponse

type ErrorResponse interface {
	SetParam(name string, value interface{})
}

ErrorResponse is an abstraction of an error response that might, or not, be sent to the customer.

type ErrorResponseAggregator

type ErrorResponseAggregator interface {
	AppendData(response ErrorResponse)
}

ErrorResponseAggregator is an error that can modify an `ErrorResponse`. It, usually, adds more information to explain better the error.

type ErrorWithCode

type ErrorWithCode interface {
	Code() string
}

ErrorWithCode implements an error with a code related to it.

type ErrorWithMessage

type ErrorWithMessage interface {
	Message() string
}

ErrorWithMessage implements an error with a code related to it.

type ErrorWithValidation

type ErrorWithValidation interface {
	Errors() map[string][]string
}

type HttpError

type HttpError interface {
	StatusCode() int
}

type ModuleError

type ModuleError interface {
	Module() string
}

ModuleError describes an error that belongs to a module.

type Option

type Option func(err error) error

func Code

func Code(code string) Option

func Combine

func Combine(options ...interface{}) Option

func Http

func Http(status int) Option

func Message

func Message(message string) Option

func Module

func Module(module string) Option

func Validation

func Validation() Option

type ValidationError

type ValidationError struct {
	// contains filtered or unexported fields
}

ValidationError implements wrapping validation errors into a reportable

func (*ValidationError) AppendData

func (err *ValidationError) AppendData(response ErrorResponse)

func (*ValidationError) Code

func (err *ValidationError) Code() string

Code returns the "validation" error code.

func (*ValidationError) Error

func (err *ValidationError) Error() string

func (*ValidationError) Errors

func (err *ValidationError) Errors() map[string][]string

func (*ValidationError) Unwrap

func (err *ValidationError) Unwrap() error

Unwrap returns the next error in the error chain. If there is no next error, Unwrap returns nil.

type Wrapper

type Wrapper interface {
	Unwrap() error
}

Wrapper is an error that has an main reason because it was raised. It is used to wrap more abstract errors with improved messages for the customer.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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