package stoneerror import ( "encoding/json" "errors" "fmt" "regexp" "time" ) // StoneError represents a structured error type with metadata and nested errors. // Code indicates the error code for categorization. // Message describes the error in a human-readable format. // Time specifies when the error occurred. // Metadata provides additional contextual data for the error. // Err holds an underlying error causing the StoneError. type StoneError struct { Code int `json:"code"` Message string `json:"message"` Time time.Time `json:"time"` Metadata map[string]interface{} `json:"metadata"` Err error `json:"-"` } // Error returns the formatted string representation of the StoneError. func (e *StoneError) Error() string { var errorStr string errorStr = fmt.Sprintf("[%d] %s", e.Code, e.Message) if len(e.Metadata) > 0 { errorStr += "\n Metadata:" for k, v := range e.Metadata { errorStr += fmt.Sprintf("\n %s: %v", k, v) } } if e.Err != nil { errorStr += fmt.Sprintf("\n Caused by: %v", e.Err) var nestedStoneErr *StoneError if errors.As(e.Err, &nestedStoneErr) { errorStr += "\n" + indentString(nestedStoneErr.Error(), " ") } } return errorStr } // New creates a new instance of StoneError with the provided code and message. // It initializes the time to the current UTC time and sets Metadata as an empty map. func New(code int, message string) *StoneError { return &StoneError{ Code: code, Message: message, Time: time.Now().UTC(), Metadata: make(map[string]interface{}), } } // Wrap creates a new StoneError with a code, message, and wrapped error. func Wrap(err error, code int, message string) *StoneError { return &StoneError{ Code: code, Message: message, Time: time.Now().UTC(), Err: err, Metadata: make(map[string]interface{}), } } // Unwrap returns the underlying error wrapped by the StoneError instance. func (e *StoneError) Unwrap() error { return e.Err } // WithMetadata adds a key-value pair to the error's metadata and returns the error. func (e *StoneError) WithMetadata(key string, value interface{}) *StoneError { if e.Metadata == nil { e.Metadata = make(map[string]interface{}) } e.Metadata[key] = value return e } // ToJSON serializes the StoneError object into a JSON RawMessage. func (e *StoneError) ToJSON() json.RawMessage { jsonData, _ := json.Marshal(e) return jsonData } // IsStoneError checks if the given error is of type *StoneError. func IsStoneError(err error) bool { var stoneError *StoneError as := errors.As(err, &stoneError) return as } // indentString prepends each line of string s with the given indent string. func indentString(s string, indent string) string { return indent + regexp.MustCompile("\n").ReplaceAllString(s, "\n"+indent) }