Merge pull request #27 from mdeggies/add-circleci

Add CircleCI & remove travis
diff --git a/README.md b/README.md
index ead5830..074a749 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
 
 [travis]: https://travis-ci.org/hashicorp/go-multierror
-[godocs]: https://godoc.org/github.com/hashicorp/go-multierror
+[godocs]: https://pkg.go.dev/github.com/hashicorp/go-multierror
 
 `go-multierror` is a package for Go that provides a mechanism for
 representing a list of `error` values as a single `error`.
@@ -14,16 +14,17 @@
 list and access the errors. If the caller doesn't know, the error
 formats to a nice human-readable format.
 
-`go-multierror` implements the
-[errwrap](https://github.com/hashicorp/errwrap) interface so that it can
-be used with that library, as well.
+`go-multierror` is fully compatible with the Go standard library
+[errors](https://golang.org/pkg/errors/) package, including the
+functions `As`, `Is`, and `Unwrap`. This provides a standardized approach
+for introspecting on error values.
 
 ## Installation and Docs
 
 Install using `go get github.com/hashicorp/go-multierror`.
 
 Full documentation is available at
-http://godoc.org/github.com/hashicorp/go-multierror
+https://pkg.go.dev/github.com/hashicorp/go-multierror
 
 ## Usage
 
@@ -81,6 +82,39 @@
 }
 ```
 
+You can also use the standard [`errors.Unwrap`](https://golang.org/pkg/errors/#Unwrap)
+function. This will continue to unwrap into subsequent errors until none exist.
+
+**Extracting an error**
+
+The standard library [`errors.As`](https://golang.org/pkg/errors/#As)
+function can be used directly with a multierror to extract a specific error:
+
+```go
+// Assume err is a multierror value
+err := somefunc()
+
+// We want to know if "err" has a "RichErrorType" in it and extract it.
+var errRich RichErrorType
+if errors.As(err, &errRich) {
+	// It has it, and now errRich is populated.
+}
+```
+
+**Checking for an exact error value**
+
+Some errors are returned as exact errors such as the [`ErrNotExist`](https://golang.org/pkg/os/#pkg-variables)
+error in the `os` package. You can check if this error is present by using
+the standard [`errors.Is`](https://golang.org/pkg/errors/#Is) function.
+
+```go
+// Assume err is a multierror value
+err := somefunc()
+if errors.Is(err, os.ErrNotExist) {
+	// err contains os.ErrNotExist
+}
+```
+
 **Returning a multierror only if there are errors**
 
 If you build a `multierror.Error`, you can use the `ErrorOrNil` function
diff --git a/append.go b/append.go
index 775b6e7..3e2589b 100644
--- a/append.go
+++ b/append.go
@@ -6,6 +6,8 @@
 // If err is not a multierror.Error, then it will be turned into
 // one. If any of the errs are multierr.Error, they will be flattened
 // one level into err.
+// Any nil errors within errs will be ignored. If err is nil, a new
+// *Error will be returned.
 func Append(err error, errs ...error) *Error {
 	switch err := err.(type) {
 	case *Error:
diff --git a/go.mod b/go.mod
index 2534331..0afe8e6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
 module github.com/hashicorp/go-multierror
 
+go 1.14
+
 require github.com/hashicorp/errwrap v1.0.0
diff --git a/group.go b/group.go
new file mode 100644
index 0000000..9c29efb
--- /dev/null
+++ b/group.go
@@ -0,0 +1,38 @@
+package multierror
+
+import "sync"
+
+// Group is a collection of goroutines which return errors that need to be
+// coalesced.
+type Group struct {
+	mutex sync.Mutex
+	err   *Error
+	wg    sync.WaitGroup
+}
+
+// Go calls the given function in a new goroutine.
+//
+// If the function returns an error it is added to the group multierror which
+// is returned by Wait.
+func (g *Group) Go(f func() error) {
+	g.wg.Add(1)
+
+	go func() {
+		defer g.wg.Done()
+
+		if err := f(); err != nil {
+			g.mutex.Lock()
+			g.err = Append(g.err, err)
+			g.mutex.Unlock()
+		}
+	}()
+}
+
+// Wait blocks until all function calls from the Go method have returned, then
+// returns the multierror.
+func (g *Group) Wait() *Error {
+	g.wg.Wait()
+	g.mutex.Lock()
+	defer g.mutex.Unlock()
+	return g.err
+}
diff --git a/group_test.go b/group_test.go
new file mode 100644
index 0000000..9d472fd
--- /dev/null
+++ b/group_test.go
@@ -0,0 +1,44 @@
+package multierror
+
+import (
+	"errors"
+	"strings"
+	"testing"
+)
+
+func TestGroup(t *testing.T) {
+	err1 := errors.New("group_test: 1")
+	err2 := errors.New("group_test: 2")
+
+	cases := []struct {
+		errs      []error
+		nilResult bool
+	}{
+		{errs: []error{}, nilResult: true},
+		{errs: []error{nil}, nilResult: true},
+		{errs: []error{err1}},
+		{errs: []error{err1, nil}},
+		{errs: []error{err1, nil, err2}},
+	}
+
+	for _, tc := range cases {
+		var g Group
+
+		for _, err := range tc.errs {
+			err := err
+			g.Go(func() error { return err })
+
+		}
+
+		gErr := g.Wait()
+		if gErr != nil {
+			for i := range tc.errs {
+				if tc.errs[i] != nil && !strings.Contains(gErr.Error(), tc.errs[i].Error()) {
+					t.Fatalf("expected error to contain %q, actual: %v", tc.errs[i].Error(), gErr)
+				}
+			}
+		} else if !tc.nilResult {
+			t.Fatalf("Group.Wait() should not have returned nil for errs: %v", tc.errs)
+		}
+	}
+}
diff --git a/multierror.go b/multierror.go
index 89b1422..d05dd92 100644
--- a/multierror.go
+++ b/multierror.go
@@ -1,6 +1,7 @@
 package multierror
 
 import (
+	"errors"
 	"fmt"
 )
 
@@ -49,3 +50,69 @@
 func (e *Error) WrappedErrors() []error {
 	return e.Errors
 }
+
+// Unwrap returns an error from Error (or nil if there are no errors).
+// This error returned will further support Unwrap to get the next error,
+// etc. The order will match the order of Errors in the multierror.Error
+// at the time of calling.
+//
+// The resulting error supports errors.As/Is/Unwrap so you can continue
+// to use the stdlib errors package to introspect further.
+//
+// This will perform a shallow copy of the errors slice. Any errors appended
+// to this error after calling Unwrap will not be available until a new
+// Unwrap is called on the multierror.Error.
+func (e *Error) Unwrap() error {
+	// If we have no errors then we do nothing
+	if e == nil || len(e.Errors) == 0 {
+		return nil
+	}
+
+	// If we have exactly one error, we can just return that directly.
+	if len(e.Errors) == 1 {
+		return e.Errors[0]
+	}
+
+	// Shallow copy the slice
+	errs := make([]error, len(e.Errors))
+	copy(errs, e.Errors)
+	return chain(errs)
+}
+
+// chain implements the interfaces necessary for errors.Is/As/Unwrap to
+// work in a deterministic way with multierror. A chain tracks a list of
+// errors while accounting for the current represented error. This lets
+// Is/As be meaningful.
+//
+// Unwrap returns the next error. In the cleanest form, Unwrap would return
+// the wrapped error here but we can't do that if we want to properly
+// get access to all the errors. Instead, users are recommended to use
+// Is/As to get the correct error type out.
+//
+// Precondition: []error is non-empty (len > 0)
+type chain []error
+
+// Error implements the error interface
+func (e chain) Error() string {
+	return e[0].Error()
+}
+
+// Unwrap implements errors.Unwrap by returning the next error in the
+// chain or nil if there are no more errors.
+func (e chain) Unwrap() error {
+	if len(e) == 1 {
+		return nil
+	}
+
+	return e[1:]
+}
+
+// As implements errors.As by attempting to map to the current value.
+func (e chain) As(target interface{}) bool {
+	return errors.As(e[0], target)
+}
+
+// Is implements errors.Is by comparing the current value directly.
+func (e chain) Is(target error) bool {
+	return errors.Is(e[0], target)
+}
diff --git a/multierror_test.go b/multierror_test.go
index 2949c3b..972c52d 100644
--- a/multierror_test.go
+++ b/multierror_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"errors"
+	"fmt"
 	"reflect"
 	"testing"
 )
@@ -69,3 +70,134 @@
 		t.Fatalf("bad: %s", multi.WrappedErrors())
 	}
 }
+
+func TestErrorUnwrap(t *testing.T) {
+	t.Run("with errors", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			errors.New("bar"),
+			errors.New("baz"),
+		}}
+
+		var current error = err
+		for i := 0; i < len(err.Errors); i++ {
+			current = errors.Unwrap(current)
+			if !errors.Is(current, err.Errors[i]) {
+				t.Fatal("should be next value")
+			}
+		}
+
+		if errors.Unwrap(current) != nil {
+			t.Fatal("should be nil at the end")
+		}
+	})
+
+	t.Run("with no errors", func(t *testing.T) {
+		err := &Error{Errors: nil}
+		if errors.Unwrap(err) != nil {
+			t.Fatal("should be nil")
+		}
+	})
+
+	t.Run("with nil multierror", func(t *testing.T) {
+		var err *Error
+		if errors.Unwrap(err) != nil {
+			t.Fatal("should be nil")
+		}
+	})
+}
+
+func TestErrorIs(t *testing.T) {
+	errBar := errors.New("bar")
+
+	t.Run("with errBar", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			errBar,
+			errors.New("baz"),
+		}}
+
+		if !errors.Is(err, errBar) {
+			t.Fatal("should be true")
+		}
+	})
+
+	t.Run("with errBar wrapped by fmt.Errorf", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			fmt.Errorf("errorf: %w", errBar),
+			errors.New("baz"),
+		}}
+
+		if !errors.Is(err, errBar) {
+			t.Fatal("should be true")
+		}
+	})
+
+	t.Run("without errBar", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			errors.New("baz"),
+		}}
+
+		if errors.Is(err, errBar) {
+			t.Fatal("should be false")
+		}
+	})
+}
+
+func TestErrorAs(t *testing.T) {
+	match := &nestedError{}
+
+	t.Run("with the value", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			match,
+			errors.New("baz"),
+		}}
+
+		var target *nestedError
+		if !errors.As(err, &target) {
+			t.Fatal("should be true")
+		}
+		if target == nil {
+			t.Fatal("target should not be nil")
+		}
+	})
+
+	t.Run("with the value wrapped by fmt.Errorf", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			fmt.Errorf("errorf: %w", match),
+			errors.New("baz"),
+		}}
+
+		var target *nestedError
+		if !errors.As(err, &target) {
+			t.Fatal("should be true")
+		}
+		if target == nil {
+			t.Fatal("target should not be nil")
+		}
+	})
+
+	t.Run("without the value", func(t *testing.T) {
+		err := &Error{Errors: []error{
+			errors.New("foo"),
+			errors.New("baz"),
+		}}
+
+		var target *nestedError
+		if errors.As(err, &target) {
+			t.Fatal("should be false")
+		}
+		if target != nil {
+			t.Fatal("target should be nil")
+		}
+	})
+}
+
+// nestedError implements error and is used for tests.
+type nestedError struct{}
+
+func (*nestedError) Error() string { return "" }