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"})