spark-server/internal/pkg/errs/apperror.go

128 lines
3.4 KiB
Go

package errs
import (
"context"
"errors"
"fmt"
)
// AppError is the process-wide error type. Transport maps Code/Msg into the {code,msg,request_id,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))
}
}
// FromDependencyError maps Redis/MySQL (or similar) errors for HTTP/TCP auth and token flows.
// context.Canceled usually means the client disconnected before the dependency returned.
func FromDependencyError(serviceName string, err error) *AppError {
if err == nil {
return nil
}
if app, ok := AsAppError(err); ok {
return app
}
switch {
case errors.Is(err, context.Canceled):
return ErrClosed.WithInnerMsg(fmt.Sprintf("[%s] canceled", serviceName))
case errors.Is(err, context.DeadlineExceeded):
return ErrTimeout.WithInnerMsg(fmt.Sprintf("[%s] timeout", serviceName))
default:
return ErrAvailable.WithError(serviceName, err)
}
}