2024-08-22 17:26:53 +07:00
|
|
|
package zerr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2024-08-26 16:24:41 +07:00
|
|
|
"io"
|
2024-08-22 17:26:53 +07:00
|
|
|
"log/slog"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"connectrpc.com/connect"
|
2024-08-26 16:24:41 +07:00
|
|
|
"github.com/pborman/indent"
|
2024-08-22 17:26:53 +07:00
|
|
|
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
|
|
|
)
|
|
|
|
|
2024-08-23 18:41:19 +07:00
|
|
|
var _ Error = (*Err)(nil)
|
|
|
|
|
2024-08-22 17:26:53 +07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-08-26 16:24:41 +07:00
|
|
|
// Join implements Error.
|
|
|
|
func (mu *Err) Join(errs ...error) Error {
|
|
|
|
for _, err := range errs {
|
|
|
|
if err != nil {
|
|
|
|
mu.errs = append(mu.errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mu
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve implements Error.
|
|
|
|
func (mu *Err) Resolve() error {
|
|
|
|
if len(mu.errs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return mu
|
|
|
|
}
|
|
|
|
|
2024-08-22 17:26:53 +07:00
|
|
|
func (mu *Err) Error() string {
|
|
|
|
if len(mu.errs) == 0 {
|
|
|
|
return "[nil]"
|
|
|
|
}
|
2024-08-26 16:24:41 +07:00
|
|
|
s := &strings.Builder{}
|
|
|
|
if mu.message != "" {
|
|
|
|
s.WriteString(mu.message)
|
|
|
|
}
|
2024-08-22 17:26:53 +07:00
|
|
|
if len(mu.errs) == 1 {
|
2024-08-26 16:24:41 +07:00
|
|
|
err := mu.errs[0]
|
|
|
|
if wb, ok := err.(interface{ WriteBuilder(io.Writer) }); ok {
|
|
|
|
wb.WriteBuilder(s)
|
|
|
|
return s.String()
|
|
|
|
}
|
|
|
|
next := strings.TrimPrefix(err.Error(), mu.message)
|
|
|
|
if next == "" {
|
|
|
|
return s.String()
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
|
|
|
s.WriteString(": ")
|
2024-08-26 16:24:41 +07:00
|
|
|
s.WriteString(next)
|
2024-08-22 17:26:53 +07:00
|
|
|
return s.String()
|
|
|
|
}
|
2024-08-26 16:24:41 +07:00
|
|
|
if mu.message != "" {
|
|
|
|
s.WriteString(":\n")
|
|
|
|
}
|
|
|
|
w := indent.New(s, " ")
|
2024-08-22 17:26:53 +07:00
|
|
|
for i, e := range mu.errs {
|
|
|
|
if i > 0 {
|
2024-08-26 16:24:41 +07:00
|
|
|
_, _ = io.WriteString(w, "\n")
|
|
|
|
}
|
|
|
|
_, _ = io.WriteString(w, strconv.Itoa(i+1))
|
|
|
|
_, _ = io.WriteString(w, ". ")
|
|
|
|
if wb, ok := e.(interface{ WriteBuilder(io.Writer) }); ok {
|
|
|
|
wb.WriteBuilder(w)
|
|
|
|
} else {
|
|
|
|
_, _ = io.WriteString(w, e.Error())
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return s.String()
|
|
|
|
}
|
|
|
|
|
2024-08-26 16:24:41 +07:00
|
|
|
// LogValue returns log fields to be consumed by logger.
|
|
|
|
func (mu *Err) LogValue() slog.Value {
|
|
|
|
attrs := make([]slog.Attr, 0, 8)
|
|
|
|
if len(mu.message) > 0 {
|
|
|
|
attrs = append(attrs, slog.String("message", mu.message))
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
2024-08-26 16:24:41 +07:00
|
|
|
if len(mu.publicMessage) > 0 {
|
|
|
|
attrs = append(attrs, slog.String("public", mu.message))
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
2024-08-26 16:24:41 +07:00
|
|
|
if mu.code != connect.CodeUnknown && mu.code != connect.CodeInternal {
|
|
|
|
attrs = append(attrs, slog.String("code", mu.code.String()))
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
2024-08-26 16:24:41 +07:00
|
|
|
if mu.sequence.IsRoot() {
|
|
|
|
}
|
|
|
|
if len(mu.errs) == 1 {
|
2024-08-22 17:26:53 +07:00
|
|
|
}
|
|
|
|
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 {
|
2024-08-23 10:18:27 +07:00
|
|
|
mu.logger.LogError(ctx, mu)
|
2024-08-22 17:26:53 +07:00
|
|
|
return mu
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mu *Err) Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error {
|
2024-08-23 10:18:27 +07:00
|
|
|
mu.notifier.NotifyError(ctx, mu, opts...)
|
2024-08-22 17:26:53 +07:00
|
|
|
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
|
|
|
|
}
|
2024-08-23 18:41:19 +07:00
|
|
|
|
|
|
|
// IsMulti implements Error.
|
|
|
|
func (mu *Err) IsMulti() bool {
|
|
|
|
return len(mu.errs) > 1
|
|
|
|
}
|