package errors import ( "errors" "fmt" httpstatus "git.diulo.com/mogfee/kit/transport/http/status" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" ) const ( // UnknownCode is unknown code for error info. UnknownCode = 500 // UnknownReason is unknown reason for error info. UnknownReason = "" ) type Error struct { Status cause error } func (e *Error) Error() string { return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v cause = %v", e.Code, e.Reason, e.Message, e.Metadata, e.cause) } func (e *Error) Unwarp() error { return e.cause } func (e *Error) Is(err error) bool { if se := new(Error); errors.As(err, &se) { return se.Code == e.Code && se.Reason == e.Reason } return false } func (e *Error) WithCause(cause error) *Error { err := Clone(e) err.cause = cause return err } func (e *Error) WithMetadata(md map[string]string) *Error { err := Clone(e) err.Metadata = md return err } func (e *Error) GRPCStatus() *status.Status { s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). WithDetails(&errdetails.ErrorInfo{ Reason: e.Reason, Metadata: e.Metadata, }) return s } func New(code int, reason, message string) *Error { return &Error{ Status: Status{ Code: int32(code), Reason: reason, Message: message, }, } } func Newf(code int, reason string, format string, a ...any) *Error { return New(code, reason, fmt.Sprintf(format, a...)) } func Errorf(code int, reason string, format string, a ...any) *Error { return New(code, reason, fmt.Sprintf(format, a...)) } func Code(err error) int { if err == nil { return 200 } return int(FromError(err).Code) } func Reason(err error) string { if err == nil { return UnknownReason } return FromError(err).Reason } func Clone(err *Error) *Error { if err == nil { return nil } metadata := make(map[string]string, len(err.Metadata)) for k, v := range err.Metadata { metadata[k] = v } return &Error{ cause: err.cause, Status: Status{ Code: err.Code, Reason: err.Reason, Message: err.Message, Metadata: metadata, }, } } func FromError(err error) *Error { if err == nil { return nil } if se := new(Error); errors.As(err, &se) { return se } gs, ok := status.FromError(err) if !ok { return New(UnknownCode, UnknownReason, err.Error()) } ret := New(httpstatus.FromGRPCCode(gs.Code()), UnknownReason, gs.Message()) for _, detail := range gs.Details() { switch d := detail.(type) { case *errdetails.ErrorInfo: ret.Reason = d.Reason return ret.WithMetadata(d.Metadata) } } return ret }