Go: Creating Custom Error Wrapper and Do Proper Error Equality Check
Tags: go, errors, http, restapi
Published: 2021-08-24 13:24:05

## Preface

Custom error wrapping is a way to bring information or context to top level function caller. So you as the developer knows just what causes the error for a program and not receive gibberish message from the machine.

With error wrapper, You can bring interesting information like what HTTP response code should be used, where the line and location of the error happened or the information of the error itself.

If you for example build a REST API and there's an error in a transaction, you want to know what error is, so you can response to a client with proper response code and proper error message.

If you're database is out of service, you don't want to send `400 Bad Request` response code, you want `503 Service Unavailable`. You can always use `500 Internal Server Error` but that's really vague, and you as the developer will need more time to identify the error. More time identifying error means less business uptime, and you don't want less business uptime. How to fulfill this goal? We give contexts to our errors.

### This article is written when Golang 1.17 is released.

The methods in this article can be deprecated or considered not optimal in the future. so be vigilant of golang updates.

## Warning: This Article Assumes You Use Golang 1.13 or above

There's an overhaul for more ergonomic error handling in Golang 1.13. The [errors](https://pkg.go.dev/errors@go1.13) library in Golang support more robust error checking via calling `Is(err error, target error) bool` and `As(err error, target error) bool`. Which you should use since this will help with potential bugs.

## Creating Error Wrapper

Error Wrapper can be easily made by implementing `error` interface. The `error` interface has this definition:

```go
type error interface {
	Error() string
}
```

_Off-topic Note_: Because `error` interface is part of standard runtime library, it's always in scope, thus it uses lowercase letters instead of Pascal Case.

So now, let's make our own custom error wrapper.

```go
type CustomErrorWrapper struct{}
```

Let's leave the definition empty for now.

To implement error, you only have to add this below:

```go
func (err CustomErrorWrapper) Error() string {
	return "custom error wrapper"
}
```

Congratulations! Now your `CustomErrorWrapper` has implemented `error` interface. You can prove this by creating a function like snippet below, and check if the compiler will complain (spoiler: it will not!)

```go
func NewErrorWrapper() error {
	return CustomErrorWrapper{}
}
```

Obviously it is useless right now. Calling the `Error()` method will only produce hard-coded `"custom error wrapper"`. We need to customize the error wrapper so it can do what we intended it to be.

```go
type CustomErrorWrapper struct{}

func (err CustomErrorWrapper) Error() string {
	return "custom error wrapper"
}

func NewErrorWrapper() error {
	return CustomErrorWrapper{}
}

func main() {
	err := NewErrorWrapper()
	fmt.Println(err.Error()) // Will only report "custom error wrapper"
}
```

## Customizing Error Wrapper

Let's continue with REST API theme. We want to send error information from our API logic to HTTP Handler, so let's fill the struct with useful types.

```go
type CustomErrorWrapper struct {
	Message string `json:"message"` // Human readable message for clients
	Code    int    `json:"-"`       // HTTP Status code. We use `-` to skip json marshaling.
	Err     error  `json:"-"`       // The original error. Same reason as above.
}
```

Let's also update the `Constructor` function to ensure the struct will always be filled when we use the `CustomErrorWrapper`.

```go
func NewErrorWrapper(code int, err error, message string) error {
	return CustomErrorWrapper{
		Message: message,
		Code: code,
		Err: err,
	}
}
```

Don't forget to modify the `error` implementation as well.

```go
// Returns Message if Err is nil. You can handle custom implementation of your own.
func (err CustomErrorWrapper) Error() string {
	// guard against panics
	if err.Err != nil {
		return err.Err.Error()
	}
	return err.Message
}
```

Ok, for now it's kind of obvious what the error wrapper is intended to be. We wrap our original error into a new kind of error, with http status code information, original error for logging. But before we continue to http handler, we have to address the following question first:

> how do we do equality check for the original error?

We don't really want the `CustomErrorWrapper` when we do equality check, we want to check against the original error.

This following snippet will show you what the equality check problem is.

```go
func main() {
	errA := errors.New("missing fields")
	wrapped := NewErrorWrapper(400, errA, "bad data")

	if errA == wrapped { // always false
		// This code flow here will never be called
		// because the value of struct errA (private in errors lib)
		// is different than our wrapped error.
	}

	// or something worse like this
	wrapWrapped := NewErrorWrapper(400, wrapped, "bad data")

	if wrapWrapped == wrapped { // always false.
		// wrapWrapped.Err is different than wrapped.Err
	}
}
```

How to solve this problem?

`errors` lib has an [anonymous interface](https://go.googlesource.com/go/+/go1.13/src/errors/wrap.go#15) that we can implement.

It's _anonymous_ definition is like this:

```go
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
	u, ok := err.(interface {
		Unwrap() error
	})
	if !ok {
		return nil
	}
	return u.Unwrap()
}
```

Look at this following snippet:

```go
u, ok := err.(interface {
	Unwrap() error
})
```

It's an anonymous interface that we can implement. This interface is used in `errors` lib in [Is](https://go.googlesource.com/go/+/go1.13/src/errors/wrap.go#47) and [As](https://go.googlesource.com/go/+/go1.13/src/errors/wrap.go#87) functions.

These two functions will be used by us to handle equality checking, so we have to implement it for our wrapper.

```go
// Implements the errors.Unwrap interface
func (err CustomErrorWrapper) Unwrap() error {
	return err.Err // Returns inner error
}
```

The implementation will be used recursively by `Is` and `As` function until it cannot `Unwrap` anymore.

So it doesn't really matter if `CustomErrorWrapper` wraps another `CustomErrorWrapper`, you can always get the root error cause as the wrapped `CustomErrorWrapper` will be called to `Unwrap` itself also.

## Doing Equality Check on CustomErrorWrapper

Now let's do the equality check. We don't use the `==` syntax anymore. Instead we use the `errors.Is` syntax.

```go
var (
	ErrSomething = errors.New("something happened")
)

func doSomething() error {
	return ErrSomething
}

func theOneCallsDoSomething() error {
	err := doSomething()
	if err != nil {
		return NewErrorWrapper(500, err, "something happened")
	}
	return nil
}

func main() {
	err := theOneCallsDoSomething()
	if errors.Is(err, ErrSomething) { // always false if err is nil
		// handle ErrSomething error
	}
}
```

But what if the error "shape" is a struct, like for example [\*json.SyntaxError](https://pkg.go.dev/encoding/json#SyntaxError)?

```go
type Foo struct {
	Bar string `json:"bar"`
}

func repoData() (Foo, error) {
	fake := []byte(`fake`)
	var foo Foo
	err := json.Unmarshal(fake, &foo)
	if err != nil {
		err = NewErrorWrapper(500, err, "failed to marshal json data")
	}
	return foo, err
}

func getDataFromRepo() (Foo, error) {
	foo, err := repoData()
	if err != nil {
		return foo, NewErrorWrapper(500, err, "failed to get data from repo")
	}
	return foo, err
}

func main() {
	foo, err := getDataFromRepo()

	var syntaxError *json.SyntaxError
	if errors.As(err, &syntaxError) {
		fmt.Println(syntaxError.Offset) // Output: 3
	}

	// we could also check for CustomErrorWrapper
	var ew CustomErrorWrapper
	if errors.As(err, &ew) { // errors.As stop on first match
		fmt.Println(ew.Message) // Output: failed to get data from repo
		fmt.Println(ew.Code)    // Output: 500
	}

	_ = foo
}
```

Notice how the syntax is used:

```go
var syntaxError *json.SyntaxError
if errors.As(err, &syntaxError) {
```

It acts like how `json.Unmarshal` would, but a little bit different. `errors.As` <b><u>requires pointer to a `Value` that <i>implements `error` interface</i>. Any otherway and it will panic.</u></b>

The code snippet above includes check for `CustomErrorWrapper`. But it only gets the first occurences or the outer most layer of wrapping.

```go
// we could also check for CustomErrorWrapper
var ew CustomErrorWrapper
if errors.As(err, &ew) { // errors.As stop on first match
	fmt.Println(ew.Message) // Output: failed to get data from repo
	fmt.Println(ew.Code)    // Output: 500
}
```

To get the lower wrapper message you have to do your own implementations. Like code snippet below:

```go
// Returns the inner most CustomErrorWrapper
func (err CustomErrorWrapper) Dig() CustomErrorWrapper {
	var ew CustomErrorWrapper
	if errors.As(err.Err, &ew) {
		// Recursively digs until wrapper error is not CustomErrorWrapper
		return ew.Dig()
	}
	return err
}
```

And to actually use it:

```go
var ew CustomErrorWrapper
if errors.As(err, &ew) { // errors.As stop on first match
	fmt.Println(ew.Message) // Output: failed to get data from repo
	fmt.Println(ew.Code)    // Output: 500
	// Dig for innermost CustomErrorWrapper
	ew = ew.Dig()
	fmt.Println(ew.Message) // Output: failed to marshal json data
	fmt.Println(ew.Code)    // Output: 400
}
```

## Full Code Preview

Ok let's combine everything to get the full picture of how to create Error Wrapper.

```go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
)

type CustomErrorWrapper struct {
	Message string `json:"message"` // Human readable message for clients
	Code    int    `json:"-"`       // HTTP Status code. We use `-` to skip json marshaling.
	Err     error  `json:"-"`       // The original error. Same reason as above.
}

// Returns Message if Err is nil
func (err CustomErrorWrapper) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return err.Message
}

func (err CustomErrorWrapper) Unwrap() error {
	return err.Err // Returns inner error
}

// Returns the inner most CustomErrorWrapper
func (err CustomErrorWrapper) Dig() CustomErrorWrapper {
	var ew CustomErrorWrapper
	if errors.As(err.Err, &ew) {
		// Recursively digs until wrapper error is not in which case it will stop
		return ew.Dig()
	}
	return err
}

func NewErrorWrapper(code int, err error, message string) error {
	return CustomErrorWrapper{
		Message: message,
		Code:    code,
		Err:     err,
	}
}

type Foo struct {
	Bar string `json:"bar"`
}

func repoData() (Foo, error) {
	fake := []byte(`fake`)
	var foo Foo
	err := json.Unmarshal(fake, &foo)
	if err != nil {
		err = NewErrorWrapper(400, err, "failed to marshal json data")
	}
	return foo, err
}

func getDataFromRepo() (Foo, error) {
	foo, err := repoData()
	if err != nil {
		return foo, NewErrorWrapper(500, err, "failed to get data from repo")
	}
	return foo, err
}

func main() {
	foo, err := getDataFromRepo()

	var syntaxError *json.SyntaxError

	// Get root error
	if errors.As(err, &syntaxError) {
		fmt.Println(syntaxError.Offset)
	}

	var ew CustomErrorWrapper
	if errors.As(err, &ew) { // errors.As stop on first match
		fmt.Println(ew.Message) // Output: failed to get data from repo
		fmt.Println(ew.Code)    // Output: 500
		// Dig for innermost CustomErrorWrapper
		ew = ew.Dig()
		fmt.Println(ew.Message) // Output: failed to marshal json data
		fmt.Println(ew.Code)    // Output: 400
	}

	_ = foo
}
```

## Error Wrapper in REST API Use Case

Let's use the `CustomErrorWrapper` against something more concrete like HTTP REST Api to show flexibility of error wrapper to propagate information upstream.

First let's create Response Helpers

```go
func ResponseError(rw http.ResponseWriter, err error) {
	rw.Header().Set("Content-Type", "Application/json")
	var ew CustomErrorWrapper
	if errors.As(err, &ew) {
		rw.WriteHeader(ew.Code)
		log.Println(ew.Err.Error())
		_ = json.NewEncoder(rw).Encode(ew)
		return
	}
	// handle non CustomErrorWrapper types
	rw.WriteHeader(500)
	log.Println(err.Error())
	_ = json.NewEncoder(rw).Encode(map[string]interface{}{
		"message": err.Error(),
	})
}

func ResponseSuccess(rw http.ResponseWriter, data interface{}) {
	rw.Header().Set("Content-Type", "Application/json")
	body := map[string]interface{}{
		"data": data,
	}
	_ = json.NewEncoder(rw).Encode(body)
}
```

The one we interested in is `ResponseError`. In the snippet:

```go
var ew CustomErrorWrapper
if errors.As(err, &ew) {
	rw.WriteHeader(ew.Code)
	log.Println(ew.Err.Error())
	_ = json.NewEncoder(rw).Encode(ew)
	return
}
```

If the error is in fact a `CustomErrorWrapper`, we can match the response code and message from the given `CustomErrorWrapper`.

Then let's add server, handler code and repo simulation code.

```go
var (
	useSecondError = false
	firstError     = errors.New("first error")
	secondError    = errors.New("second error")
)

// Always error
func repoSimulation() error {
	var err error
	if useSecondError {
		err = NewErrorWrapper(404, firstError, "data not found")
	} else {
		err = NewErrorWrapper(503, secondError, "required dependency are not available")
	}
	// This is for example and readability purposes! Don't follow this example. This is not thread / goroutine safe.
	// Use Atomic Operations or Mutex for safe handling.
	useSecondError = !useSecondError
	return err
}

func handler(rw http.ResponseWriter, r *http.Request) {
	err := repoSimulation()
	if err != nil {
		ResponseError(rw, err)
		return
	}
	ResponseSuccess(rw, "this code should not be reachable")
}


func main() {
	// Routes everything to handler
	server := http.Server{
		Addr:    ":8080",
		Handler: http.HandlerFunc(handler),
	}

	log.Println("server is running on port 8080")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}
```

The `repoSimulation` is simple. It will (in its bad practice glory) alternate returned error.

If we add everything, it will look like this:

## Full Code Preview HTTP Service

```go
package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
)

type CustomErrorWrapper struct {
	Message string `json:"message"` // Human readable message for clients
	Code    int    `json:"-"`       // HTTP Status code. We use `-` to skip json marshaling.
	Err     error  `json:"-"`       // The original error. Same reason as above.
}

// Returns Message if Err is nil
func (err CustomErrorWrapper) Error() string {
	if err.Err != nil {
		return err.Err.Error()
	}
	return err.Message
}

func (err CustomErrorWrapper) Unwrap() error {
	return err.Err // Returns inner error
}

// Returns the inner most CustomErrorWrapper
func (err CustomErrorWrapper) Dig() CustomErrorWrapper {
	var ew CustomErrorWrapper
	if errors.As(err.Err, &ew) {
		// Recursively digs until wrapper error is not CustomErrorWrapper
		return ew.Dig()
	}
	return err
}

func NewErrorWrapper(code int, err error, message string) error {
	return CustomErrorWrapper{
		Message: message,
		Code:    code,
		Err:     err,
	}
}

// ===================================== Simulation =====================================

var (
	useSecondError = false
	firstError     = errors.New("first error")
	secondError    = errors.New("second error")
)

// Always error
func repoSimulation() error {
	var err error
	if useSecondError {
		err = NewErrorWrapper(404, firstError, "data not found")
	} else {
		err = NewErrorWrapper(503, secondError, "required dependency are not available")
	}
	// This is for example and readability purposes! Don't follow this example. This is not thread / goroutine safe.
	// Use Atomic Operations or Mutex for safe handling.
	useSecondError = !useSecondError
	return err
}

func handler(rw http.ResponseWriter, r *http.Request) {
	err := repoSimulation()
	if err != nil {
		ResponseError(rw, err)
		return
	}
	ResponseSuccess(rw, "this code should not be reachable")
}

func ResponseError(rw http.ResponseWriter, err error) {
	rw.Header().Set("Content-Type", "Application/json")
	var ew CustomErrorWrapper
	if errors.As(err, &ew) {
		rw.WriteHeader(ew.Code)
		log.Println(ew.Err.Error())
		_ = json.NewEncoder(rw).Encode(ew)
		return
	}
	// handle non CustomErrorWrapper types
	rw.WriteHeader(500)
	log.Println(err.Error())
	_ = json.NewEncoder(rw).Encode(map[string]interface{}{
		"message": err.Error(),
	})
}

func ResponseSuccess(rw http.ResponseWriter, data interface{}) {
	rw.Header().Set("Content-Type", "Application/json")
	body := map[string]interface{}{
		"data": data,
	}
	_ = json.NewEncoder(rw).Encode(body)
}

func main() {
	// Routes everything to handler
	server := http.Server{
		Addr:    ":8080",
		Handler: http.HandlerFunc(handler),
	}

	log.Println("server is running on port 8080")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}
```

Compile the code and run, and it will print the server is running on port 8080.

Run this command repeatedly in different terminal.

```
curl -sSL -D - localhost:8080
```

You will get different message every time with different response code. With the logger only showing private error message not shown to client.

This may seem simple, but it's very extensible, scalable, and can be created as flexible or frigid as you like.

For example, You can integrate `runtime.Frame` to get the location of whoever called `NewErrorWrapper` for easier debugging.