234 lines
4.2 KiB
Go
234 lines
4.2 KiB
Go
package grpcerrors
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/containerd/typeurl"
|
|
gogotypes "github.com/gogo/protobuf/types"
|
|
"github.com/golang/protobuf/proto" // nolint:staticcheck
|
|
"github.com/golang/protobuf/ptypes/any"
|
|
"github.com/moby/buildkit/util/stack"
|
|
"github.com/sirupsen/logrus"
|
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
type TypedError interface {
|
|
ToProto() TypedErrorProto
|
|
}
|
|
|
|
type TypedErrorProto interface {
|
|
proto.Message
|
|
WrapError(error) error
|
|
}
|
|
|
|
func ToGRPC(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
st, ok := AsGRPCStatus(err)
|
|
if !ok || st == nil {
|
|
st = status.New(Code(err), err.Error())
|
|
}
|
|
if st.Code() != Code(err) {
|
|
code := Code(err)
|
|
if code == codes.OK {
|
|
code = codes.Unknown
|
|
}
|
|
pb := st.Proto()
|
|
pb.Code = int32(code)
|
|
st = status.FromProto(pb)
|
|
}
|
|
|
|
var details []proto.Message
|
|
|
|
for _, st := range stack.Traces(err) {
|
|
details = append(details, st)
|
|
}
|
|
|
|
each(err, func(err error) {
|
|
if te, ok := err.(TypedError); ok {
|
|
details = append(details, te.ToProto())
|
|
}
|
|
})
|
|
|
|
if len(details) > 0 {
|
|
if st2, err := withDetails(st, details...); err == nil {
|
|
st = st2
|
|
}
|
|
}
|
|
|
|
return st.Err()
|
|
}
|
|
|
|
func withDetails(s *status.Status, details ...proto.Message) (*status.Status, error) {
|
|
if s.Code() == codes.OK {
|
|
return nil, errors.New("no error details for status with code OK")
|
|
}
|
|
p := s.Proto()
|
|
for _, detail := range details {
|
|
url, err := typeurl.TypeURL(detail)
|
|
if err != nil {
|
|
logrus.Warnf("ignoring typed error %T: not registered", detail)
|
|
continue
|
|
}
|
|
dt, err := json.Marshal(detail)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Details = append(p.Details, &any.Any{TypeUrl: url, Value: dt})
|
|
}
|
|
return status.FromProto(p), nil
|
|
}
|
|
|
|
func Code(err error) codes.Code {
|
|
if se, ok := err.(interface {
|
|
Code() codes.Code
|
|
}); ok {
|
|
return se.Code()
|
|
}
|
|
|
|
if se, ok := err.(interface {
|
|
GRPCStatus() *status.Status
|
|
}); ok {
|
|
return se.GRPCStatus().Code()
|
|
}
|
|
|
|
wrapped, ok := err.(interface {
|
|
Unwrap() error
|
|
})
|
|
if ok {
|
|
if err := wrapped.Unwrap(); err != nil {
|
|
return Code(err)
|
|
}
|
|
}
|
|
return status.FromContextError(err).Code()
|
|
}
|
|
|
|
func WrapCode(err error, code codes.Code) error {
|
|
return &withCode{error: err, code: code}
|
|
}
|
|
|
|
func AsGRPCStatus(err error) (*status.Status, bool) {
|
|
if err == nil {
|
|
return nil, true
|
|
}
|
|
if se, ok := err.(interface {
|
|
GRPCStatus() *status.Status
|
|
}); ok {
|
|
return se.GRPCStatus(), true
|
|
}
|
|
|
|
wrapped, ok := err.(interface {
|
|
Unwrap() error
|
|
})
|
|
if ok {
|
|
if err := wrapped.Unwrap(); err != nil {
|
|
return AsGRPCStatus(err)
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func FromGRPC(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
pb := st.Proto()
|
|
|
|
n := &spb.Status{
|
|
Code: pb.Code,
|
|
Message: pb.Message,
|
|
}
|
|
|
|
details := make([]TypedErrorProto, 0, len(pb.Details))
|
|
stacks := make([]*stack.Stack, 0, len(pb.Details))
|
|
|
|
// details that we don't understand are copied as proto
|
|
for _, d := range pb.Details {
|
|
m, err := typeurl.UnmarshalAny(gogoAny(d))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
switch v := m.(type) {
|
|
case *stack.Stack:
|
|
stacks = append(stacks, v)
|
|
case TypedErrorProto:
|
|
details = append(details, v)
|
|
default:
|
|
n.Details = append(n.Details, d)
|
|
}
|
|
}
|
|
|
|
err = &grpcStatusError{st: status.FromProto(n)}
|
|
|
|
for _, s := range stacks {
|
|
if s != nil {
|
|
err = stack.Wrap(err, *s)
|
|
}
|
|
}
|
|
|
|
for _, d := range details {
|
|
err = d.WrapError(err)
|
|
}
|
|
|
|
if err != nil {
|
|
stack.Helper()
|
|
}
|
|
|
|
return stack.Enable(err)
|
|
}
|
|
|
|
type grpcStatusError struct {
|
|
st *status.Status
|
|
}
|
|
|
|
func (e *grpcStatusError) Error() string {
|
|
if e.st.Code() == codes.OK || e.st.Code() == codes.Unknown {
|
|
return e.st.Message()
|
|
}
|
|
return e.st.Code().String() + ": " + e.st.Message()
|
|
}
|
|
|
|
func (e *grpcStatusError) GRPCStatus() *status.Status {
|
|
return e.st
|
|
}
|
|
|
|
type withCode struct {
|
|
code codes.Code
|
|
error
|
|
}
|
|
|
|
func (e *withCode) Code() codes.Code {
|
|
return e.code
|
|
}
|
|
|
|
func (e *withCode) Unwrap() error {
|
|
return e.error
|
|
}
|
|
|
|
func each(err error, fn func(error)) {
|
|
fn(err)
|
|
if wrapped, ok := err.(interface {
|
|
Unwrap() error
|
|
}); ok {
|
|
each(wrapped.Unwrap(), fn)
|
|
}
|
|
}
|
|
|
|
func gogoAny(in *any.Any) *gogotypes.Any {
|
|
return &gogotypes.Any{
|
|
TypeUrl: in.TypeUrl,
|
|
Value: in.Value,
|
|
}
|
|
}
|