You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.3 KiB
152 lines
4.3 KiB
6 years ago
|
package status
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/golang/protobuf/proto"
|
||
|
"github.com/golang/protobuf/ptypes"
|
||
|
"github.com/pkg/errors"
|
||
|
"google.golang.org/grpc/codes"
|
||
|
"google.golang.org/grpc/status"
|
||
|
|
||
|
"github.com/bilibili/kratos/pkg/ecode"
|
||
|
"github.com/bilibili/kratos/pkg/ecode/pb"
|
||
|
)
|
||
|
|
||
|
// togRPCCode convert ecode.Codo to gRPC code
|
||
|
func togRPCCode(code ecode.Codes) codes.Code {
|
||
|
switch code.Code() {
|
||
|
case ecode.OK.Code():
|
||
|
return codes.OK
|
||
|
case ecode.RequestErr.Code():
|
||
|
return codes.InvalidArgument
|
||
|
case ecode.NothingFound.Code():
|
||
|
return codes.NotFound
|
||
|
case ecode.Unauthorized.Code():
|
||
|
return codes.Unauthenticated
|
||
|
case ecode.AccessDenied.Code():
|
||
|
return codes.PermissionDenied
|
||
|
case ecode.LimitExceed.Code():
|
||
|
return codes.ResourceExhausted
|
||
|
case ecode.MethodNotAllowed.Code():
|
||
|
return codes.Unimplemented
|
||
|
case ecode.Deadline.Code():
|
||
|
return codes.DeadlineExceeded
|
||
|
case ecode.ServiceUnavailable.Code():
|
||
|
return codes.Unavailable
|
||
|
}
|
||
|
return codes.Unknown
|
||
|
}
|
||
|
|
||
|
func toECode(gst *status.Status) ecode.Code {
|
||
|
gcode := gst.Code()
|
||
|
switch gcode {
|
||
|
case codes.OK:
|
||
|
return ecode.OK
|
||
|
case codes.InvalidArgument:
|
||
|
return ecode.RequestErr
|
||
|
case codes.NotFound:
|
||
|
return ecode.NothingFound
|
||
|
case codes.PermissionDenied:
|
||
|
return ecode.AccessDenied
|
||
|
case codes.Unauthenticated:
|
||
|
return ecode.Unauthorized
|
||
|
case codes.ResourceExhausted:
|
||
|
return ecode.LimitExceed
|
||
|
case codes.Unimplemented:
|
||
|
return ecode.MethodNotAllowed
|
||
|
case codes.DeadlineExceeded:
|
||
|
return ecode.Deadline
|
||
|
case codes.Unavailable:
|
||
|
return ecode.ServiceUnavailable
|
||
|
case codes.Unknown:
|
||
|
return ecode.String(gst.Message())
|
||
|
}
|
||
|
return ecode.ServerErr
|
||
|
}
|
||
|
|
||
|
// FromError convert error for service reply and try to convert it to grpc.Status.
|
||
|
func FromError(svrErr error) (gst *status.Status) {
|
||
|
var err error
|
||
|
svrErr = errors.Cause(svrErr)
|
||
|
if code, ok := svrErr.(ecode.Codes); ok {
|
||
|
// TODO: deal with err
|
||
|
if gst, err = gRPCStatusFromEcode(code); err == nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
// for some special error convert context.Canceled to ecode.Canceled,
|
||
|
// context.DeadlineExceeded to ecode.DeadlineExceeded only for raw error
|
||
|
// if err be wrapped will not effect.
|
||
|
switch svrErr {
|
||
|
case context.Canceled:
|
||
|
gst, _ = gRPCStatusFromEcode(ecode.Canceled)
|
||
|
case context.DeadlineExceeded:
|
||
|
gst, _ = gRPCStatusFromEcode(ecode.Deadline)
|
||
|
default:
|
||
|
gst, _ = status.FromError(svrErr)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func gRPCStatusFromEcode(code ecode.Codes) (*status.Status, error) {
|
||
|
var st *ecode.Status
|
||
|
switch v := code.(type) {
|
||
|
// compatible old pb.Error remove it after nobody use pb.Error.
|
||
|
case *pb.Error:
|
||
|
return status.New(codes.Unknown, v.Error()).WithDetails(v)
|
||
|
case *ecode.Status:
|
||
|
st = v
|
||
|
case ecode.Code:
|
||
|
st = ecode.FromCode(v)
|
||
|
default:
|
||
|
st = ecode.Error(ecode.Code(code.Code()), code.Message())
|
||
|
for _, detail := range code.Details() {
|
||
|
if msg, ok := detail.(proto.Message); ok {
|
||
|
st.WithDetails(msg)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// gst := status.New(togRPCCode(st), st.Message())
|
||
|
// NOTE: compatible with PHP swoole gRPC put code in status message as string.
|
||
|
// gst := status.New(togRPCCode(st), strconv.Itoa(st.Code()))
|
||
|
gst := status.New(codes.Unknown, strconv.Itoa(st.Code()))
|
||
|
pbe := &pb.Error{ErrCode: int32(st.Code()), ErrMessage: gst.Message()}
|
||
|
// NOTE: server return ecode.Status will be covert to pb.Error details will be ignored
|
||
|
// and put it at details[0] for compatible old client
|
||
|
return gst.WithDetails(pbe, st.Proto())
|
||
|
}
|
||
|
|
||
|
// ToEcode convert grpc.status to ecode.Codes
|
||
|
func ToEcode(gst *status.Status) ecode.Codes {
|
||
|
details := gst.Details()
|
||
|
// reverse range details, details may contain three case,
|
||
|
// if details contain pb.Error and ecode.Status use eocde.Status first.
|
||
|
//
|
||
|
// Details layout:
|
||
|
// pb.Error [0: pb.Error]
|
||
|
// both pb.Error and ecode.Status [0: pb.Error, 1: ecode.Status]
|
||
|
// ecode.Status [0: ecode.Status]
|
||
|
for i := len(details) - 1; i >= 0; i-- {
|
||
|
detail := details[i]
|
||
|
// compatible with old pb.Error.
|
||
|
if pe, ok := detail.(*pb.Error); ok {
|
||
|
st := ecode.Error(ecode.Code(pe.ErrCode), pe.ErrMessage)
|
||
|
if pe.ErrDetail != nil {
|
||
|
dynMsg := new(ptypes.DynamicAny)
|
||
|
// TODO deal with unmarshalAny error.
|
||
|
if err := ptypes.UnmarshalAny(pe.ErrDetail, dynMsg); err == nil {
|
||
|
st, _ = st.WithDetails(dynMsg.Message)
|
||
|
}
|
||
|
}
|
||
|
return st
|
||
|
}
|
||
|
// convert detail to status only use first detail
|
||
|
if pb, ok := detail.(proto.Message); ok {
|
||
|
return ecode.FromProto(pb)
|
||
|
}
|
||
|
}
|
||
|
return toECode(gst)
|
||
|
}
|