package zerr import ( "context" "fmt" "log/slog" "strconv" "strings" "time" "connectrpc.com/connect" "gitlab.bareksa.com/backend/zen/core/zoptions" ) type Err struct { message string publicMessage string code connect.Code errs []error caller uintptr details []any time time.Time id string logger Logger notifier Notifier sequence *Sequence } func (mu *Err) Error() string { if len(mu.errs) == 0 { return "[nil]" } s := strings.Builder{} if len(mu.errs) == 1 { str := getNestedErrorString(mu.errs[0]) str, found := strings.CutPrefix(str, mu.message) if found { str, _ = strings.CutPrefix(str, ": ") } s.WriteString(mu.message) s.WriteString(": ") s.WriteString(str) return s.String() } s.WriteString(mu.message) for i, e := range mu.errs { if i > 0 { s.WriteString("\n") } s.WriteString(strconv.Itoa(i + 1)) s.WriteString(". ") s.WriteString(getNestedErrorString(e)) } return s.String() } func getNestedErrorString(err error) string { if err == nil { return "[nil]" } s := strings.Builder{} if e, ok := err.(interface{ Unwrap() error }); ok { current := err.Error() if err := e.Unwrap(); err != nil { next := getNestedErrorString(err) hasPrefix := strings.HasPrefix(next, current) if !hasPrefix { s.WriteString(current) s.WriteString(": ") } s.WriteString(next) } } if errs, ok := err.(interface{ Unwrap() []error }); ok { for i, e := range errs.Unwrap() { if i > 0 { s.WriteString("\n") } s.WriteString(strconv.Itoa(i + 1)) s.WriteString(". ") s.WriteString(getNestedErrorString(e)) } } if s.Len() == 0 { s.WriteString(err.Error()) } return s.String() } // LogValue returns log fields to be consumed by logger. func (mu *Err) LogValue() slog.Value { attrs := make([]slog.Attr, 0, len(mu.errs)) i := 0 for _, err := range mu.errs { if err == nil { continue } i++ attrs = append(attrs, slog.Attr{ Key: strconv.Itoa(i), Value: errorValuer{err}.LogValue(), }) } return slog.GroupValue(attrs...) } // Code sets error code. Ignored in Multi. func (mu *Err) Code(code connect.Code) Error { mu.code = code return mu } // GetCode Always returns 0 in Multi. func (mu *Err) GetCode() connect.Code { return mu.code } // Message sets error message. It uses fmt.Sprintf to format the message. func (mu *Err) Message(msg string, args ...any) Error { mu.message = fmt.Sprintf(msg, args...) return mu } func (mu *Err) GetMessage() string { return mu.message } func (mu *Err) PublicMessage(msg string, args ...any) Error { mu.publicMessage = fmt.Sprintf(msg, args...) return mu } func (mu *Err) GetPublicMessage() string { return mu.publicMessage } func (mu *Err) Caller(pc uintptr) Error { mu.caller = pc return mu } func (mu *Err) GetCaller() uintptr { return mu.caller } // Details sets error details. It's a key-value pair where the odd index is the key and the even index is the value. // // Invalid key-value pair number and format will cause the misplaced value to be paired up with "!BADKEY". // // Example: // // err.Details( // "key1", 12345, // "key2", float64(12345.67), // ) func (mu *Err) Details(fields ...any) Error { mu.details = fields return mu } func (mu *Err) GetDetails() []any { return mu.details } // Time sets the error time. // // Time is already set to current when the error is created or wrapped, // so this method is only useful when you want to set a different time. func (mu *Err) Time(t time.Time) Error { mu.time = t return mu } func (mu *Err) GetTime() time.Time { return mu.time } // ID sets the error ID. // // ID is used to identify the error. Used by Zen to consider if an error is the same as another. // // Current implementation func (mu *Err) ID(msg string, args ...any) Error { mu.id = fmt.Sprintf(msg, args...) return mu } func (mu *Err) GetID() string { return mu.id } func (mu *Err) Log(ctx context.Context) Error { mu.logger.Log(ctx, mu) return mu } func (mu *Err) Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error { mu.notifier.Notify(ctx, mu, opts...) return mu } // Sequence returns the state of the error sequence. func (mu *Err) Sequence() *Sequence { return mu.sequence } func (mu *Err) Unwrap() []error { return mu.errs }