207 lines
6.9 KiB
Go
207 lines
6.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 errbase
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/cockroachdb/errors/errorspb"
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/gogo/protobuf/types"
|
|
)
|
|
|
|
// DecodeError decodes an error.
|
|
//
|
|
// Can only be called if the EncodedError is set (see IsSet()).
|
|
func DecodeError(ctx context.Context, enc EncodedError) error {
|
|
if w := enc.GetWrapper(); w != nil {
|
|
return decodeWrapper(ctx, w)
|
|
}
|
|
return decodeLeaf(ctx, enc.GetLeaf())
|
|
}
|
|
|
|
func decodeLeaf(ctx context.Context, enc *errorspb.EncodedErrorLeaf) error {
|
|
// In case there is a detailed payload, decode it.
|
|
var payload proto.Message
|
|
if enc.Details.FullDetails != nil {
|
|
var d types.DynamicAny
|
|
err := types.UnmarshalAny(enc.Details.FullDetails, &d)
|
|
if err != nil {
|
|
// It's OK if we can't decode. We'll use
|
|
// the opaque type below.
|
|
warningFn(ctx, "error while unmarshalling error: %+v", err)
|
|
} else {
|
|
payload = d.Message
|
|
}
|
|
}
|
|
|
|
// Do we have a leaf decoder for this type?
|
|
typeKey := TypeKey(enc.Details.ErrorTypeMark.FamilyName)
|
|
if decoder, ok := leafDecoders[typeKey]; ok {
|
|
// Yes, use it.
|
|
genErr := decoder(ctx, enc.Message, enc.Details.ReportablePayload, payload)
|
|
if genErr != nil {
|
|
// Decoding succeeded. Use this.
|
|
return genErr
|
|
}
|
|
// Decoding failed, we'll drop through to opaqueLeaf{} below.
|
|
} else if decoder, ok := multiCauseDecoders[typeKey]; ok {
|
|
causes := make([]error, len(enc.MultierrorCauses))
|
|
for i, e := range enc.MultierrorCauses {
|
|
causes[i] = DecodeError(ctx, *e)
|
|
}
|
|
genErr := decoder(ctx, causes, enc.Message, enc.Details.ReportablePayload, payload)
|
|
if genErr != nil {
|
|
return genErr
|
|
}
|
|
} else {
|
|
// Shortcut for non-registered proto-encodable error types:
|
|
// if it already implements `error`, it's good to go.
|
|
if e, ok := payload.(error); ok {
|
|
// yes: we're done!
|
|
return e
|
|
}
|
|
}
|
|
|
|
if len(enc.MultierrorCauses) > 0 {
|
|
causes := make([]error, len(enc.MultierrorCauses))
|
|
for i, e := range enc.MultierrorCauses {
|
|
causes[i] = DecodeError(ctx, *e)
|
|
}
|
|
leaf := &opaqueLeafCauses{
|
|
causes: causes,
|
|
}
|
|
leaf.msg = enc.Message
|
|
leaf.details = enc.Details
|
|
return leaf
|
|
}
|
|
|
|
// No decoder and no error type: we'll keep what we received and
|
|
// make it ready to re-encode exactly (if the error leaves over the
|
|
// network again).
|
|
return &opaqueLeaf{
|
|
msg: enc.Message,
|
|
details: enc.Details,
|
|
}
|
|
}
|
|
|
|
func decodeWrapper(ctx context.Context, enc *errorspb.EncodedWrapper) error {
|
|
// First decode the cause.
|
|
cause := DecodeError(ctx, enc.Cause)
|
|
|
|
// In case there is a detailed payload, decode it.
|
|
var payload proto.Message
|
|
if enc.Details.FullDetails != nil {
|
|
var d types.DynamicAny
|
|
err := types.UnmarshalAny(enc.Details.FullDetails, &d)
|
|
if err != nil {
|
|
// It's OK if we can't decode. We'll use
|
|
// the opaque type below.
|
|
warningFn(ctx, "error while unmarshalling wrapper error: %+v", err)
|
|
} else {
|
|
payload = d.Message
|
|
}
|
|
}
|
|
|
|
// Do we have a wrapper decoder for this?
|
|
typeKey := TypeKey(enc.Details.ErrorTypeMark.FamilyName)
|
|
if decoder, ok := decoders[typeKey]; ok {
|
|
// Yes, use it.
|
|
genErr := decoder(ctx, cause, enc.Message, enc.Details.ReportablePayload, payload)
|
|
if genErr != nil {
|
|
// Decoding succeeded. Use this.
|
|
return genErr
|
|
}
|
|
// Decoding failed, we'll drop through to opaqueWrapper{} below.
|
|
}
|
|
|
|
// Otherwise, preserve all details about the original object.
|
|
return &opaqueWrapper{
|
|
cause: cause,
|
|
prefix: enc.Message,
|
|
details: enc.Details,
|
|
messageType: MessageType(enc.MessageType),
|
|
}
|
|
}
|
|
|
|
// RegisterLeafDecoder can be used to register new leaf error types to
|
|
// the library. Registered types will be decoded using their own
|
|
// Go type when an error is decoded. Wrappers that have not been
|
|
// registered will be decoded 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 RegisterLeafDecoder().
|
|
func RegisterLeafDecoder(theType TypeKey, decoder LeafDecoder) {
|
|
if decoder == nil {
|
|
delete(leafDecoders, theType)
|
|
} else {
|
|
leafDecoders[theType] = decoder
|
|
}
|
|
}
|
|
|
|
// LeafDecoder is to be provided (via RegisterLeafDecoder above)
|
|
// by additional wrapper types not yet known to this library.
|
|
// A nil return indicates that decoding was not successful.
|
|
type LeafDecoder = func(ctx context.Context, msg string, safeDetails []string, payload proto.Message) error
|
|
|
|
// registry for RegisterLeafDecoder.
|
|
var leafDecoders = map[TypeKey]LeafDecoder{}
|
|
|
|
// RegisterWrapperDecoder can be used to register new wrapper types to
|
|
// the library. Registered wrappers will be decoded using their own
|
|
// Go type when an error is decoded. Wrappers that have not been
|
|
// registered will be decoded 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 RegisterWrapperDecoder().
|
|
func RegisterWrapperDecoder(theType TypeKey, decoder WrapperDecoder) {
|
|
if decoder == nil {
|
|
delete(decoders, theType)
|
|
} else {
|
|
decoders[theType] = decoder
|
|
}
|
|
}
|
|
|
|
// WrapperDecoder is to be provided (via RegisterWrapperDecoder above)
|
|
// by additional wrapper types not yet known to this library.
|
|
// A nil return indicates that decoding was not successful.
|
|
type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, safeDetails []string, payload proto.Message) error
|
|
|
|
// registry for RegisterWrapperType.
|
|
var decoders = map[TypeKey]WrapperDecoder{}
|
|
|
|
// MultiCauseDecoder is to be provided (via RegisterMultiCauseDecoder
|
|
// above) by additional multi-cause wrapper types not yet known by the
|
|
// library. A nil return indicates that decoding was not successful.
|
|
type MultiCauseDecoder = func(ctx context.Context, causes []error, msgPrefix string, safeDetails []string, payload proto.Message) error
|
|
|
|
// registry for RegisterMultiCauseDecoder.
|
|
var multiCauseDecoders = map[TypeKey]MultiCauseDecoder{}
|
|
|
|
// RegisterMultiCauseDecoder can be used to register new multi-cause
|
|
// wrapper types to the library. Registered wrappers will be decoded
|
|
// using their own Go type when an error is decoded. Multi-cause
|
|
// wrappers that have not been registered will be decoded using the
|
|
// opaqueWrapper type.
|
|
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder) {
|
|
if decoder == nil {
|
|
delete(multiCauseDecoders, theType)
|
|
} else {
|
|
multiCauseDecoders[theType] = decoder
|
|
}
|
|
}
|