New Upstream Release - golang-github-gravitational-trace

Ready changes

Summary

Merged new upstream version: 1.2.1 (was: 1.1.15).

Resulting package

Built on 2023-08-01T02:19 (took 6m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-gravitational-trace-dev

Lintian Result

Diff

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..d42115a
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,40 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'go' ]
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v2
+      with:
+        languages: ${{ matrix.language }}
+
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v2
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v2
+      with:
+        category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml
new file mode 100644
index 0000000..5eb64ee
--- /dev/null
+++ b/.github/workflows/dependency-review.yaml
@@ -0,0 +1,10 @@
+name: Dependency Review
+
+on:
+  pull_request:
+
+jobs:
+  dependency-review:
+    uses: gravitational/shared-workflows/.github/workflows/dependency-review.yaml@main
+    permissions:
+      contents: read
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..f71bb7f
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,22 @@
+name: Test
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+permissions: {}
+
+jobs:
+  go-test:
+    name: Run Go tests
+    runs-on: ubuntu-22.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Setup Go
+        uses: actions/setup-go@v3
+        with:
+          go-version: '1.17'
+      - name: Test
+        run: go test -race ./...
diff --git a/README.md b/README.md
index 800ef07..e4fcd1b 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
 # Trace
 
 [![GoDoc](https://godoc.org/github.com/gravitational/trace?status.png)](https://godoc.org/github.com/gravitational/trace)
-
+![Test workflow](https://github.com/gravitational/trace/actions/workflows/test.yaml/badge.svg?branch=master)
 
 Package for error handling and error reporting
 
 Read more here:
 
-http://gravitational.com/blog/golang_error_handling/
+https://goteleport.com/blog/golang-error-handling/
 
 ### Capture file, line and function
 
diff --git a/debian/changelog b/debian/changelog
index 3d0be8c..29300ac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-gravitational-trace (1.2.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 01 Aug 2023 02:13:44 -0000
+
 golang-github-gravitational-trace (1.1.15-2) unstable; urgency=medium
 
   * Source only upload.
diff --git a/errors.go b/errors.go
index 1a80eee..4f241a3 100644
--- a/errors.go
+++ b/errors.go
@@ -55,6 +55,20 @@ func (e *NotFoundError) OrigError() error {
 	return e
 }
 
+// Is provides an equivalency check for NotFoundError to be used with errors.Is
+func (e *NotFoundError) Is(target error) bool {
+	if os.IsNotExist(target) {
+		return true
+	}
+
+	err, ok := Unwrap(target).(*NotFoundError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
+}
+
 // IsNotFound returns whether this error is of NotFoundError type
 func IsNotFound(err error) bool {
 	err = Unwrap(err)
@@ -81,9 +95,9 @@ type AlreadyExistsError struct {
 }
 
 // Error returns log friendly description of an error
-func (n *AlreadyExistsError) Error() string {
-	if n.Message != "" {
-		return n.Message
+func (e *AlreadyExistsError) Error() string {
+	if e.Message != "" {
+		return e.Message
 	}
 	return "object already exists"
 }
@@ -98,6 +112,16 @@ func (e *AlreadyExistsError) OrigError() error {
 	return e
 }
 
+// Is provides an equivalency check for AlreadyExistsError to be used with errors.Is
+func (e *AlreadyExistsError) Is(target error) bool {
+	err, ok := Unwrap(target).(*AlreadyExistsError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
+}
+
 // IsAlreadyExists returns whether this is error indicating that object
 // already exists
 func IsAlreadyExists(e error) bool {
@@ -136,6 +160,16 @@ func (b *BadParameterError) IsBadParameterError() bool {
 	return true
 }
 
+// Is provides an equivalency check for BadParameterError to be used with errors.Is
+func (b *BadParameterError) Is(target error) bool {
+	err, ok := Unwrap(target).(*BadParameterError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == b.Message
+}
+
 // IsBadParameter returns whether this error is of BadParameterType
 func IsBadParameter(e error) bool {
 	type bp interface {
@@ -173,6 +207,16 @@ func (e *NotImplementedError) IsNotImplementedError() bool {
 	return true
 }
 
+// Is provides an equivalency check for NotImplementedError to be used with errors.Is
+func (e *NotImplementedError) Is(target error) bool {
+	err, ok := Unwrap(target).(*NotImplementedError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
+}
+
 // IsNotImplemented returns whether this error is of NotImplementedError type
 func IsNotImplemented(e error) bool {
 	type ni interface {
@@ -213,6 +257,16 @@ func (e *CompareFailedError) IsCompareFailedError() bool {
 	return true
 }
 
+// Is provides an equivalency check for CompareFailedError to be used with errors.Is
+func (e *CompareFailedError) Is(target error) bool {
+	err, ok := Unwrap(target).(*CompareFailedError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
+}
+
 // IsCompareFailed detects if this error is of CompareFailed type
 func IsCompareFailed(e error) bool {
 	type cf interface {
@@ -252,6 +306,16 @@ func (e *AccessDeniedError) OrigError() error {
 	return e
 }
 
+// Is provides an equivalency check for AccessDeniedError to be used with errors.Is
+func (e *AccessDeniedError) Is(target error) bool {
+	err, ok := Unwrap(target).(*AccessDeniedError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
+}
+
 // IsAccessDenied detects if this error is of AccessDeniedError type
 func IsAccessDenied(err error) bool {
 	_, ok := Unwrap(err).(interface {
@@ -341,6 +405,16 @@ func (c *ConnectionProblemError) OrigError() error {
 	return c
 }
 
+// Is provides an equivalency check for ConnectionProblemError to be used with errors.Is
+func (c *ConnectionProblemError) Is(target error) bool {
+	err, ok := Unwrap(target).(*ConnectionProblemError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == c.Message && err.Err == c.Err
+}
+
 // IsConnectionProblem returns whether this error is of ConnectionProblemError
 func IsConnectionProblem(e error) bool {
 	type ad interface {
@@ -363,18 +437,28 @@ type LimitExceededError struct {
 }
 
 // Error is debug - friendly error message
-func (c *LimitExceededError) Error() string {
-	return c.Message
+func (e *LimitExceededError) Error() string {
+	return e.Message
 }
 
 // IsLimitExceededError indicates that this error is of ConnectionProblem
-func (c *LimitExceededError) IsLimitExceededError() bool {
+func (e *LimitExceededError) IsLimitExceededError() bool {
 	return true
 }
 
 // OrigError returns original error (in this case this is the error itself)
-func (c *LimitExceededError) OrigError() error {
-	return c
+func (e *LimitExceededError) OrigError() error {
+	return e
+}
+
+// Is provides an equivalency check for LimitExceededError to be used with errors.Is
+func (e *LimitExceededError) Is(target error) bool {
+	err, ok := Unwrap(target).(*LimitExceededError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == e.Message
 }
 
 // IsLimitExceeded detects if this error is of LimitExceededError
@@ -427,6 +511,16 @@ func (t *TrustError) OrigError() error {
 	return t
 }
 
+// Is provides an equivalency check for TrustError to be used with errors.Is
+func (t *TrustError) Is(target error) bool {
+	err, ok := Unwrap(target).(*TrustError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == t.Message && err.Err == t.Err
+}
+
 // IsTrustError returns if this is a trust error
 func IsTrustError(e error) bool {
 	type te interface {
@@ -462,6 +556,36 @@ func (o *OAuth2Error) IsOAuth2Error() bool {
 	return true
 }
 
+// Is provides an equivalency check for OAuth2Error to be used with errors.Is
+func (o *OAuth2Error) Is(target error) bool {
+	err, ok := Unwrap(target).(*OAuth2Error)
+	if !ok {
+		return false
+	}
+
+	if err.Message != o.Message ||
+		err.Code != o.Code ||
+		len(err.Query) != len(o.Query) {
+		return false
+	}
+
+	for k, v := range err.Query {
+		for k2, v2 := range o.Query {
+			if k != k2 && len(v) != len(v2) {
+				return false
+			}
+
+			for i := range v {
+				if v[i] != v2[i] {
+					return false
+				}
+			}
+		}
+	}
+
+	return true
+}
+
 // IsOAuth2 returns if this is a OAuth2-related error
 func IsOAuth2(e error) bool {
 	type oe interface {
@@ -516,6 +640,16 @@ func (c *RetryError) OrigError() error {
 	return c
 }
 
+// Is provides an equivalency check for RetryError to be used with errors.Is
+func (c *RetryError) Is(target error) bool {
+	err, ok := Unwrap(target).(*RetryError)
+	if !ok {
+		return false
+	}
+
+	return err.Message == c.Message && err.Err == c.Err
+}
+
 // IsRetryError returns whether this error is of ConnectionProblemError
 func IsRetryError(e error) bool {
 	type ad interface {
diff --git a/errors_test.go b/errors_test.go
new file mode 100644
index 0000000..588350b
--- /dev/null
+++ b/errors_test.go
@@ -0,0 +1,403 @@
+/*
+Copyright 2022 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package trace
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"net/url"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func Test_Is(t *testing.T) {
+	type testError struct {
+		err  error
+		name string
+	}
+
+	cases := map[error][]testError{
+		&NotFoundError{}: {
+			{
+				err:  NotFound(""),
+				name: "NotFound",
+			}, {
+				err:  os.ErrNotExist,
+				name: "os.ErrNotExist",
+			},
+		},
+		&BadParameterError{}: {
+			{
+				err:  BadParameter(""),
+				name: "BadParameter",
+			},
+		},
+		&RetryError{}: {
+			{
+				err:  Retry(nil, ""),
+				name: "Retry",
+			},
+		},
+		&OAuth2Error{}: {
+			{
+				err:  OAuth2("", "", url.Values{}),
+				name: "OAuth2",
+			},
+		},
+		&TrustError{}: {
+			{
+				err:  Trust(nil, ""),
+				name: "Trust",
+			},
+		},
+		&LimitExceededError{}: {
+			{
+				err:  LimitExceeded(""),
+				name: "LimitExceeded",
+			},
+		},
+		&ConnectionProblemError{}: {
+			{
+				err:  ConnectionProblem(nil, ""),
+				name: "ConnectionProblem",
+			},
+		},
+		&AccessDeniedError{}: {
+			{
+				err:  AccessDenied(""),
+				name: "AccessDenied",
+			},
+		},
+		&CompareFailedError{}: {
+			{
+				err:  CompareFailed(""),
+				name: "CompareFailed",
+			},
+		},
+		&NotImplementedError{}: {
+			{
+				err:  NotImplemented(""),
+				name: "NotImplemented",
+			},
+		},
+		&AlreadyExistsError{}: {
+			{
+				err:  AlreadyExists(""),
+				name: "AlreadyExists",
+			},
+		},
+	}
+
+	// none of the target errors should Is for these and many more...
+	not := []testError{
+		{
+			err:  nil,
+			name: "nil",
+		},
+		{
+			err:  NewAggregate(Wrap(AccessDenied("test"), AccessDenied("test"), ConnectionProblem(io.EOF, "fail"), Wrap(NotFound("test")))),
+			name: "Aggregate",
+		},
+		{
+			err:  errors.New("test"),
+			name: "test",
+		},
+		{
+			err:  io.EOF,
+			name: "io.EOF",
+		},
+		{
+			err:  os.ErrPermission,
+			name: "os.ErrPermission",
+		},
+		{
+			err:  context.Canceled,
+			name: "context.Canceled",
+		},
+	}
+
+	require.ErrorIs(t, &CompareFailedError{}, CompareFailed(""))
+
+	// for each target error in the case check if it Is
+	for k := range cases {
+		t.Run(fmt.Sprintf("%T", k), func(t *testing.T) {
+			for _, te := range not {
+				t.Run(fmt.Sprintf("is not %s", te.name), func(t *testing.T) {
+					require.NotErrorIs(t, k, te.err)
+					require.NotErrorIs(t, k, Wrap(te.err))
+
+				})
+			}
+
+			// check that the target error only Is for itself and not any other trace.Error
+			for kk, v := range cases {
+				for _, te := range v {
+					if k == kk { // iterating on the target error - require Is
+						t.Run(fmt.Sprintf("is %s", te.name), func(t *testing.T) {
+							require.ErrorIs(t, k, te.err)
+						})
+					} else { // iterating on another error - require not Is
+						t.Run(fmt.Sprintf("is not %s", te.name), func(t *testing.T) {
+							require.NotErrorIs(t, k, te.err)
+						})
+					}
+				}
+			}
+		})
+	}
+
+}
+
+func TestNotFoundError_Is(t *testing.T) {
+	errs := []error{
+		NotFound("one"),
+		NotFound("two"),
+		Wrap(NotFound("three")),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+		require.ErrorIs(t, errs[i], os.ErrNotExist)
+		require.NotErrorIs(t, os.ErrNotExist, errs[i])
+	}
+}
+
+func TestAlreadyExistsError_Is(t *testing.T) {
+	errs := []error{
+		AlreadyExists("one"),
+		AlreadyExists("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestBadParameterError_Is(t *testing.T) {
+	errs := []error{
+		BadParameter("one"),
+		BadParameter("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestIsNotImplementedError_Is(t *testing.T) {
+	errs := []error{
+		NotImplemented("one"),
+		NotImplemented("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestCompareFailedError_Is(t *testing.T) {
+	errs := []error{
+		CompareFailed("one"),
+		CompareFailed("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, errs[i], Wrap(errs[j]))
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestAccessDeniedError_Is(t *testing.T) {
+	errs := []error{
+		AccessDenied("one"),
+		AccessDenied("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestConnectionProblemError_Is(t *testing.T) {
+	errs := []error{
+		ConnectionProblem(io.EOF, "one"),
+		ConnectionProblem(os.ErrNotExist, "one"),
+		ConnectionProblem(io.EOF, "two"),
+		ConnectionProblem(os.ErrNotExist, "two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestLimitExceededError_Is(t *testing.T) {
+	errs := []error{
+		LimitExceeded("one"),
+		LimitExceeded("two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestTrustError_Is(t *testing.T) {
+	errs := []error{
+		Trust(io.EOF, "one"),
+		Trust(os.ErrNotExist, "one"),
+		Trust(io.EOF, "two"),
+		Trust(os.ErrNotExist, "two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestOauth2Error_Is(t *testing.T) {
+	errs := []error{
+		OAuth2("a", "one", nil),
+		OAuth2("b", "one", url.Values{}),
+		OAuth2("c", "one", url.Values{"test": []string{"test"}}),
+		OAuth2("d", "one", url.Values{"test": []string{"error"}}),
+		OAuth2("d", "one", url.Values{"test": []string{"test", "test"}}),
+		OAuth2("a", "two", nil),
+		OAuth2("b", "two", url.Values{}),
+		OAuth2("c", "two", url.Values{"test": []string{"test"}}),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
+
+func TestRetryError_Is(t *testing.T) {
+	errs := []error{
+		Retry(io.EOF, "one"),
+		Retry(os.ErrNotExist, "one"),
+		Retry(io.EOF, "two"),
+		Retry(os.ErrNotExist, "two"),
+	}
+
+	for i := range errs {
+		for j := range errs {
+			if i == j {
+				require.ErrorIs(t, errs[i], errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), errs[j])
+				require.ErrorIs(t, Wrap(errs[i]), Wrap(errs[j]))
+			} else {
+				require.NotErrorIs(t, errs[i], errs[j])
+				require.NotErrorIs(t, errs[j], errs[i])
+			}
+		}
+	}
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..8e86fbf
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,14 @@
+module github.com/gravitational/trace
+
+go 1.15
+
+require (
+	github.com/jonboulle/clockwork v0.2.2
+	github.com/sirupsen/logrus v1.8.1
+	github.com/stretchr/testify v1.7.0
+	golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8
+	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
+	google.golang.org/grpc v1.43.0
+)
+
+require golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..76b4d36
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,142 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
+github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8 h1:kACShD3qhmr/3rLmg1yXyt+N4HcwutKyPRB93s54TIU=
+golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
+google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/httplib.go b/httplib.go
index 2542dd2..8f85c0b 100644
--- a/httplib.go
+++ b/httplib.go
@@ -90,8 +90,8 @@ func replyJSON(w http.ResponseWriter, code int, err error) {
 	var out []byte
 	// wrap regular errors in order to achieve unification
 	// and provide structurally consistent responses
-	var obj interface{} = err
-	if _, ok := err.(*TraceErr); !ok {
+	obj, ok := err.(*TraceErr)
+	if !ok {
 		obj = &TraceErr{Err: err}
 	}
 	out, err = json.MarshalIndent(obj, "", "    ")
@@ -107,12 +107,11 @@ func unmarshalError(err error, responseBody []byte) error {
 	}
 	var raw RawTrace
 	if err2 := json.Unmarshal(responseBody, &raw); err2 != nil {
-		return err
+		return errorOnInvalidJSON(err, responseBody)
 	}
-	if len(raw.Traces) != 0 && len(raw.Err) != 0 {
-		err2 := json.Unmarshal(raw.Err, err)
-		if err2 != nil {
-			return err
+	if len(raw.Err) != 0 {
+		if err2 := json.Unmarshal(raw.Err, err); err2 != nil {
+			return errorOnInvalidJSON(err, responseBody)
 		}
 		return &TraceErr{
 			Traces:   raw.Traces,
@@ -122,6 +121,19 @@ func unmarshalError(err error, responseBody []byte) error {
 			Fields:   raw.Fields,
 		}
 	}
-	json.Unmarshal(responseBody, err)
+	if err2 := json.Unmarshal(responseBody, err); err2 != nil {
+		return errorOnInvalidJSON(err, responseBody)
+	}
 	return err
 }
+
+// errorOnInvalidJSON is used to construct a TraceErr with the
+// input error as Err and the responseBody as Messages.
+// This function is used when the responseBody is not valid
+// JSON or it contains an unexpected JSON.
+func errorOnInvalidJSON(err error, responseBody []byte) error {
+	return &TraceErr{
+		Err:      err,
+		Messages: []string{string(responseBody)},
+	}
+}
diff --git a/httplib_test.go b/httplib_test.go
index 0e163d7..56b2865 100644
--- a/httplib_test.go
+++ b/httplib_test.go
@@ -19,33 +19,107 @@ package trace
 import (
 	"errors"
 	"net/http/httptest"
+	"testing"
 
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/require"
 )
 
-type HttplibSuite struct{}
+func TestReplyJSON(t *testing.T) {
+	t.Parallel()
 
-var _ = Suite(&HttplibSuite{})
+	var expectedErrorResponse = `{
+		"error": {
+			"message": "test error"
+		}
+	}`
 
-var (
-	errCode               = 400
-	errText               = "test error"
-	expectedErrorResponse = "" +
-		"{\n" +
-		"    \"error\": {\n" +
-		"        \"message\": \"" + errText + "\"\n" +
-		"    }\n" +
-		"}"
-)
+	tests := []struct {
+		desc string
+		err  error
+	}{
+		{
+			desc: "plain error",
+			err:  errors.New("test error"),
+		},
+		{
+			desc: "trace error",
+			err:  &TraceErr{Err: errors.New("test error")},
+		},
+		{
+			desc: "trace error with stacktrace",
+			err:  &TraceErr{Err: errors.New("test error"), Traces: Traces{{Path: "A", Func: "B", Line: 1}}},
+		},
+	}
 
-func (s *HttplibSuite) TestRegularErrorResponseJSON(c *C) {
-	recorder := httptest.NewRecorder()
-	replyJSON(recorder, errCode, errors.New(errText))
-	c.Assert(recorder.Body.String(), Equals, expectedErrorResponse)
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			recorder := httptest.NewRecorder()
+			const errCode = 400
+			replyJSON(recorder, errCode, tc.err)
+			require.JSONEq(t, expectedErrorResponse, recorder.Body.String())
+		})
+	}
 }
 
-func (s *HttplibSuite) TestTraceErrorResponseJSON(c *C) {
-	recorder := httptest.NewRecorder()
-	replyJSON(recorder, errCode, &TraceErr{Err: errors.New(errText)})
-	c.Assert(recorder.Body.String(), Equals, expectedErrorResponse)
+func TestUnmarshalError(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		desc          string
+		inputErr      error
+		inputResponse string
+		assertErr     func(error) bool
+		expectedMsg   string
+	}{
+		{
+			desc:          "unmarshal not found error",
+			inputErr:      &NotFoundError{},
+			inputResponse: `{"error": {"message": "ABC"}}`,
+			assertErr:     IsNotFound,
+			expectedMsg:   "ABC",
+		},
+		{
+			desc:          "unmarshal access denied error",
+			inputErr:      &AccessDeniedError{},
+			inputResponse: `{"error": {"message": "ABC"}}`,
+			assertErr:     IsAccessDenied,
+			expectedMsg:   "ABC",
+		},
+		{
+			desc:          "unmarshal error without error JSON key",
+			inputErr:      &AccessDeniedError{},
+			inputResponse: `{"message": "ABC"}`,
+			assertErr:     IsAccessDenied,
+			expectedMsg:   "ABC",
+		},
+		{
+			desc:          "unmarshal invalid error",
+			inputErr:      &AccessDeniedError{},
+			inputResponse: `{"error": "message ABC"}`,
+			assertErr:     IsAccessDenied,
+			expectedMsg:   "{\"error\": \"message ABC\"}\n\taccess denied",
+		},
+		{
+			desc:          "unmarshal invalid error without error JSON key",
+			inputErr:      &AccessDeniedError{},
+			inputResponse: `["error message ABC"]`,
+			assertErr:     IsAccessDenied,
+			expectedMsg:   "[\"error message ABC\"]\n\taccess denied",
+		},
+		{
+			desc:          "unmarshal error with non-JSON body",
+			inputErr:      &AccessDeniedError{},
+			inputResponse: "error message ABC",
+			assertErr:     IsAccessDenied,
+			expectedMsg:   "error message ABC\n\taccess denied",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			readErr := unmarshalError(tc.inputErr, []byte(tc.inputResponse))
+			require.True(t, tc.assertErr(readErr))
+			require.EqualError(t, readErr, tc.expectedMsg)
+		})
+	}
 }
diff --git a/trace.go b/trace.go
index f40caa7..15b1313 100644
--- a/trace.go
+++ b/trace.go
@@ -21,6 +21,7 @@ package trace
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"html/template"
 	"strings"
@@ -60,7 +61,7 @@ func Wrap(err error, args ...interface{}) Error {
 		trace = newTrace(err, 2)
 	}
 	if len(args) > 0 {
-		trace = trace.AddUserMessage(args[0], args[1:]...)
+		trace = WithUserMessage(trace, args[0], args[1:]...)
 	}
 	return trace
 }
@@ -138,9 +139,28 @@ func GetFields(err error) map[string]interface{} {
 	if err == nil {
 		return map[string]interface{}{}
 	}
+
+	// for proxyError combine top-level and nested fields.
+	if proxy, ok := err.(proxyError); ok {
+		fields := map[string]interface{}{}
+
+		// nested
+		for field, value := range GetFields(proxy.Err) {
+			fields[field] = value
+		}
+
+		// top-level
+		for field, value := range proxy.GetFields() {
+			fields[field] = value
+		}
+
+		return fields
+	}
+
 	if wrap, ok := err.(Error); ok {
 		return wrap.GetFields()
 	}
+
 	return map[string]interface{}{}
 }
 
@@ -152,8 +172,7 @@ func WrapWithMessage(err error, message interface{}, args ...interface{}) Error
 	} else {
 		trace = newTrace(err, 2)
 	}
-	trace.AddUserMessage(message, args...)
-	return trace
+	return WithUserMessage(trace, message, args...)
 }
 
 // Errorf is similar to fmt.Errorf except that it captures
@@ -184,13 +203,13 @@ type Traces = internal.Traces
 type Trace = internal.Trace
 
 // MarshalJSON marshals this error as JSON-encoded payload
-func (r *TraceErr) MarshalJSON() ([]byte, error) {
-	if r == nil {
+func (e *TraceErr) MarshalJSON() ([]byte, error) {
+	if e == nil {
 		return nil, nil
 	}
 	type marshalableError TraceErr
-	err := marshalableError(*r)
-	err.Err = &RawTrace{Message: r.Err.Error()}
+	err := marshalableError(*e)
+	err.Err = &RawTrace{Message: e.Err.Error()}
 	return json.Marshal(err)
 }
 
@@ -200,7 +219,7 @@ type TraceErr struct {
 	// Err is the underlying error that TraceErr wraps
 	Err error `json:"error"`
 	// Traces is a slice of stack trace entries for the error
-	Traces `json:"traces,omitempty"`
+	Traces `json:"-"`
 	// Message is an optional message that can be wrapped with the original error.
 	//
 	// This field is obsolete, replaced by messages list below.
@@ -234,31 +253,29 @@ type RawTrace struct {
 	Fields map[string]interface{} `json:"fields,omitempty"`
 }
 
-// AddUserMessage adds user-friendly message describing the error nature
-func (e *TraceErr) AddUserMessage(formatArg interface{}, rest ...interface{}) *TraceErr {
-	newMessage := fmt.Sprintf(fmt.Sprintf("%v", formatArg), rest...)
-	e.Messages = append(e.Messages, newMessage)
-	return e
-}
+func (e *TraceErr) Clone() *TraceErr {
+	if e == nil {
+		return nil
+	}
 
-// AddFields adds the given map of fields to the error being reported
-func (e *TraceErr) AddFields(fields map[string]interface{}) *TraceErr {
-	if e.Fields == nil {
-		e.Fields = make(map[string]interface{}, len(fields))
+	tr := &TraceErr{
+		Err:     e.Err,
+		Traces:  e.Traces,
+		Message: e.Message,
 	}
-	for k, v := range fields {
-		e.Fields[k] = v
+
+	if e.Messages != nil {
+		tr.Messages = append([]string{}, e.Messages...)
 	}
-	return e
-}
 
-// AddField adds a single field to the error wrapper as context for the error
-func (e *TraceErr) AddField(k string, v interface{}) *TraceErr {
-	if e.Fields == nil {
-		e.Fields = make(map[string]interface{}, 1)
+	if e.Fields != nil {
+		tr.Fields = make(map[string]interface{}, len(e.Fields))
+		for k, v := range e.Fields {
+			tr.Fields[k] = v
+		}
 	}
-	e.Fields[k] = v
-	return e
+
+	return tr
 }
 
 // UserMessage returns user-friendly error message
@@ -336,7 +353,7 @@ func (e *TraceErr) OrigError() error {
 }
 
 // GoString formats this trace object for use with
-// with the "%#v" format string
+// the "%#v" format string
 func (e *TraceErr) GoString() string {
 	return e.DebugReport()
 }
@@ -355,21 +372,48 @@ type Error interface {
 	DebugReporter
 	UserMessager
 
-	// AddMessage adds formatted user-facing message
-	// to the error, depends on the implementation,
-	// usually works as fmt.Sprintf(formatArg, rest...)
-	// but implementations can choose another way, e.g. treat
-	// arguments as structured args
-	AddUserMessage(formatArg interface{}, rest ...interface{}) *TraceErr
+	// GetFields returns any fields that have been added to the error
+	GetFields() map[string]interface{}
+
+	// Clone returns a copy of the current Error.
+	Clone() *TraceErr
+}
 
-	// AddField adds additional field information to the error
-	AddField(key string, value interface{}) *TraceErr
+// WithUserMessage adds formatted user-facing message
+// to the error, depends on the implementation,
+// usually works as fmt.Sprintf(formatArg, rest...)
+// but implementations can choose another way, e.g. treat
+// arguments as structured args.
+func WithUserMessage(err Error, formatArg interface{}, rest ...interface{}) *TraceErr {
+	errCopy := err.Clone()
 
-	// AddFields adds a map of additional fields to the error
-	AddFields(fields map[string]interface{}) *TraceErr
+	newMessage := fmt.Sprintf(fmt.Sprintf("%v", formatArg), rest...)
+	errCopy.Messages = append(errCopy.Messages, newMessage)
+	return errCopy
+}
 
-	// GetFields returns any fields that have been added to the error
-	GetFields() map[string]interface{}
+// WithField adds additional field information to the error.
+func WithField(err Error, key string, value interface{}) *TraceErr {
+	errCopy := err.Clone()
+
+	if errCopy.Fields == nil {
+		errCopy.Fields = make(map[string]interface{}, 1)
+	}
+	errCopy.Fields[key] = value
+	return errCopy
+}
+
+// WithFields adds a map of additional fields to the error
+func WithFields(err Error, fields map[string]interface{}) *TraceErr {
+	errCopy := err.Clone()
+
+	if errCopy.Fields == nil {
+		errCopy.Fields = make(map[string]interface{}, len(fields))
+	}
+	for k, v := range fields {
+		errCopy.Fields[k] = v
+	}
+	return errCopy
 }
 
 // NewAggregate creates a new aggregate instance from the specified
@@ -434,6 +478,28 @@ func (r aggregate) Error() string {
 	return output
 }
 
+// Is implements the `Is` interface, by iterating through each error in the
+// aggregate and invoking `errors.Is`.
+func (r aggregate) Is(t error) bool {
+	for _, err := range r {
+		if errors.Is(err, t) {
+			return true
+		}
+	}
+	return false
+}
+
+// As implements the `As` interface, by iterating through each error in the
+// aggregate and invoking `errors.As`.
+func (r aggregate) As(t interface{}) bool {
+	for _, err := range r {
+		if errors.As(err, t) {
+			return true
+		}
+	}
+	return false
+}
+
 // Errors obtains the list of errors this aggregate combines
 func (r aggregate) Errors() []error {
 	return []error(r)
@@ -504,8 +570,9 @@ type errorReport struct {
 	Caught string
 }
 
-var reportTemplate = template.Must(template.New("debugReport").Parse(reportTemplateText))
-var reportTemplateText = `
+var (
+	reportTemplate     = template.Must(template.New("debugReport").Parse(reportTemplateText))
+	reportTemplateText = `
 ERROR REPORT:
 Original Error: {{.OrigErrType}} {{.OrigErrMessage}}
 {{if .Fields}}Fields:
@@ -516,3 +583,4 @@ Original Error: {{.OrigErrType}} {{.OrigErrMessage}}
 {{.Caught}}
 User Message: {{.UserMessage}}
 {{else}}User Message: {{.UserMessage}}{{end}}`
+)
diff --git a/trace_test.go b/trace_test.go
index a5b262a..4ddf073 100644
--- a/trace_test.go
+++ b/trace_test.go
@@ -18,114 +18,166 @@ package trace
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"fmt"
 	"io"
 	"net/http"
 	"os"
 	"strings"
+	"sync"
 	"testing"
 
 	log "github.com/sirupsen/logrus"
-	"golang.org/x/net/context"
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
 )
 
-func TestTrace(t *testing.T) { TestingT(t) }
-
-type TraceSuite struct{}
+func TestTrace(t *testing.T) {
+	suite.Run(t, new(TraceSuite))
+}
 
-var _ = Suite(&TraceSuite{})
+type TraceSuite struct {
+	suite.Suite
+}
 
-func (s *TraceSuite) TestEmpty(c *C) {
-	c.Assert(DebugReport(nil), Equals, "")
-	c.Assert(UserMessage(nil), Equals, "")
-	c.Assert(UserMessageWithFields(nil), Equals, "")
-	c.Assert(GetFields(nil), DeepEquals, map[string]interface{}{})
+func (s *TraceSuite) TestEmpty() {
+	s.Equal("", DebugReport(nil))
+	s.Equal("", UserMessage(nil))
+	s.Equal("", UserMessageWithFields(nil))
+	s.Equal(map[string]interface{}{}, GetFields(nil))
 }
 
-func (s *TraceSuite) TestWrap(c *C) {
+func (s *TraceSuite) TestWrap() {
 	testErr := &testError{Param: "param"}
 	err := Wrap(Wrap(testErr))
 
-	c.Assert(line(DebugReport(err)), Matches, ".*trace_test.go.*")
-	c.Assert(line(DebugReport(err)), Not(Matches), ".*trace.go.*")
-	c.Assert(line(UserMessage(err)), Not(Matches), ".*trace_test.go.*")
-	c.Assert(line(UserMessage(err)), Matches, ".*param.*")
+	s.Regexp(".*trace_test.go.*", line(DebugReport(err)))
+	s.NotRegexp(".*trace.go.*", line(DebugReport(err)))
+	s.NotRegexp(".*trace_test.go.*", line(UserMessage(err)))
+	s.Regexp(".*param.*", line(UserMessage(err)))
 }
 
-func (s *TraceSuite) TestOrigError(c *C) {
+func (s *TraceSuite) TestOrigError() {
 	testErr := fmt.Errorf("some error")
 	err := Wrap(Wrap(testErr))
-	c.Assert(err.OrigError(), Equals, testErr)
+
+	s.Equal(testErr, err.OrigError())
 }
 
-func (s *TraceSuite) TestIsEOF(c *C) {
-	c.Assert(IsEOF(io.EOF), Equals, true)
-	c.Assert(IsEOF(Wrap(io.EOF)), Equals, true)
+func (s *TraceSuite) TestIsEOF() {
+	s.True(IsEOF(io.EOF))
+	s.True(IsEOF(Wrap(io.EOF)))
 }
 
-func (s *TraceSuite) TestWrapUserMessage(c *C) {
+func (s *TraceSuite) TestWrapUserMessage() {
 	testErr := fmt.Errorf("description")
 
 	err := Wrap(testErr, "user message")
-	c.Assert(line(DebugReport(err)), Matches, "*.trace_test.go.*")
-	c.Assert(line(DebugReport(err)), Not(Matches), "*.trace.go.*")
-	c.Assert(line(UserMessage(err)), Equals, "user message\tdescription")
+	s.Regexp(".*trace_test.go.*", line(DebugReport(err)))
+	s.NotRegexp(".*trace.go.*", line(DebugReport(err)))
+	s.Equal("user message\tdescription", line(UserMessage(err)))
 
 	err = Wrap(err, "user message 2")
-	c.Assert(line(UserMessage(err)), Equals, "user message 2\tuser message\t\tdescription")
+	s.Equal("user message 2\tuser message\t\tdescription", line(UserMessage(err)))
 }
 
-func (s *TraceSuite) TestWrapWithMessage(c *C) {
+func (s *TraceSuite) TestWrapWithMessage() {
 	testErr := fmt.Errorf("description")
 	err := WrapWithMessage(testErr, "user message")
-	c.Assert(line(UserMessage(err)), Equals, "user message\tdescription")
-	c.Assert(line(DebugReport(err)), Matches, "*.trace_test.go.*")
-	c.Assert(line(DebugReport(err)), Not(Matches), "*.trace.go.*")
+	s.Equal("user message\tdescription", line(UserMessage(err)))
+	s.Regexp(".*trace_test.go.*", line(DebugReport(err)))
+	s.NotRegexp(".*trace.go.*", line(DebugReport(err)))
 }
 
-func (s *TraceSuite) TestUserMessageWithFields(c *C) {
+func (s *TraceSuite) TestUserMessageWithFields() {
 	testErr := fmt.Errorf("description")
-	c.Assert(UserMessageWithFields(testErr), Equals, testErr.Error())
+	s.Equal(testErr.Error(), UserMessageWithFields(testErr))
 
 	err := Wrap(testErr, "user message")
-	c.Assert(line(UserMessageWithFields(err)), Equals, "user message\tdescription")
+	s.Equal("user message\tdescription", line(UserMessageWithFields(err)))
 
-	err.AddField("test_key", "test_value")
-	c.Assert(line(UserMessageWithFields(err)), Equals, "test_key=\"test_value\" user message\tdescription")
+	err = WithField(err, "test_key", "test_value")
+	s.Equal("test_key=\"test_value\" user message\tdescription", line(UserMessageWithFields(err)))
 }
 
-func (s *TraceSuite) TestGetFields(c *C) {
+func (s *TraceSuite) TestGetFields() {
 	testErr := fmt.Errorf("description")
-	c.Assert(GetFields(testErr), DeepEquals, map[string]interface{}{})
+	s.Equal(map[string]interface{}{}, GetFields(testErr))
 
 	fields := map[string]interface{}{
 		"test_key": "test_value",
 	}
-	err := Wrap(testErr).AddFields(fields)
-	c.Assert(GetFields(err), DeepEquals, fields)
+	err := WithFields(Wrap(testErr), fields)
+	s.Equal(fields, GetFields(err))
+
+	// ensure that you can get fields from a proxyError
+	e := roundtripError(err)
+	s.Equal(fields, GetFields(e))
+}
+
+func roundtripError(err error) error {
+	w := newTestWriter()
+	WriteError(w, err)
+
+	outErr := ReadError(w.StatusCode, w.Body)
+	return outErr
 }
 
-func (s *TraceSuite) TestWrapNil(c *C) {
+func (s *TraceSuite) TestWrapNil() {
 	err1 := Wrap(nil, "message: %v", "extra")
-	c.Assert(err1, IsNil)
+	s.Nil(err1)
 
 	var err2 error
 	err2 = nil
 
 	err3 := Wrap(err2)
-	c.Assert(err3, IsNil)
+	s.Nil(err3)
 
 	err4 := Wrap(err3)
-	c.Assert(err4, IsNil)
+	s.Nil(err4)
+}
+
+func TestRaceErrorWrap(t *testing.T) {
+	baseErr := BadParameter("foo")
+
+	iters := 100_000
+
+	wg := sync.WaitGroup{}
+	wg.Add(3)
+
+	// trace.Wrap with format arguments
+	go func() {
+		for i := 0; i < iters; i++ {
+			_ = Wrap(baseErr, "foo bar %q", "baz")
+		}
+		wg.Done()
+	}()
+
+	// trace.WrapWithMessage
+	go func() {
+		for i := 0; i < iters; i++ {
+			_ = WrapWithMessage(baseErr, "foo bar %q", "baz")
+		}
+		wg.Done()
+	}()
+
+	// plain Error() call
+	go func() {
+		for i := 0; i < iters; i++ {
+			_ = baseErr.Error()
+		}
+		wg.Done()
+	}()
+
+	wg.Wait()
 }
 
-func (s *TraceSuite) TestWrapStdlibErrors(c *C) {
-	c.Assert(IsNotFound(os.ErrNotExist), Equals, true)
+func (s *TraceSuite) TestWrapStdlibErrors() {
+	s.True(IsNotFound(os.ErrNotExist))
 }
 
-func (s *TraceSuite) TestLogFormatter(c *C) {
+func (s *TraceSuite) TestLogFormatter() {
 	for _, f := range []log.Formatter{&TextFormatter{}, &JSONFormatter{}} {
 		log.SetFormatter(f)
 
@@ -133,12 +185,12 @@ func (s *TraceSuite) TestLogFormatter(c *C) {
 		var buf bytes.Buffer
 		log.SetOutput(&buf)
 		log.Infof("hello")
-		c.Assert(line(buf.String()), Matches, ".*trace_test.go.*")
+		s.Regexp(".*trace_test.go.*", line(buf.String()))
 
 		// check case with embedded Infof
 		buf.Reset()
 		log.WithFields(log.Fields{"a": "b"}).Infof("hello")
-		c.Assert(line(buf.String()), Matches, ".*trace_test.go.*")
+		s.Regexp(".*trace_test.go.*", line(buf.String()))
 	}
 }
 
@@ -148,7 +200,7 @@ func (p panicker) String() string {
 	panic(p)
 }
 
-func (s *TraceSuite) TestTextFormatter(c *C) {
+func (s *TraceSuite) TestTextFormatter() {
 	padding := 6
 	f := &TextFormatter{
 		DisableTimestamp: true,
@@ -241,15 +293,14 @@ func (s *TraceSuite) TestTextFormatter(c *C) {
 	}
 
 	for i, tc := range testCases {
-		comment := Commentf("test case %v %v, expected match: %v", i+1, tc.comment, tc.match)
 		buf := &bytes.Buffer{}
 		log.SetOutput(buf)
 		tc.log()
-		c.Assert(line(buf.String()), Matches, tc.match, comment)
+		s.Regexp(tc.match, line(buf.String()), "test case %v %v, expected match: %v", i+1, tc.comment, tc.match)
 	}
 }
 
-func (s *TraceSuite) TestTextFormatterWithColors(c *C) {
+func (s *TraceSuite) TestTextFormatterWithColors() {
 	padding := 6
 	f := &TextFormatter{
 		DisableTimestamp: true,
@@ -313,16 +364,15 @@ func (s *TraceSuite) TestTextFormatterWithColors(c *C) {
 	}
 
 	for i, tc := range testCases {
-		comment := Commentf("test case %v %v, expected match: %v", i+1, tc.comment, tc.match)
 		buf := &bytes.Buffer{}
 		log.SetOutput(buf)
 		log.SetLevel(log.DebugLevel)
 		tc.log()
-		c.Assert(line(buf.String()), Matches, tc.match, comment)
+		s.Regexpf(tc.match, line(buf.String()), "test case %v %v, expected match: %v", i+1, tc.comment, tc.match)
 	}
 }
 
-func (s *TraceSuite) TestGenericErrors(c *C) {
+func (s *TraceSuite) TestGenericErrors() {
 	testCases := []struct {
 		Err        Error
 		Predicate  func(error) bool
@@ -380,109 +430,112 @@ func (s *TraceSuite) TestGenericErrors(c *C) {
 	}
 
 	for _, testCase := range testCases {
-		comment := Commentf(testCase.comment)
 		SetDebug(true)
 		err := testCase.Err
 
 		var traceErr *TraceErr
 		var ok bool
 		if traceErr, ok = err.(*TraceErr); !ok {
-			c.Fatal("Expected error to be of type *TraceErr")
+			s.Fail("Expected error to be of type *TraceErr")
 		}
-		c.Assert(len(traceErr.Traces), Not(Equals), 0, comment)
-		c.Assert(line(DebugReport(err)), Matches, "*.trace_test.go.*", comment)
-		c.Assert(line(DebugReport(err)), Not(Matches), "*.errors.go.*", comment)
-		c.Assert(line(DebugReport(err)), Not(Matches), "*.trace.go.*", comment)
-		c.Assert(testCase.Predicate(err), Equals, true, comment)
+
+		s.NotEmpty(traceErr.Traces, testCase.comment)
+		s.Regexp(".*.trace_test\\.go.*", line(DebugReport(err)), testCase.comment)
+		s.NotRegexp(".*.errors\\.go.*", line(DebugReport(err)), testCase.comment)
+		s.NotRegexp(".*.trace\\.go.*", line(DebugReport(err)), testCase.comment)
+		s.True(testCase.Predicate(err), testCase.comment)
 
 		w := newTestWriter()
 		WriteError(w, err)
 
 		outErr := ReadError(w.StatusCode, w.Body)
 		if _, ok := outErr.(proxyError); !ok {
-			c.Fatal("Expected error to be of type proxyError")
+			s.Fail("Expected error to be of type proxyError")
 		}
-		c.Assert(testCase.Predicate(outErr), Equals, true, comment)
+		s.True(testCase.Predicate(outErr), testCase.comment)
 
 		SetDebug(false)
 		w = newTestWriter()
 		WriteError(w, err)
 		outErr = ReadError(w.StatusCode, w.Body)
-		c.Assert(testCase.Predicate(outErr), Equals, true, comment)
+		s.True(testCase.Predicate(outErr), testCase.comment)
 	}
 }
 
 // Make sure we write some output produced by standard errors
-func (s *TraceSuite) TestWriteExternalErrors(c *C) {
+func (s *TraceSuite) TestWriteExternalErrors() {
 	err := Wrap(fmt.Errorf("snap!"))
 
 	SetDebug(true)
 	w := newTestWriter()
 	WriteError(w, err)
 	extErr := ReadError(w.StatusCode, w.Body)
-	c.Assert(w.StatusCode, Equals, http.StatusInternalServerError)
-	c.Assert(strings.Replace(string(w.Body), "\n", "", -1), Matches, "*.snap.*")
-	c.Assert(err.Error(), Equals, extErr.Error())
+	s.Equal(http.StatusInternalServerError, w.StatusCode)
+	s.Regexp(".*.snap.*", strings.Replace(string(w.Body), "\n", "", -1))
+	s.Require().NotNil(extErr)
+	s.EqualError(err, extErr.Error())
 
 	SetDebug(false)
 	w = newTestWriter()
 	WriteError(w, err)
 	extErr = ReadError(w.StatusCode, w.Body)
-	c.Assert(w.StatusCode, Equals, http.StatusInternalServerError)
-	c.Assert(strings.Replace(string(w.Body), "\n", "", -1), Matches, "*.snap.*")
-	c.Assert(err.Error(), Equals, extErr.Error())
+	s.Equal(http.StatusInternalServerError, w.StatusCode)
+	s.Regexp(".*.snap.*", strings.Replace(string(w.Body), "\n", "", -1))
+	s.Require().NotNil(extErr)
+	s.EqualError(err, extErr.Error())
 }
 
-type netError struct {
-}
+type netError struct{}
 
 func (e *netError) Error() string   { return "net" }
 func (e *netError) Timeout() bool   { return true }
 func (e *netError) Temporary() bool { return true }
 
-func (s *TraceSuite) TestConvert(c *C) {
+func (s *TraceSuite) TestConvert() {
 	err := ConvertSystemError(&netError{})
-	c.Assert(IsConnectionProblem(err), Equals, true, Commentf("failed to detect network error"))
+	s.True(IsConnectionProblem(err), "failed to detect network error")
 
-	dir := c.MkDir()
-	err = os.Mkdir(dir, 0770)
+	dir := s.T().TempDir()
+	err = os.Mkdir(dir, 0o770)
 	err = ConvertSystemError(err)
-	c.Assert(IsAlreadyExists(err), Equals, true, Commentf("expected AlreadyExists error, got %T", err))
+	s.True(IsAlreadyExists(err), "expected AlreadyExists error, got %T", err)
 }
 
-func (s *TraceSuite) TestAggregates(c *C) {
+func (s *TraceSuite) TestAggregates() {
 	err1 := Errorf("failed one")
 	err2 := Errorf("failed two")
+
 	err := NewAggregate(err1, err2)
-	c.Assert(IsAggregate(err), Equals, true)
+	s.True(IsAggregate(err))
+
 	agg := Unwrap(err).(Aggregate)
-	c.Assert(agg.Errors(), DeepEquals, []error{err1, err2})
-	c.Assert(err.Error(), DeepEquals, "failed one, failed two")
+	s.Equal([]error{err1, err2}, agg.Errors())
+	s.Equal("failed one, failed two", err.Error())
 }
 
-func (s *TraceSuite) TestErrorf(c *C) {
+func (s *TraceSuite) TestErrorf() {
 	err := Errorf("error")
-	c.Assert(line(DebugReport(err)), Matches, "*.trace_test.go.*")
-	c.Assert(line(DebugReport(err)), Not(Matches), "*.Fields.*")
-	c.Assert(err.(*TraceErr).Messages, DeepEquals, []string(nil))
+	s.Regexp(".*.trace_test.go.*", line(DebugReport(err)))
+	s.NotRegexp(".*.Fields.*", line(DebugReport(err)))
+	s.Equal([]string(nil), err.(*TraceErr).Messages)
 }
 
-func (s *TraceSuite) TestWithField(c *C) {
-	err := Wrap(Errorf("error")).AddField("testfield", true)
-	c.Assert(line(DebugReport(err)), Matches, "*.testfield.*")
+func (s *TraceSuite) TestWithField() {
+	err := WithField(Wrap(Errorf("error")), "testfield", true)
+	s.Regexp(".*.testfield.*", line(DebugReport(err)))
 }
 
-func (s *TraceSuite) TestWithFields(c *C) {
-	err := Wrap(Errorf("error")).AddFields(map[string]interface{}{
+func (s *TraceSuite) TestWithFields() {
+	err := WithFields(Wrap(Errorf("error")), map[string]interface{}{
 		"testfield1": true,
 		"testfield2": "value2",
 	})
-	c.Assert(line(DebugReport(err)), Matches, "*.Fields.*")
-	c.Assert(line(DebugReport(err)), Matches, "*.testfield1: true.*")
-	c.Assert(line(DebugReport(err)), Matches, "*.testfield2: value2.*")
+	s.Regexp(".*.Fields.*", line(DebugReport(err)))
+	s.Regexp(".*.testfield1: true.*", line(DebugReport(err)))
+	s.Regexp(".*.testfield2: value2.*", line(DebugReport(err)))
 }
 
-func (s *TraceSuite) TestAggregateConvertsToCommonErrors(c *C) {
+func (s *TraceSuite) TestAggregateConvertsToCommonErrors() {
 	testCases := []struct {
 		Err                error
 		Predicate          func(error) bool
@@ -514,61 +567,69 @@ func (s *TraceSuite) TestAggregateConvertsToCommonErrors(c *C) {
 		},
 	}
 	for _, testCase := range testCases {
-		comment := Commentf(testCase.comment)
 		SetDebug(true)
 		err := testCase.Err
 
-		c.Assert(line(DebugReport(err)), Matches, "*.trace_test.go.*", comment)
-		c.Assert(testCase.Predicate(err), Equals, true, comment)
+		s.Regexp(".*.trace_test.go.*", line(DebugReport(err)), testCase.comment)
+		s.True(testCase.Predicate(err), testCase.comment)
 
 		w := newTestWriter()
 		WriteError(w, err)
 		outErr := ReadError(w.StatusCode, w.Body)
-		c.Assert(testCase.RoundtripPredicate(outErr), Equals, true, comment)
+		s.True(testCase.RoundtripPredicate(outErr), testCase.comment)
 
 		SetDebug(false)
 		w = newTestWriter()
 		WriteError(w, err)
 		outErr = ReadError(w.StatusCode, w.Body)
-		c.Assert(testCase.RoundtripPredicate(outErr), Equals, true, comment)
+		s.True(testCase.RoundtripPredicate(outErr), testCase.comment)
 	}
 }
 
-func (s *TraceSuite) TestAggregateThrowAwayNils(c *C) {
+func (s *TraceSuite) TestAggregateThrowAwayNils() {
 	err := NewAggregate(fmt.Errorf("error1"), nil, fmt.Errorf("error2"))
-	c.Assert(err.Error(), Not(Matches), ".*nil.*")
+	s.Require().NotNil(err)
+	s.NotRegexp(".*nil.*", err.Error())
 }
 
-func (s *TraceSuite) TestAggregateAllNils(c *C) {
-	c.Assert(NewAggregate(nil, nil, nil), IsNil)
+func (s *TraceSuite) TestAggregateAllNils() {
+	s.Nil(NewAggregate(nil, nil, nil))
 }
 
-func (s *TraceSuite) TestAggregateFromChannel(c *C) {
+func (s *TraceSuite) TestAggregateFromChannel() {
 	errCh := make(chan error, 3)
 	errCh <- fmt.Errorf("Snap!")
 	errCh <- fmt.Errorf("BAM")
 	errCh <- fmt.Errorf("omg")
 	close(errCh)
+
 	err := NewAggregateFromChannel(errCh, context.Background())
-	c.Assert(err.Error(), Matches, ".*Snap!.*")
-	c.Assert(err.Error(), Matches, ".*BAM.*")
-	c.Assert(err.Error(), Matches, ".*omg.*")
+	s.Require().NotNil(err)
+	s.Regexp(".*Snap!.*", err.Error())
+	s.Regexp(".*BAM.*", err.Error())
+	s.Regexp(".*omg.*", err.Error())
 }
 
-func (s *TraceSuite) TestAggregateFromChannelCancel(c *C) {
-	errCh := make(chan error, 3)
+func (s *TraceSuite) TestAggregateFromChannelCancel() {
+	ctx, cancel := context.WithCancel(context.Background())
+	errCh := make(chan error)
+	outCh := make(chan error)
+	go func() {
+		outCh <- NewAggregateFromChannel(errCh, ctx)
+	}()
 	errCh <- fmt.Errorf("Snap!")
 	errCh <- fmt.Errorf("BAM")
 	errCh <- fmt.Errorf("omg")
-	ctx, cancel := context.WithCancel(context.Background())
-	// we never closed the channel so we just need to make sure
+	// we never closed the channel, so we just need to make sure
 	// the function exits when we cancel it
 	cancel()
-	NewAggregateFromChannel(errCh, ctx)
+
+	err := <-outCh
+	s.Error(err)
 }
 
-func (s *TraceSuite) TestCompositeErrorsCanProperlyUnwrap(c *C) {
-	var testCases = []struct {
+func (s *TraceSuite) TestCompositeErrorsCanProperlyUnwrap() {
+	testCases := []struct {
 		err            error
 		message        string
 		wrappedMessage string
@@ -591,9 +652,9 @@ func (s *TraceSuite) TestCompositeErrorsCanProperlyUnwrap(c *C) {
 	}
 	var wrapper ErrorWrapper
 	for _, tt := range testCases {
-		c.Assert(tt.err.Error(), Equals, tt.message)
-		c.Assert(Unwrap(tt.err), Implements, &wrapper)
-		c.Assert(Unwrap(tt.err).(ErrorWrapper).OrigError().Error(), Equals, tt.wrappedMessage)
+		s.Equal(tt.message, tt.err.Error())
+		s.Implements(&wrapper, Unwrap(tt.err))
+		s.Equal(tt.wrappedMessage, Unwrap(tt.err).(ErrorWrapper).OrigError().Error())
 	}
 }
 
@@ -677,3 +738,23 @@ func TestStdlibCompat(t *testing.T) {
 		t.Errorf("got %q, want %q", wrappedErrorMessage, expectedErr.Error())
 	}
 }
+
+// TestStdLibCompat_Aggregate runs through a scenario which ensures that
+// Aggregate behaves well with errors.Is/errors.As in cases with trace
+// wrapped errors and stdlib errors
+func TestStdlibCompat_Aggregate(t *testing.T) {
+	randomErr := fmt.Errorf("random")
+	bpMsg := "bad param"
+	badParamErr := BadParameter(bpMsg)
+	fooErr := fmt.Errorf("foo")
+
+	agg := Wrap(NewAggregate(Wrap(badParamErr), fooErr))
+
+	require.ErrorIs(t, agg, badParamErr)
+	require.ErrorIs(t, agg, fooErr)
+	require.NotErrorIs(t, agg, randomErr)
+
+	var badParamErrTarget *BadParameterError
+	require.ErrorAs(t, agg, &badParamErrTarget)
+	require.Equal(t, bpMsg, badParamErrTarget.Message)
+}
diff --git a/trail/trail.go b/trail/trail.go
index e621fa9..0ea13cb 100644
--- a/trail/trail.go
+++ b/trail/trail.go
@@ -41,6 +41,8 @@ package trail
 import (
 	"encoding/base64"
 	"encoding/json"
+	"errors"
+	"io"
 
 	"github.com/gravitational/trace"
 	"github.com/gravitational/trace/internal"
@@ -49,6 +51,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/status"
 )
 
 // Send is a high level function that:
@@ -81,6 +84,16 @@ func ToGRPC(err error) error {
 	if err == nil {
 		return nil
 	}
+
+	if errors.Is(err, io.EOF) {
+		return err
+	}
+
+	// If err is already a gRPC error, don't modify it.
+	if _, ok := status.FromError(err); ok {
+		return err
+	}
+
 	userMessage := trace.UserMessage(err)
 	if trace.IsNotFound(err) {
 		return grpc.Errorf(codes.NotFound, userMessage)
diff --git a/trail/trail_test.go b/trail/trail_test.go
index 6282778..4c46204 100644
--- a/trail/trail_test.go
+++ b/trail/trail_test.go
@@ -17,33 +17,42 @@ limitations under the License.
 package trail
 
 import (
+	"errors"
 	"io"
 	"strings"
 	"testing"
 
 	"github.com/gravitational/trace"
 
+	"github.com/stretchr/testify/suite"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
-	. "gopkg.in/check.v1"
+	"google.golang.org/grpc/status"
 )
 
-func TestTrail(t *testing.T) { TestingT(t) }
+func TestTrail(t *testing.T) {
+	suite.Run(t, new(TrailSuite))
+}
 
 type TrailSuite struct {
+	suite.Suite
 }
 
-var _ = Suite(&TrailSuite{})
-
 // TestConversion makes sure we convert all trace supported errors
 // to and back from GRPC codes
-func (s *TrailSuite) TestConversion(c *C) {
-	type TestCase struct {
+func (s *TrailSuite) TestConversion() {
+	testCases := []struct {
 		Error     error
 		Message   string
 		Predicate func(error) bool
-	}
-	testCases := []TestCase{
+	}{
+		{
+			Error: io.EOF,
+			Predicate: func(err error) bool {
+				return errors.Is(err, io.EOF)
+			},
+		},
 		{
 			Error:     trace.AccessDenied("access denied"),
 			Predicate: trace.IsAccessDenied,
@@ -77,39 +86,51 @@ func (s *TrailSuite) TestConversion(c *C) {
 			Predicate: trace.IsNotImplemented,
 		},
 	}
+
 	for i, tc := range testCases {
-		comment := Commentf("test case #v", i+1)
 		grpcError := ToGRPC(tc.Error)
-		c.Assert(grpc.ErrorDesc(grpcError), Equals, tc.Error.Error(), comment)
+		s.Equal(tc.Error.Error(), grpc.ErrorDesc(grpcError), "test case %v", i+1)
 		out := FromGRPC(grpcError)
-		c.Assert(tc.Predicate(out), Equals, true, comment)
-		c.Assert(line(trace.DebugReport(out)), Matches, ".*trail_test.go.*")
-		c.Assert(line(trace.DebugReport(out)), Not(Matches), ".*trail.go.*")
+		s.True(tc.Predicate(out), "test case %v", i+1)
+		s.Regexp(".*trail_test.go.*", line(trace.DebugReport(out)))
+		s.NotRegexp(".*trail.go.*", line(trace.DebugReport(out)))
 	}
 }
 
 // TestNil makes sure conversions of nil to and from GRPC are no-op
-func (s *TrailSuite) TestNil(c *C) {
+func (s *TrailSuite) TestNil() {
 	out := FromGRPC(ToGRPC(nil))
-	c.Assert(out, IsNil)
+	s.Nil(out)
 }
 
 // TestFromEOF makes sure that non-grpc error such as io.EOF is preserved well.
-func (s *TrailSuite) TestFromEOF(c *C) {
+func (s *TrailSuite) TestFromEOF() {
 	out := FromGRPC(trace.Wrap(io.EOF))
-	c.Assert(trace.IsEOF(out), Equals, true)
+	s.True(trace.IsEOF(out))
 }
 
 // TestTraces makes sure we pass traces via metadata and can decode it back
-func (s *TrailSuite) TestTraces(c *C) {
+func (s *TrailSuite) TestTraces() {
 	err := trace.BadParameter("param")
 	meta := metadata.New(nil)
 	SetDebugInfo(err, meta)
 	err2 := FromGRPC(ToGRPC(err), meta)
-	c.Assert(line(trace.DebugReport(err)), Matches, ".*trail_test.go.*")
-	c.Assert(line(trace.DebugReport(err2)), Matches, ".*trail_test.go.*")
+	s.Regexp(".*trail_test.go.*", line(trace.DebugReport(err)))
+	s.Regexp(".*trail_test.go.*", line(trace.DebugReport(err2)))
 }
 
 func line(s string) string {
 	return strings.Replace(s, "\n", "", -1)
 }
+
+func TestToGRPCKeepCode(t *testing.T) {
+	err := status.Errorf(codes.PermissionDenied, "denied")
+	err = ToGRPC(err)
+	if code := status.Code(err); code != codes.PermissionDenied {
+		t.Errorf("after ToGRPC, got error code %v, want %v, error: %v", code, codes.PermissionDenied, err)
+	}
+	err = FromGRPC(err)
+	if !trace.IsAccessDenied(err) {
+		t.Errorf("after FromGRPC, trace.IsAccessDenied is false, want true, error: %v", err)
+	}
+}
diff --git a/udphook_test.go b/udphook_test.go
index bf47855..9f76076 100644
--- a/udphook_test.go
+++ b/udphook_test.go
@@ -18,17 +18,22 @@ package trace
 
 import (
 	"io/ioutil"
+	"testing"
 
 	"github.com/jonboulle/clockwork"
 	log "github.com/sirupsen/logrus"
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/suite"
 )
 
-type HooksSuite struct{}
+type HooksSuite struct {
+	suite.Suite
+}
 
-var _ = Suite(&HooksSuite{})
+func TestHooks(t *testing.T) {
+	suite.Run(t, new(HooksSuite))
+}
 
-func (s *HooksSuite) TestSafeForConcurrentAccess(c *C) {
+func (s *HooksSuite) TestSafeForConcurrentAccess() {
 	logger := log.New()
 	logger.Out = ioutil.Discard
 	entry := logger.WithFields(log.Fields{"foo": "bar"})

More details

Full run details