BETA

ainsleyclark/errors

🫠 A drop-in replacement for Go errors, with some added sugar! Error handling in Go made easy with codes, messages and more. Failure is your domain!


10
GitHub
Errors Logo

made-with-Go Go Report Card Maintainability Test codecov go.dev reference

🫠 Errors

A drop-in replacement for Go errors, with some added sugar! Error handling in Go made easy with codes, messages and more. Failure is your domain!

Overview

  • βœ… A humble selection of errors codes that touches most bases.
  • βœ… User friendly error messages for returning via an API for example.
  • βœ… Operation support (Struct.Method) naming convention.
  • βœ… Retrieve call stacks as preformatted or as a string slice.
  • βœ… Generate HTTP response codes from all error types.
  • βœ… Extremely lightweight with no external dependencies.

Why?

Errors are as important in your domain and application as the entities such as a User or Database. They should be treated as individual types. Not only do they give a clear meaning to the users of the application if something goes wrong, they can save hours of debugging time when used effectively.

Coupled with consistent and effective use of a logging package, we are able to tell if something goes wrong, where it went wrong, how it went wrong and.

Installation

go get -u github.com/ainsleyclark/errors

How to use

See below on some common examples on how to use the error package.

The Error Type

The Error struct below describes an application error that's returned when using the New...() constructors such as NewInternal(). See below on more detail on what each field contains.

// Error defines a standard application error.
type Error struct {
	// The application error code.
	Code string `json:"code" bson:"code"`
	// A human-readable message to send back to the end user.
	Message string `json:"message" bson:"message"`
	// Defines what operation is currently being run.
	Op string `json:"operation" bson:"op"`
	// The error that was returned from the caller.
	Err error `json:"error" bson:"err"`
}

Returning

Below is an example of returning a formatted Error type in a database call for obtaining a singular user. If no rows are found, we return a NOTFOUND code. Likewise if there was an error executing the SQL query, we return a NOTFOUND code with user-friendly messages.

func (s *UserStore) Find(ctx context.Context, schema string, id int64) (core.User, error) {
	const op = "UserStore.Find"

	q := "SELECT from users WHERE ID = ? LIMIT 1"

	var out core.User
	err := s.DB().GetContext(ctx, &out, q.Build(), id)
	if err == sql.ErrNoRows {
		return core.User{}, errors.NewNotFound(err, fmt.Sprintf("Error obtaining User with the ID: %d", id), op)
	} else if err != nil {
		return core.User{}, errors.NewInternal(err, "Error executing SQL query", op)
	}

	return out, nil
}

Output

Let's assume that an SQL error occurred during the execution of the query and no rows were returned. Without any context of the error, we simply get:

syntax error near SELECT

However, by calling err.Error() on our wrapped error, it will return:

<internal> /Users/me/project/store/users.go:27 - UserStore.Find: syntax error near SELECT, Error executing SQL query

Now we know exactly where the error occurred, why it occurred and what file line and method.

Checking Types

The package comes built in with handy functions for obtaining messages, codes and casting to the Error type, see below for some examples.

To Error

e := errors.New("error")
err := errors.ToError(e)

Obtaining a message

err := errors.NewInternal(errors.New("error"), "My Message", "Operation")
msg := errors.Message(err)
fmt.Println(msg) // Output - "My Message"

Obtaining an error code

err := errors.NewInternal(errors.New("error"), "My Message", "Operation")
code := errors.Code(err)
fmt.Println(code) // Output - "internal"

Available Error Codes

Below is a list of available error codes within the errors package. It's tempting to build fine-grained error codes, but it's a lot easier to manage more generic codes. The codes below are a good start to set off on, if you feel there is one missing, please open a pull request.

CodeValueNotes
CONFLICT"conflict"An action cannot be performed.
INTERNAL"internal"Error within the application.
INVALID"invalid"Validation failed.
NOTFOUND"not_found"Entity does not exist.
UNKNOWN"unknown"Application unknown error.
MAXIMUMATTEMPTS"maximum_attempts"More than allowed action.
EXPIRED"expired"Subscription expired.

Benchmarks

Ran on 19/05/2022

$ go version
go version go1.18.1 darwin/amd64

$ go test -benchmem -bench .
goos: darwin
goarch: amd64
pkg: github.com/ainsleyclark/errors
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkNew-16                          1000000              1026 ns/op            1320 B/op          6 allocs/op
BenchmarkNewInternal-16                  1000000              1023 ns/op            1320 B/op          6 allocs/op
BenchmarkError_Error-16                  6582385               180.6 ns/op           464 B/op          4 allocs/op
BenchmarkError_Code-16                  637953824                1.883 ns/op           0 B/op          0 allocs/op
BenchmarkError_Message-16               628838649                1.906 ns/op           0 B/op          0 allocs/op
BenchmarkError_ToError-16               705541435                1.699 ns/op           0 B/op          0 allocs/op
BenchmarkError_HTTPStatusCode-16        1000000000               0.6282 ns/op          0 B/op          0 allocs/op

Contributing

Please feel free to make a pull request if you think something should be added to this package!

Credits

Shout out to the incredible Maria Letta for her excellent Gopher illustrations.

Licence

Code Copyright 2022 Errors. Code released under the MIT Licence.