267 lines
5.5 KiB
Go
267 lines
5.5 KiB
Go
package errors
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"runtime"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
// Core error interface
|
|
type Error interface {
|
|
Error() string
|
|
Code() string
|
|
Category() string
|
|
HTTPStatus() int
|
|
GRPCCode() codes.Code
|
|
Metadata() map[string]interface{}
|
|
Cause() error
|
|
GetLocalizedMessage(lang string) string
|
|
WithMetadata(key string, value interface{}) Error
|
|
WithCause(err error) Error
|
|
}
|
|
|
|
// Metadata type for error context
|
|
type Metadata map[string]interface{}
|
|
|
|
// Core error implementation
|
|
type AppError struct {
|
|
code string
|
|
message string
|
|
category string
|
|
httpStatus int
|
|
grpcCode codes.Code
|
|
metadata Metadata
|
|
cause error
|
|
timestamp time.Time
|
|
stackTrace []string
|
|
}
|
|
|
|
// New creates a new error
|
|
func New(message string) Error {
|
|
return &AppError{
|
|
code: ErrCodeInternalError,
|
|
message: message,
|
|
category: CategoryInternal,
|
|
httpStatus: http.StatusInternalServerError,
|
|
grpcCode: codes.Internal,
|
|
metadata: make(Metadata),
|
|
timestamp: time.Now(),
|
|
stackTrace: captureStackTrace(),
|
|
}
|
|
}
|
|
|
|
// NewWithCode creates a new error with code
|
|
func NewWithCode(code, message string) Error {
|
|
info := GetErrorInfo(code)
|
|
if info.Code == "" {
|
|
info = ErrorInfo{
|
|
Code: code,
|
|
Category: CategoryInternal,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
GRPCCode: codes.Internal,
|
|
}
|
|
}
|
|
|
|
return &AppError{
|
|
code: code,
|
|
message: message,
|
|
category: info.Category,
|
|
httpStatus: info.HTTPStatus,
|
|
grpcCode: info.GRPCCode,
|
|
metadata: make(Metadata),
|
|
timestamp: time.Now(),
|
|
stackTrace: captureStackTrace(),
|
|
}
|
|
}
|
|
|
|
// NewWithMetadata creates a new error with metadata
|
|
func NewWithMetadata(code, message string, metadata Metadata) Error {
|
|
err := NewWithCode(code, message)
|
|
err.(*AppError).metadata = metadata
|
|
return err
|
|
}
|
|
|
|
// NewWithType creates a new error with custom type
|
|
func NewWithType(errorType string, metadata Metadata) Error {
|
|
template := GetErrorTemplate(errorType)
|
|
if template == nil {
|
|
return NewWithMetadata(ErrCodeInternalError, "Unknown error type", metadata)
|
|
}
|
|
|
|
err := &AppError{
|
|
code: template.Code,
|
|
message: template.Message,
|
|
category: template.Category,
|
|
httpStatus: template.HTTPStatus,
|
|
grpcCode: template.GRPCCode,
|
|
metadata: make(Metadata),
|
|
timestamp: time.Now(),
|
|
stackTrace: captureStackTrace(),
|
|
}
|
|
|
|
for k, v := range metadata {
|
|
err.metadata[k] = v
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Wrap wraps an existing error
|
|
func Wrap(err error, code, message string) Error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if IsAppError(err) {
|
|
return err.(Error)
|
|
}
|
|
|
|
appErr := NewWithCode(code, message)
|
|
appErr.(*AppError).cause = err
|
|
return appErr
|
|
}
|
|
|
|
// FromError converts any error to AppError
|
|
func FromError(err error) Error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if appErr, ok := err.(Error); ok {
|
|
return appErr
|
|
}
|
|
|
|
return Wrap(err, ErrCodeInternalError, err.Error())
|
|
}
|
|
|
|
// IsAppError checks if error is AppError
|
|
func IsAppError(err error) bool {
|
|
_, ok := err.(Error)
|
|
return ok
|
|
}
|
|
|
|
// Error implements error interface
|
|
func (e *AppError) Error() string {
|
|
if e.cause != nil {
|
|
return fmt.Sprintf("%s: %v", e.message, e.cause)
|
|
}
|
|
return e.message
|
|
}
|
|
|
|
// Code returns error code
|
|
func (e *AppError) Code() string {
|
|
return e.code
|
|
}
|
|
|
|
// Category returns error category
|
|
func (e *AppError) Category() string {
|
|
return e.category
|
|
}
|
|
|
|
// HTTPStatus returns HTTP status code
|
|
func (e *AppError) HTTPStatus() int {
|
|
return e.httpStatus
|
|
}
|
|
|
|
// GRPCCode returns gRPC code
|
|
func (e *AppError) GRPCCode() codes.Code {
|
|
return e.grpcCode
|
|
}
|
|
|
|
// Metadata returns error metadata
|
|
func (e *AppError) Metadata() map[string]interface{} {
|
|
return e.metadata
|
|
}
|
|
|
|
// Cause returns underlying error
|
|
func (e *AppError) Cause() error {
|
|
return e.cause
|
|
}
|
|
|
|
// Unwrap implements the standard library unwrap interface for Go 1.13+ error chains
|
|
func (e *AppError) Unwrap() error {
|
|
return e.cause
|
|
}
|
|
|
|
// GetLocalizedMessage returns localized message
|
|
func (e *AppError) GetLocalizedMessage(lang string) string {
|
|
return GetLocalizedMessage(e.code, lang, e.message)
|
|
}
|
|
|
|
// WithMetadata adds metadata to error
|
|
func (e *AppError) WithMetadata(key string, value interface{}) Error {
|
|
if e.metadata == nil {
|
|
e.metadata = make(Metadata)
|
|
}
|
|
e.metadata[key] = value
|
|
return e
|
|
}
|
|
|
|
// WithCause adds cause to error
|
|
func (e *AppError) WithCause(err error) Error {
|
|
e.cause = err
|
|
return e
|
|
}
|
|
|
|
// ToJSON converts error to JSON
|
|
func (e *AppError) ToJSON() ([]byte, error) {
|
|
return json.Marshal(map[string]interface{}{
|
|
"code": e.code,
|
|
"message": e.message,
|
|
"category": e.category,
|
|
"http_status": e.httpStatus,
|
|
"grpc_code": e.grpcCode.String(),
|
|
"metadata": e.metadata,
|
|
"timestamp": e.timestamp,
|
|
"stack_trace": e.stackTrace,
|
|
})
|
|
}
|
|
|
|
// captureStackTrace captures current stack trace
|
|
func captureStackTrace() []string {
|
|
var stack []string
|
|
for i := 2; i < 15; i++ {
|
|
pc, file, line, ok := runtime.Caller(i)
|
|
if !ok {
|
|
break
|
|
}
|
|
fn := runtime.FuncForPC(pc)
|
|
stack = append(stack, fmt.Sprintf("%s:%d %s", file, line, fn.Name()))
|
|
}
|
|
return stack
|
|
}
|
|
|
|
// Is checks if error matches target
|
|
func Is(err, target error) bool {
|
|
if err == target {
|
|
return true
|
|
}
|
|
|
|
if appErr, ok := err.(Error); ok {
|
|
if targetErr, ok := target.(Error); ok {
|
|
return appErr.Code() == targetErr.Code()
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// As finds the first error in err's chain that matches target
|
|
func As(err error, target interface{}) bool {
|
|
if appErr, ok := err.(Error); ok {
|
|
switch t := target.(type) {
|
|
case **AppError:
|
|
*t = appErr.(*AppError)
|
|
return true
|
|
case *Error:
|
|
*t = appErr
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|