215 lines
6.8 KiB
Go
215 lines
6.8 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"
|
|
goErr "errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/cockroachdb/errors/errorspb"
|
|
"github.com/gogo/protobuf/proto"
|
|
pkgErr "github.com/pkg/errors"
|
|
)
|
|
|
|
// This file provides the library the ability to encode/decode
|
|
// standard error types.
|
|
|
|
// errors.errorString from base Go does not need an encoding
|
|
// function, because the base encoding logic in EncodeLeaf() is
|
|
// able to extract everything about it.
|
|
|
|
// we can then decode it exactly.
|
|
func decodeErrorString(_ context.Context, msg string, _ []string, _ proto.Message) error {
|
|
return goErr.New(msg)
|
|
}
|
|
|
|
// context.DeadlineExceeded uses a custom type.
|
|
func decodeDeadlineExceeded(_ context.Context, _ string, _ []string, _ proto.Message) error {
|
|
return context.DeadlineExceeded
|
|
}
|
|
|
|
// errors.fundamental from github.com/pkg/errors cannot be encoded
|
|
// exactly because it includes a non-serializable stack trace
|
|
// object. In order to work with it, we encode it by dumping
|
|
// the stack trace in a safe reporting detail field, and decode
|
|
// it as an opaqueLeaf instance in this package.
|
|
|
|
func encodePkgFundamental(
|
|
_ context.Context, err error,
|
|
) (msg string, safe []string, _ proto.Message) {
|
|
msg = err.Error()
|
|
iErr := err.(interface{ StackTrace() pkgErr.StackTrace })
|
|
safeDetails := []string{fmt.Sprintf("%+v", iErr.StackTrace())}
|
|
return msg, safeDetails, nil
|
|
}
|
|
|
|
// errors.withMessage from github.com/pkg/errors can be encoded
|
|
// exactly because it just has a message prefix. The base encoding
|
|
// logic in EncodeWrapper() is able to extract everything from it.
|
|
|
|
// we can then decode it exactly.
|
|
func decodeWithMessage(
|
|
_ context.Context, cause error, msgPrefix string, _ []string, _ proto.Message,
|
|
) error {
|
|
return pkgErr.WithMessage(cause, msgPrefix)
|
|
}
|
|
|
|
// errors.withStack from github.com/pkg/errors cannot be encoded
|
|
// exactly because it includes a non-serializable stack trace
|
|
// object. In order to work with it, we encode it by dumping
|
|
// the stack trace in a safe reporting detail field, and decode
|
|
// it as an opaqueWrapper instance in this package.
|
|
|
|
func encodePkgWithStack(
|
|
_ context.Context, err error,
|
|
) (msgPrefix string, safe []string, _ proto.Message) {
|
|
iErr := err.(interface{ StackTrace() pkgErr.StackTrace })
|
|
safeDetails := []string{fmt.Sprintf("%+v", iErr.StackTrace())}
|
|
return "" /* withStack does not have a message prefix */, safeDetails, nil
|
|
}
|
|
|
|
func encodePathError(
|
|
_ context.Context, err error,
|
|
) (msgPrefix string, safe []string, details proto.Message) {
|
|
p := err.(*os.PathError)
|
|
msg := p.Op + " " + p.Path
|
|
details = &errorspb.StringsPayload{
|
|
Details: []string{p.Op, p.Path},
|
|
}
|
|
return msg, []string{p.Op}, details
|
|
}
|
|
|
|
func decodePathError(
|
|
_ context.Context, cause error, _ string, _ []string, payload proto.Message,
|
|
) (result error) {
|
|
m, ok := payload.(*errorspb.StringsPayload)
|
|
if !ok || len(m.Details) < 2 {
|
|
// If this ever happens, this means some version of the library
|
|
// (presumably future) changed the payload type, and we're
|
|
// receiving this here. In this case, give up and let
|
|
// DecodeError use the opaque type.
|
|
return nil
|
|
}
|
|
return &os.PathError{
|
|
Op: m.Details[0],
|
|
Path: m.Details[1],
|
|
Err: cause,
|
|
}
|
|
}
|
|
|
|
func encodeLinkError(
|
|
_ context.Context, err error,
|
|
) (msgPrefix string, safe []string, details proto.Message) {
|
|
p := err.(*os.LinkError)
|
|
msg := p.Op + " " + p.Old + " " + p.New
|
|
details = &errorspb.StringsPayload{
|
|
Details: []string{p.Op, p.Old, p.New},
|
|
}
|
|
return msg, []string{p.Op}, details
|
|
}
|
|
|
|
func decodeLinkError(
|
|
_ context.Context, cause error, _ string, _ []string, payload proto.Message,
|
|
) (result error) {
|
|
m, ok := payload.(*errorspb.StringsPayload)
|
|
if !ok || len(m.Details) < 3 {
|
|
// If this ever happens, this means some version of the library
|
|
// (presumably future) changed the payload type, and we're
|
|
// receiving this here. In this case, give up and let
|
|
// DecodeError use the opaque type.
|
|
return nil
|
|
}
|
|
return &os.LinkError{
|
|
Op: m.Details[0],
|
|
Old: m.Details[1],
|
|
New: m.Details[2],
|
|
Err: cause,
|
|
}
|
|
}
|
|
|
|
func encodeSyscallError(
|
|
_ context.Context, err error,
|
|
) (msgPrefix string, safe []string, details proto.Message) {
|
|
p := err.(*os.SyscallError)
|
|
return p.Syscall, nil, nil
|
|
}
|
|
|
|
func decodeSyscallError(
|
|
_ context.Context, cause error, msg string, _ []string, _ proto.Message,
|
|
) (result error) {
|
|
return os.NewSyscallError(msg, cause)
|
|
}
|
|
|
|
// OpaqueErrno represents a syscall.Errno error object that
|
|
// was constructed on a different OS/platform combination.
|
|
type OpaqueErrno struct {
|
|
msg string
|
|
details *errorspb.ErrnoPayload
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (o *OpaqueErrno) Error() string { return o.msg }
|
|
|
|
// Is tests whether this opaque errno object represents a special os error type.
|
|
func (o *OpaqueErrno) Is(target error) bool {
|
|
return (target == os.ErrPermission && o.details.IsPermission) ||
|
|
(target == os.ErrExist && o.details.IsExist) ||
|
|
(target == os.ErrNotExist && o.details.IsNotExist)
|
|
}
|
|
|
|
// Temporary tests whether this opaque errno object encodes a temporary error.
|
|
func (o *OpaqueErrno) Temporary() bool { return o.details.IsTemporary }
|
|
|
|
// Timeout tests whether this opaque errno object encodes a timeout error.
|
|
func (o *OpaqueErrno) Timeout() bool { return o.details.IsTimeout }
|
|
|
|
func encodeOpaqueErrno(
|
|
_ context.Context, err error,
|
|
) (msg string, safe []string, payload proto.Message) {
|
|
e := err.(*OpaqueErrno)
|
|
return e.Error(), []string{e.Error()}, e.details
|
|
}
|
|
|
|
func init() {
|
|
baseErr := goErr.New("")
|
|
RegisterLeafDecoder(GetTypeKey(baseErr), decodeErrorString)
|
|
|
|
RegisterLeafDecoder(GetTypeKey(context.DeadlineExceeded), decodeDeadlineExceeded)
|
|
|
|
pkgE := pkgErr.New("")
|
|
RegisterLeafEncoder(GetTypeKey(pkgE), encodePkgFundamental)
|
|
|
|
RegisterWrapperDecoder(GetTypeKey(pkgErr.WithMessage(baseErr, "")), decodeWithMessage)
|
|
|
|
ws := pkgErr.WithStack(baseErr)
|
|
RegisterWrapperEncoder(GetTypeKey(ws), encodePkgWithStack)
|
|
|
|
registerOsPathErrorMigration() // Needed for Go 1.16.
|
|
pKey := GetTypeKey(&os.PathError{})
|
|
RegisterWrapperEncoder(pKey, encodePathError)
|
|
RegisterWrapperDecoder(pKey, decodePathError)
|
|
|
|
pKey = GetTypeKey(&os.LinkError{})
|
|
RegisterWrapperEncoder(pKey, encodeLinkError)
|
|
RegisterWrapperDecoder(pKey, decodeLinkError)
|
|
pKey = GetTypeKey(&os.SyscallError{})
|
|
RegisterWrapperEncoder(pKey, encodeSyscallError)
|
|
RegisterWrapperDecoder(pKey, decodeSyscallError)
|
|
|
|
RegisterLeafEncoder(GetTypeKey(&OpaqueErrno{}), encodeOpaqueErrno)
|
|
}
|