package errs import ( "context" "errors" "fmt" ) // AppError is the process-wide error type. Transport maps Code/Msg into the {code,msg,data} envelope. // InnerMsg is log-only and must not be sent to clients. type AppError struct { Code string `json:"code"` Msg string `json:"msg"` InnerMsg string `json:"inner_msg"` LogPrint bool `json:"log_print"` custom bool } func (e *AppError) Error() string { if e == nil { return "" } return fmt.Sprintf("[%s]:%s:%s", e.Code, e.Msg, e.InnerMsg) } func NewError(code, msg, innerMsg string) *AppError { return &AppError{Code: code, Msg: msg, InnerMsg: innerMsg} } func NewErrorPrint(code, msg, innerMsg string) *AppError { return &AppError{Code: code, Msg: msg, InnerMsg: innerMsg, LogPrint: true} } func (e *AppError) CodeMsg() (string, string, string) { if e == nil { return "", "", "" } return e.Code, e.Msg, e.InnerMsg } // WithMsg sets the user-visible message. func (e *AppError) WithMsg(msg string) *AppError { if e == nil { return nil } return &AppError{Code: e.Code, Msg: msg, InnerMsg: e.InnerMsg, LogPrint: e.LogPrint, custom: true} } // CustomMsg is true when Msg was set by WithMsg and must not be replaced by i18n. func (e *AppError) CustomMsg() bool { return e != nil && e.custom && e.Msg != "" } // WithInnerMsg sets the log-only internal message. func (e *AppError) WithInnerMsg(innerMsg string) *AppError { if e == nil { return nil } return &AppError{Code: e.Code, Msg: e.Msg, InnerMsg: innerMsg, LogPrint: e.LogPrint, custom: e.custom} } // WithError sets InnerMsg from a downstream error. func (e *AppError) WithError(serviceName string, err error) *AppError { if e == nil { return nil } return &AppError{ Code: e.Code, Msg: e.Msg, InnerMsg: fmt.Sprintf("[%s] err:%v", serviceName, err), LogPrint: e.LogPrint, custom: e.custom, } } func (e *AppError) Is(target error) bool { t, ok := target.(*AppError) if !ok || e == nil || t == nil { return false } return e.Code == t.Code } func AsAppError(err error) (*AppError, bool) { var app *AppError if errors.As(err, &app) && app != nil { return app, true } return nil, false } // FromRPCError maps a downstream call error. This process has no gRPC; context and AppError are used. func FromRPCError(serviceName string, err error) *AppError { if err == nil { return nil } if app, ok := AsAppError(err); ok { return app } switch { case errors.Is(err, context.DeadlineExceeded): return ErrTimeout.WithInnerMsg(fmt.Sprintf("[%s] timeout", serviceName)) case errors.Is(err, context.Canceled): return ErrOperatFail.WithInnerMsg(fmt.Sprintf("[%s] canceled", serviceName)) default: return ErrInner.WithInnerMsg(fmt.Sprintf("[%s] unexpected error: %v", serviceName, err)) } }