# errs

[![GoDoc](https://godoc.org/github.com/zeebo/errs?status.svg)](https://godoc.org/github.com/zeebo/errs)
[![Sourcegraph](https://sourcegraph.com/github.com/zeebo/errs/-/badge.svg)](https://sourcegraph.com/github.com/zeebo/errs?badge)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeebo/errs)](https://goreportcard.com/report/github.com/zeebo/errs)

errs is a package for making errors friendly and easy.

### Creating Errors

The easiest way to use it, is to use the package level [New][New] function.
It's much like `fmt.Errorf`, but better. For example:

```go
func checkThing() error {
	return errs.New("what's up with %q?", "zeebo")
}
```

Why is it better? Errors come with a stack trace that is only printed
when a `"+"` character is used in the format string. This should retain the
benefits of being able to diagnose where and why errors happen, without all of
the noise of printing a stack trace in every situation. For example:

```go
func doSomeRealWork() {
	err := checkThing()
	if err != nil {
		fmt.Printf("%+v\n", err) // contains stack trace if it's a errs error.
		fmt.Printf("%v\n", err)  // does not contain a stack trace
		return
	}
}
```

### Error Classes

You can create a [Class][Class] of errors and check if any error was created by
that class. The class name is prefixed to all of the errors it creates. For example:

```go
var Unauthorized = errs.Class("unauthorized")

func checkUser(username, password string) error {
	if username != "zeebo" {
		return Unauthorized.New("who is %q?", username)
	}
	if password != "hunter2" {
		return Unauthorized.New("that's not a good password, jerkmo!")
	}
	return nil
}

func handleRequest() {
	if err := checkUser("zeebo", "hunter3"); Unauthorized.Has(err) {
		fmt.Println(err)
	}

	// output:
	// unauthorized: that's not a good password, jerkmo!
}
```

Classes can also [Wrap][ClassWrap] other errors, and errors may be wrapped
multiple times. For example:

```go
var (
	Error        = errs.Class("mypackage")
	Unauthorized = errs.Class("unauthorized")
)

func deep3() error {
	return fmt.Errorf("ouch")
}

func deep2() error {
	return Unauthorized.Wrap(deep3())
}

func deep1() error {
	return Error.Wrap(deep2())
}

func deep() {
	fmt.Println(deep1())

	// output:
	// mypackage: unauthorized: ouch
}
```

In the above example, both `Error.Has(deep1())` and `Unauthorized.Has(deep1())`
would return `true`, and the stack trace would only be recorded once at the
`deep2` call.

In addition, when an error has been wrapped, wrapping it again with the same class will
not do anything. For example:

```go
func doubleWrap() {
	fmt.Println(Error.Wrap(Error.New("foo")))

	// output:
	// mypackage: foo
}
```

This is to make it an easier decision if you should wrap or not (you should).

### Utilities

[Classes][Classes] is a helper function to get a slice of classes that an error
has. The latest wrap is first in the slice. For example:

```go
func getClasses() {
	classes := errs.Classes(deep1())
	fmt.Println(classes[0] == &Error)
	fmt.Println(classes[1] == &Unauthorized)

	// output:
	// true
	// true
}
```

Finally, a helper function, [Unwrap][Unwrap] is provided to get the
wrapped error in cases where you might want to inspect details. For
example:

```go
var Error = errs.Class("mypackage")

func getHandle() (*os.File, error) {
	fh, err := os.Open("neat_things")
	if err != nil {
		return nil, Error.Wrap(err)
	}
	return fh, nil
}

func checkForNeatThings() {
	fh, err := getHandle()
	if os.IsNotExist(errs.Unwrap(err)) {
		panic("no neat things?!")
	}
	if err != nil {
		panic("phew, at least there are neat things, even if i can't see them")
	}
	fh.Close()
}
```

It knows about both the `Cause() error` and `Unwrap() error` methods that are
often used in the community, and will call them as many times as possible.

### Defer

The package also provides [WrapP][WrapP] versions of [Wrap][Wrap] that are useful
in defer contexts. For example:

```go
func checkDefer() (err error) {
	defer Error.WrapP(&err)

	fh, err := os.Open("secret_stash")
	if err != nil {
		return nil, err
	}
	return fh.Close()
}
```

### Groups

[Groups][Group] allow one to collect a set of errors. For example:

```go
func tonsOfErrors() error {
	var group errs.Group
	for _, work := range someWork {
		group.Add(maybeErrors(work))
	}
	return group.Err()
}
```

Some things to note:

- The [Add][GroupAdd] method only adds to the group if the passed in error is non-nil.
- The [Err][GroupErr] method returns an error only if non-nil errors have been added, and
  additionally returns just the error if only one error was added. Thus, we always
  have that if you only call `group.Add(err)`, then `group.Err() == err`.

The returned error will format itself similarly:

```go
func groupFormat() {
	var group errs.Group
	group.Add(errs.New("first"))
	group.Add(errs.New("second"))
	err := group.Err()

	fmt.Printf("%v\n", err)
	fmt.Println()
	fmt.Printf("%+v\n", err)

	// output:
	// first; second
	//
	// group:
	// --- first
	//     ... stack trace
	// --- second
	//     ... stack trace
}
```

### Contributing

errs is released under an MIT License. If you want to contribute, be sure to
add yourself to the list in AUTHORS.

[New]: https://godoc.org/github.com/zeebo/errs#New
[Wrap]: https://godoc.org/github.com/zeebo/errs#Wrap
[WrapP]: https://godoc.org/github.com/zeebo/errs#WrapP
[Class]: https://godoc.org/github.com/zeebo/errs#Class
[ClassNew]: https://godoc.org/github.com/zeebo/errs#Class.New
[ClassWrap]: https://godoc.org/github.com/zeebo/errs#Class.Wrap
[Unwrap]: https://godoc.org/github.com/zeebo/errs#Unwrap
[Classes]: https://godoc.org/github.com/zeebo/errs#Classes
[Group]: https://godoc.org/github.com/zeebo/errs#Group
[GroupAdd]: https://godoc.org/github.com/zeebo/errs#Group.Add
[GroupErr]: https://godoc.org/github.com/zeebo/errs#Group.Err
