Skip to content

Commit 9b2e535

Browse files
committed
Import the initial implementation.
1 parent 6c5aa36 commit 9b2e535

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+10556
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# cockroachdb/errors: an error library with network portability
2+
3+
After the discussion in https://github.com/cockroachdb/cockroach/pull/36987
4+
and https://github.com/cockroachdb/cockroach/pull/37121

assert/assert.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2019 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package assert
16+
17+
import (
18+
"github.com/cockroachdb/errors/errbase"
19+
"github.com/cockroachdb/errors/markers"
20+
"github.com/cockroachdb/errors/stdstrings"
21+
"github.com/gogo/protobuf/proto"
22+
)
23+
24+
// WithAssertionFailure decorates the error with an assertion failure marker.
25+
// This is not intended to be used directly (see AssertionFailed() for
26+
// further decoration).
27+
func WithAssertionFailure(err error) error {
28+
if err == nil {
29+
return nil
30+
}
31+
return &withAssertionFailure{cause: err}
32+
}
33+
34+
// HasAssertionFailure returns true if the error or any of its causes
35+
// is an assertion failure annotation.
36+
func HasAssertionFailure(err error) bool {
37+
_, ok := markers.If(err, func(err error) (v interface{}, ok bool) {
38+
v, ok = err.(*withAssertionFailure)
39+
return
40+
})
41+
return ok
42+
}
43+
44+
// IsAssertionFailure returns true if the error (not its causes) is an
45+
// assertion failure annotation. Consider using markers.If or
46+
// HasAssertionFailure to test both the error and its causes.
47+
func IsAssertionFailure(err error) bool {
48+
_, ok := err.(*withAssertionFailure)
49+
return ok
50+
}
51+
52+
type withAssertionFailure struct {
53+
cause error
54+
}
55+
56+
// ErrorHint implements the hintdetail.ErrorHinter interface.
57+
func (w *withAssertionFailure) ErrorHint() string {
58+
return AssertionErrorHint + stdstrings.IssueReferral
59+
}
60+
61+
// AssertionErrorHint is the hint emitted upon assertion failures.
62+
const AssertionErrorHint = `You have encountered an unexpected error.`
63+
64+
func (w *withAssertionFailure) Error() string { return w.cause.Error() }
65+
func (w *withAssertionFailure) Cause() error { return w.cause }
66+
func (w *withAssertionFailure) Unwrap() error { return w.cause }
67+
68+
func decodeAssertFailure(cause error, _ string, _ []string, _ proto.Message) error {
69+
return &withAssertionFailure{cause: cause}
70+
}
71+
72+
func init() {
73+
errbase.RegisterWrapperDecoder(errbase.GetTypeKey(&withAssertionFailure{}), decodeAssertFailure)
74+
}

assert/assert_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2019 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package assert_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/cockroachdb/errors/assert"
21+
"github.com/cockroachdb/errors/errbase"
22+
"github.com/cockroachdb/errors/markers"
23+
"github.com/cockroachdb/errors/testutils"
24+
"github.com/pkg/errors"
25+
)
26+
27+
func TestAssert(t *testing.T) {
28+
tt := testutils.T{T: t}
29+
30+
baseErr := errors.New("world")
31+
err := errors.Wrap(assert.WithAssertionFailure(baseErr), "hello")
32+
33+
tt.Check(markers.Is(err, baseErr))
34+
35+
tt.Check(assert.HasAssertionFailure(err))
36+
37+
if _, ok := markers.If(err, func(err error) (interface{}, bool) { return nil, assert.IsAssertionFailure(err) }); !ok {
38+
t.Error("woops")
39+
}
40+
41+
tt.CheckEqual(err.Error(), "hello: world")
42+
43+
enc := errbase.EncodeError(err)
44+
newErr := errbase.DecodeError(enc)
45+
46+
tt.Check(markers.Is(newErr, baseErr))
47+
48+
tt.Check(assert.HasAssertionFailure(newErr))
49+
50+
if _, ok := markers.If(newErr, func(err error) (interface{}, bool) { return nil, assert.IsAssertionFailure(err) }); !ok {
51+
t.Error("woops")
52+
}
53+
54+
tt.CheckEqual(newErr.Error(), "hello: world")
55+
}

assert_api.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2019 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package errors
16+
17+
import "github.com/cockroachdb/errors/assert"
18+
19+
// WithAssertionFailure forwards a definition.
20+
func WithAssertionFailure(err error) error { return assert.WithAssertionFailure(err) }
21+
22+
// HasAssertionFailure forwards a definition.
23+
func HasAssertionFailure(err error) bool { return assert.HasAssertionFailure(err) }
24+
25+
// IsAssertionFailure forwards a definition.
26+
func IsAssertionFailure(err error) bool { return assert.IsAssertionFailure(err) }

barriers/barriers.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2019 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package barriers
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/cockroachdb/errors/errbase"
21+
"github.com/gogo/protobuf/proto"
22+
)
23+
24+
// Handled swallows the provided error and hides is from the
25+
// Cause()/Unwrap() interface, and thus the Is() facility that
26+
// identifies causes. However, it retains it for the purpose of
27+
// printing the error out (e.g. for troubleshooting). The error
28+
// message is preserved in full.
29+
func Handled(err error) error {
30+
if err == nil {
31+
return nil
32+
}
33+
return HandledWithMessage(err, err.Error())
34+
}
35+
36+
// HandledWithMessage is like Handled except the message is overridden.
37+
// This can be used e.g. to hide message details or to prevent
38+
// downstream code to make assertions on the message's contents.
39+
func HandledWithMessage(err error, msg string) error {
40+
if err == nil {
41+
return nil
42+
}
43+
return &barrierError{maskedErr: err, msg: msg}
44+
}
45+
46+
// HandledWithMessagef is like HandledWithMessagef except the message
47+
// is formatted.
48+
func HandledWithMessagef(err error, format string, args ...interface{}) error {
49+
if err == nil {
50+
return nil
51+
}
52+
return &barrierError{maskedErr: err, msg: fmt.Sprintf(format, args...)}
53+
}
54+
55+
// barrierError is a leaf error type. It encapsulates a chain of
56+
// original causes, but these causes are hidden so that they inhibit
57+
// matching via Is() and the Cause()/Unwrap() recursions.
58+
type barrierError struct {
59+
// Message for the barrier itself.
60+
// In the common case, the message from the masked error
61+
// is used as-is (see Handled() above) however it is
62+
// useful to cache it here since the masked error may
63+
// have a long chain of wrappers and its Error() call
64+
// may be expensive.
65+
msg string
66+
// Masked error chain.
67+
maskedErr error
68+
}
69+
70+
var _ error = &barrierError{}
71+
var _ errbase.SafeDetailer = &barrierError{}
72+
73+
// barrierError is an error.
74+
func (e *barrierError) Error() string { return e.msg }
75+
76+
// SafeDetails reports the PII-free details from the masked error.
77+
func (e *barrierError) SafeDetails() []string {
78+
var details []string
79+
for err := e.maskedErr; err != nil; err = errbase.UnwrapOnce(err) {
80+
sd := errbase.GetSafeDetails(err)
81+
details = sd.Fill(details)
82+
}
83+
return details
84+
}
85+
86+
// Printing a barrier reveals the details.
87+
func (e *barrierError) Format(s fmt.State, verb rune) {
88+
switch verb {
89+
case 'v':
90+
if s.Flag('+') {
91+
fmt.Fprintf(s, "%s", e.msg)
92+
if e.maskedErr != nil {
93+
fmt.Fprintf(s, "\n-- original cause:\n")
94+
errbase.FormatError(s, verb, e.maskedErr)
95+
}
96+
return
97+
}
98+
fallthrough
99+
case 's', 'q':
100+
fmt.Fprintf(s, fmt.Sprintf("%%%c", verb), e.msg)
101+
}
102+
}
103+
104+
// A barrier error is encoded exactly.
105+
func encodeBarrier(err error) (msg string, details []string, payload proto.Message) {
106+
e := err.(*barrierError)
107+
enc := errbase.EncodeError(e.maskedErr)
108+
return e.msg, e.SafeDetails(), &enc
109+
}
110+
111+
// A barrier error is decoded exactly.
112+
func decodeBarrier(msg string, _ []string, payload proto.Message) error {
113+
enc := payload.(*errbase.EncodedError)
114+
return &barrierError{msg: msg, maskedErr: errbase.DecodeError(*enc)}
115+
}
116+
117+
func init() {
118+
tn := errbase.GetTypeKey(&barrierError{})
119+
errbase.RegisterLeafDecoder(tn, decodeBarrier)
120+
errbase.RegisterLeafEncoder(tn, encodeBarrier)
121+
}

barriers/barriers_test.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2019 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package barriers_test
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
"testing"
21+
22+
"github.com/cockroachdb/errors/barriers"
23+
"github.com/cockroachdb/errors/errbase"
24+
"github.com/cockroachdb/errors/markers"
25+
"github.com/cockroachdb/errors/testutils"
26+
"github.com/pkg/errors"
27+
)
28+
29+
// This test demonstrates that a barrier hides it causes.
30+
func TestHideCause(t *testing.T) {
31+
tt := testutils.T{T: t}
32+
33+
origErr := errors.New("hello")
34+
err1 := errors.Wrap(origErr, "world")
35+
36+
// Assertion: without barriers, the cause can be identified.
37+
tt.Assert(markers.Is(err1, origErr))
38+
39+
// This test: a barrier hides the cause.
40+
err := barriers.Handled(err1)
41+
tt.Check(!markers.Is(err, origErr))
42+
}
43+
44+
// This test demonstrates how the message is preserved (or not) depending
45+
// on how the barrier is constructed.
46+
func TestBarrierMessage(t *testing.T) {
47+
tt := testutils.T{T: t}
48+
49+
origErr := errors.New("hello")
50+
51+
b1 := barriers.Handled(origErr)
52+
tt.CheckEqual(b1.Error(), origErr.Error())
53+
54+
b2 := barriers.HandledWithMessage(origErr, "woo")
55+
tt.CheckEqual(b2.Error(), "woo")
56+
}
57+
58+
// This test demonstrates that the original error details
59+
// are preserved through barriers, even when they go through the network.
60+
func TestBarrierMaskedDetails(t *testing.T) {
61+
tt := testutils.T{T: t}
62+
63+
origErr := errors.New("hello friends")
64+
65+
b := barriers.HandledWithMessage(origErr, "message hidden")
66+
67+
// Assertion: the friends message is hidden.
68+
tt.Assert(!strings.Contains(b.Error(), "friends"))
69+
70+
// This test: the details are available when printing the error details.
71+
errV := fmt.Sprintf("%+v", b)
72+
tt.Check(strings.Contains(errV, "friends"))
73+
74+
// Simulate a network traversal.
75+
enc := errbase.EncodeError(b)
76+
newB := errbase.DecodeError(enc)
77+
78+
// The friends message is hidden.
79+
tt.Check(!strings.Contains(b.Error(), "friends"))
80+
81+
// The cause is still hidden.
82+
tt.Check(!markers.Is(newB, origErr))
83+
84+
// However the cause's details are still visible.
85+
errV = fmt.Sprintf("%+v", newB)
86+
tt.Check(strings.Contains(errV, "friends"))
87+
}
88+
89+
// This test exercises HandledWithMessagef.
90+
func TestHandledWithMessagef(t *testing.T) {
91+
tt := testutils.T{T: t}
92+
93+
origErr := errors.New("hello friends")
94+
95+
b1 := barriers.HandledWithMessage(origErr, "woo woo")
96+
b2 := barriers.HandledWithMessagef(origErr, "woo %s", "woo")
97+
98+
tt.CheckEqual(b1.Error(), b2.Error())
99+
}

0 commit comments

Comments
 (0)