zen/core/zerr/implementation.go
2024-08-22 17:26:53 +07:00

213 lines
4.3 KiB
Go

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
}