# errors

This is our own implementation of the Golang `error` interface.

## Why

Packages tend to use one of the following to return errors:
* Standard go ```"errors"``` package with ```errors.New("...")```
* Standard go ```"fmt"``` package with ```fmt.Errorf("...")```
* ```github.com/pkg/errors``` package with ```errors.New("...")```

Of those three, the pkg/errors is by far the better option as it includes an error stack.

One could use pkg/errors, but:
- it does not offer much flexibility in how it displays errors
- its display is cluttered, with the wrong info (e.g. runtime stack and not the error handling stack)
- it does not display the up passing of errors in the stack unless you add the stack each time
- if you do add the stack each time, it dumps many copies of a stack and becomes cluttered

We write our own version of errors, to:
- always add the stack
- add the correct stack of where we raised/handled the error
- make error handling simpler (Wrapf(err) vs WithMessage(WithStack(err)))
- give us all the flexibility we need
    - e.g. give us option to output JSON structured stack

For lots of detail on this, see comments in error_formats_test.go and run that test with ```go test -v error_formats_test.go``` ...

## Usage
Get the package into your project:
```
go get gitlab.com/uafrica/go-utils
```

## func New()
Fail with your own simple error message:

```
if !authenticated {
    return errors.New("not authenticated")
}
```

## func Errorf()
Fail with your own error using Printf style, e.g. when you detect an invalid value:

```
if limit < 0 {
    return errors.Errorf("invalid limit %d<0", limit)
}
```

## func Wrapf()
Fail when a called function returned an error:

```
if err := db.Select(query); err != nil {
    return errors.Wrapf(err, "query failed on user=%s", username)
}
```

## Refactoring exiting code
Replace all other errors package imports with this package:
```
import (
    "gitlab.com/uafrica/go-utils/errors"
)
```

Refactor from standad go "errors" package:

- No change: ```errors.New()``` is still supported in this package

Refactor from "fmt" package:

- Replace `errors.Errorf("my message: %v", err)` with `errors.Wrapf(err, "my message")` so that the layers are preserved and not merged into a single text string.

Refactor from "github.com/pkg/errors":

- Replace ```errors.WithStack(err)``` with ```errors.New(err)```.
- Replace ```return err``` with ```return errors.Wrapf(err, "some message")``` saying what failed as result of the err
- Replace ```errors.WithMessagef(err, "...", ...)``` with ```return errors.Wrapf(err, "...", ...)```
- Replace ```errors.WithMessage(err, "...")``` with ```return errors.Wrap(err, "...")```

## Formatting

Report an error with:

```
user,err := getUser(userName)
if err != nil {
    log.Errorf("failed to get user %s: %+v", username, err)
    ...
}
```

Select the appropriate format:

- `%+c` in most cases to write the full compact error with file names and line numbers all on one line.
- `%+v` is the same when you write to console over multipole lines
- `%c` is full error on one line, without reference to source code, i.e. concatenate the error message of all levels e.g. ```A, because B, because C```, which you also get from ```err.Error()```.
- `%v` is full error over multipole lines, without references to source code, e.g.
```
    A, because
    B, because
    C
```
- `%s` is just the last error message.

## JSON error:

Call ```err.Description()``` to get a struct that can marshal to JSON for a complete dump, e.g.:

```
    {
        "error": "login failed",
        "source": {
            "package": "gitlab.com/uafrica/go-utils/errors/errors_test",
            "file": "errors_test.go",
            "line": 18,
            "function": "TestErrorFormatting"
        },
        "cause": {
            "error": "failed to find account",
            "source": {
                "package": "gitlab.com/uafrica/go-utils/errors/errors_test",
                "file": "errors_test.go",
                "line": 17,
                "function": "TestErrorFormatting"
            },
            "cause": {
                "error": "query failed",
                "source": {
                    "package": "gitlab.com/uafrica/go-utils/errors/errors_test",
                    "file": "errors_test.go",
                    "line": 16,
                    "function": "TestErrorFormatting"
                },
                "cause": {
                    "error": "you have problem in your SQL near xxx"
                }
            }
        }
    }
```

## func Caller()

This function can be used also for logging to determine the caller from the runtime stack. It takes a skip value to skip a few levels of the stack, making it flexible to be called in various wrapper functions, without reporting the wrappers.

## func Stack()

This function is similar to Caller() but reports an array of callers, not only one.