Files
aleesa-phrases-go/vendor/github.com/cockroachdb/errors/errbase/encode.go
2022-05-21 01:38:58 +03:00

311 lines
9.4 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 errbase
import (
"context"
"log"
"reflect"
"strings"
"github.com/cockroachdb/errors/errorspb"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
)
// EncodedError is the type of an encoded (and protobuf-encodable) error.
type EncodedError = errorspb.EncodedError
// EncodeError encodes an error.
func EncodeError(ctx context.Context, err error) EncodedError {
if cause := UnwrapOnce(err); cause != nil {
return encodeWrapper(ctx, err, cause)
}
// Not a causer.
return encodeLeaf(ctx, err)
}
// encodeLeaf encodes a leaf error.
func encodeLeaf(ctx context.Context, err error) EncodedError {
var msg string
var details errorspb.EncodedErrorDetails
if e, ok := err.(*opaqueLeaf); ok {
msg = e.msg
details = e.details
} else {
details.OriginalTypeName, details.ErrorTypeMark.FamilyName, details.ErrorTypeMark.Extension = getTypeDetails(err, false /*onlyFamily*/)
var payload proto.Message
// If we have a manually registered encoder, use that.
typeKey := TypeKey(details.ErrorTypeMark.FamilyName)
if enc, ok := leafEncoders[typeKey]; ok {
msg, details.ReportablePayload, payload = enc(ctx, err)
} else {
// No encoder. Let's try to manually extract fields.
// The message comes from Error(). Simple.
msg = err.Error()
// If there are known safe details, use them.
if s, ok := err.(SafeDetailer); ok {
details.ReportablePayload = s.SafeDetails()
}
// If it's also a protobuf message, we'll use that as
// payload. DecodeLeaf() will know how to turn that back into a
// full error if there is no decoder.
payload, _ = err.(proto.Message)
}
// If there is a detail payload, encode it.
details.FullDetails = encodeAsAny(ctx, err, payload)
}
return EncodedError{
Error: &errorspb.EncodedError_Leaf{
Leaf: &errorspb.EncodedErrorLeaf{
Message: msg,
Details: details,
},
},
}
}
// warningFn can be overridden with a suitable logging function using
// SetWarningFn() below.
var warningFn = func(_ context.Context, format string, args ...interface{}) {
log.Printf(format, args...)
}
// SetWarningFn enables configuration of the warning function.
func SetWarningFn(fn func(context.Context, string, ...interface{})) {
warningFn = fn
}
func encodeAsAny(ctx context.Context, err error, payload proto.Message) *types.Any {
if payload == nil {
return nil
}
any, marshalErr := types.MarshalAny(payload)
if marshalErr != nil {
warningFn(ctx,
"error %+v (%T) announces proto message, but marshaling fails: %+v",
err, err, marshalErr)
return nil
}
return any
}
// encodeWrapper encodes an error wrapper.
func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
var msg string
var details errorspb.EncodedErrorDetails
if e, ok := err.(*opaqueWrapper); ok {
msg = e.prefix
details = e.details
} else {
details.OriginalTypeName, details.ErrorTypeMark.FamilyName, details.ErrorTypeMark.Extension = getTypeDetails(err, false /*onlyFamily*/)
var payload proto.Message
// If we have a manually registered encoder, use that.
typeKey := TypeKey(details.ErrorTypeMark.FamilyName)
if enc, ok := encoders[typeKey]; ok {
msg, details.ReportablePayload, payload = enc(ctx, err)
} else {
// No encoder.
// In that case, we'll try to compute a message prefix
// manually.
msg = extractPrefix(err, cause)
// If there are known safe details, use them.
if s, ok := err.(SafeDetailer); ok {
details.ReportablePayload = s.SafeDetails()
}
// That's all we can get.
}
// If there is a detail payload, encode it.
details.FullDetails = encodeAsAny(ctx, err, payload)
}
return EncodedError{
Error: &errorspb.EncodedError_Wrapper{
Wrapper: &errorspb.EncodedWrapper{
Cause: EncodeError(ctx, cause),
MessagePrefix: msg,
Details: details,
},
},
}
}
// extractPrefix extracts the prefix from a wrapper's error message.
// For example,
// err := errors.New("bar")
// err = errors.Wrap(err, "foo")
// extractPrefix(err)
// returns "foo".
func extractPrefix(err, cause error) string {
causeSuffix := cause.Error()
errMsg := err.Error()
if strings.HasSuffix(errMsg, causeSuffix) {
prefix := errMsg[:len(errMsg)-len(causeSuffix)]
if strings.HasSuffix(prefix, ": ") {
return prefix[:len(prefix)-2]
}
}
return ""
}
func getTypeDetails(
err error, onlyFamily bool,
) (origTypeName string, typeKeyFamily string, typeKeyExtension string) {
// If we have received an error of type not known locally,
// we still know its type name. Return that.
switch t := err.(type) {
case *opaqueLeaf:
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
case *opaqueWrapper:
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
}
// Compute the full error name, for reporting and printing details.
tn := getFullTypeName(err)
// Compute a family name, used to find decoders and to compare error identities.
fm := tn
if prevKey, ok := backwardRegistry[TypeKey(tn)]; ok {
fm = string(prevKey)
}
if onlyFamily {
return tn, fm, ""
}
// If the error has an extra type marker, add it.
// This is not used by the base functionality but
// is hooked into by the barrier subsystem.
var em string
if tm, ok := err.(TypeKeyMarker); ok {
em = tm.ErrorKeyMarker()
}
return tn, fm, em
}
// TypeKeyMarker can be implemented by errors that wish to extend
// their type name as seen by GetTypeKey().
//
// Note: the key marker is considered safe for reporting and
// is included in sentry reports.
type TypeKeyMarker interface {
ErrorKeyMarker() string
}
func getFullTypeName(err error) string {
t := reflect.TypeOf(err)
pkgPath := getPkgPath(t)
return makeTypeKey(pkgPath, t.String())
}
func makeTypeKey(pkgPath, typeNameString string) string {
return pkgPath + "/" + typeNameString
}
// getPkgPath extract the package path for a Go type. We'll do some
// extra work for typical types that did not get a name, for example
// *E has the package path of E.
func getPkgPath(t reflect.Type) string {
pkgPath := t.PkgPath()
if pkgPath != "" {
return pkgPath
}
// Try harder.
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
return getPkgPath(t.Elem())
}
// Nothing to report.
return ""
}
// TypeKey identifies an error for the purpose of looking up decoders.
// It is equivalent to the "family name" in ErrorTypeMarker.
type TypeKey string
// GetTypeKey retrieve the type key for a given error object. This
// is meant for use in combination with the Register functions.
func GetTypeKey(err error) TypeKey {
_, familyName, _ := getTypeDetails(err, true /*onlyFamily*/)
return TypeKey(familyName)
}
// GetTypeMark retrieves the ErrorTypeMark for a given error object.
// This is meant for use in the markers sub-package.
func GetTypeMark(err error) errorspb.ErrorTypeMark {
_, familyName, extension := getTypeDetails(err, false /*onlyFamily*/)
return errorspb.ErrorTypeMark{FamilyName: familyName, Extension: extension}
}
// RegisterLeafEncoder can be used to register new leaf error types to
// the library. Registered types will be encoded using their own
// Go type when an error is encoded. Wrappers that have not been
// registered will be encoded using the opaqueLeaf type.
//
// Note: if the error type has been migrated from a previous location
// or a different type, ensure that RegisterTypeMigration() was called
// prior to RegisterLeafEncoder().
func RegisterLeafEncoder(theType TypeKey, encoder LeafEncoder) {
if encoder == nil {
delete(leafEncoders, theType)
} else {
leafEncoders[theType] = encoder
}
}
// LeafEncoder is to be provided (via RegisterLeafEncoder above)
// by additional wrapper types not yet known to this library.
type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
// registry for RegisterLeafEncoder.
var leafEncoders = map[TypeKey]LeafEncoder{}
// RegisterWrapperEncoder can be used to register new wrapper types to
// the library. Registered wrappers will be encoded using their own
// Go type when an error is encoded. Wrappers that have not been
// registered will be encoded using the opaqueWrapper type.
//
// Note: if the error type has been migrated from a previous location
// or a different type, ensure that RegisterTypeMigration() was called
// prior to RegisterWrapperEncoder().
func RegisterWrapperEncoder(theType TypeKey, encoder WrapperEncoder) {
if encoder == nil {
delete(encoders, theType)
} else {
encoders[theType] = encoder
}
}
// WrapperEncoder is to be provided (via RegisterWrapperEncoder above)
// by additional wrapper types not yet known to this library.
type WrapperEncoder = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message)
// registry for RegisterWrapperType.
var encoders = map[TypeKey]WrapperEncoder{}