125 lines
3.9 KiB
Go
125 lines
3.9 KiB
Go
// Copyright 2019 The Cockroach Authors.
|
|
//
|
|
// 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 barriers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/cockroachdb/errors/errbase"
|
|
"github.com/gogo/protobuf/proto"
|
|
)
|
|
|
|
// Handled swallows the provided error and hides it from the
|
|
// Cause()/Unwrap() interface, and thus the Is() facility that
|
|
// identifies causes. However, it retains it for the purpose of
|
|
// printing the error out (e.g. for troubleshooting). The error
|
|
// message is preserved in full.
|
|
//
|
|
// Detail is shown:
|
|
// - via `errors.GetSafeDetails()`, shows details from hidden error.
|
|
// - when formatting with `%+v`.
|
|
// - in Sentry reports.
|
|
func Handled(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return HandledWithMessage(err, err.Error())
|
|
}
|
|
|
|
// HandledWithMessage is like Handled except the message is overridden.
|
|
// This can be used e.g. to hide message details or to prevent
|
|
// downstream code to make assertions on the message's contents.
|
|
func HandledWithMessage(err error, msg string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &barrierError{maskedErr: err, msg: msg}
|
|
}
|
|
|
|
// HandledWithMessagef is like HandledWithMessagef except the message
|
|
// is formatted.
|
|
func HandledWithMessagef(err error, format string, args ...interface{}) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &barrierError{maskedErr: err, msg: fmt.Sprintf(format, args...)}
|
|
}
|
|
|
|
// barrierError is a leaf error type. It encapsulates a chain of
|
|
// original causes, but these causes are hidden so that they inhibit
|
|
// matching via Is() and the Cause()/Unwrap() recursions.
|
|
type barrierError struct {
|
|
// Message for the barrier itself.
|
|
// In the common case, the message from the masked error
|
|
// is used as-is (see Handled() above) however it is
|
|
// useful to cache it here since the masked error may
|
|
// have a long chain of wrappers and its Error() call
|
|
// may be expensive.
|
|
msg string
|
|
// Masked error chain.
|
|
maskedErr error
|
|
}
|
|
|
|
var _ error = (*barrierError)(nil)
|
|
var _ errbase.SafeDetailer = (*barrierError)(nil)
|
|
var _ errbase.SafeFormatter = (*barrierError)(nil)
|
|
var _ fmt.Formatter = (*barrierError)(nil)
|
|
|
|
// barrierError is an error.
|
|
func (e *barrierError) Error() string { return e.msg }
|
|
|
|
// SafeDetails reports the PII-free details from the masked error.
|
|
func (e *barrierError) SafeDetails() []string {
|
|
var details []string
|
|
for err := e.maskedErr; err != nil; err = errbase.UnwrapOnce(err) {
|
|
sd := errbase.GetSafeDetails(err)
|
|
details = sd.Fill(details)
|
|
}
|
|
return details
|
|
}
|
|
|
|
// Printing a barrier reveals the details.
|
|
func (e *barrierError) Format(s fmt.State, verb rune) { errbase.FormatError(e, s, verb) }
|
|
|
|
func (e *barrierError) SafeFormatError(p errbase.Printer) (next error) {
|
|
p.Print(e.msg)
|
|
if p.Detail() {
|
|
p.Printf("-- cause hidden behind barrier\n%+v", e.maskedErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// A barrier error is encoded exactly.
|
|
func encodeBarrier(
|
|
ctx context.Context, err error,
|
|
) (msg string, details []string, payload proto.Message) {
|
|
e := err.(*barrierError)
|
|
enc := errbase.EncodeError(ctx, e.maskedErr)
|
|
return e.msg, e.SafeDetails(), &enc
|
|
}
|
|
|
|
// A barrier error is decoded exactly.
|
|
func decodeBarrier(ctx context.Context, msg string, _ []string, payload proto.Message) error {
|
|
enc := payload.(*errbase.EncodedError)
|
|
return &barrierError{msg: msg, maskedErr: errbase.DecodeError(ctx, *enc)}
|
|
}
|
|
|
|
func init() {
|
|
tn := errbase.GetTypeKey((*barrierError)(nil))
|
|
errbase.RegisterLeafDecoder(tn, decodeBarrier)
|
|
errbase.RegisterLeafEncoder(tn, encodeBarrier)
|
|
}
|