package errs import ( "context" "errors" "fmt" "log/slog" "reflect" "strings" "connectrpc.com/connect" "github.com/tigorlazuardi/bluemage/go/pkg/caller" ) type Error interface { error Message(msg string, args ...any) Error GetMessage() string Code(status connect.Code) Error GetCode() connect.Code Caller(pc caller.Caller) Error GetCaller() caller.Caller Details(...any) Error GetDetails() []any Log(ctx context.Context) Error } var ( _ Error = (*Err)(nil) _ slog.LogValuer = (*Err)(nil) ) type Err struct { message string code connect.Code caller caller.Caller details []any origin error } // Unwrap implements the implementation of errors.Unwrap. func (er *Err) Unwrap() error { return er.origin } // LogValue implements slog.LogValuer. func (er *Err) LogValue() slog.Value { values := make([]slog.Attr, 0, 5) if er.message != "" { values = append(values, slog.String("message", er.message)) } if er.code != 0 && er.code != connect.CodeInternal { values = append(values, slog.String("code", er.code.String())) } if er.caller.PC != 0 { values = append(values, slog.Attr{Key: "caller", Value: er.caller.LogValue()}) } if len(er.details) > 0 { values = append(values, slog.Group("details", er.details...)) } if er.origin == nil { values = append(values, slog.Any("error", er.origin)) } else if lv, ok := er.origin.(slog.LogValuer); ok { values = append(values, slog.Attr{Key: "error", Value: lv.LogValue()}) } else { values = append(values, slog.Attr{Key: "error", Value: slog.GroupValue( slog.String("type", reflect.TypeOf(er.origin).String()), slog.String("message", er.origin.Error()), slog.Any("data", er.origin), )}) } return slog.GroupValue(values...) } // Caller implements Error. func (e *Err) Caller(pc caller.Caller) Error { e.caller = pc return e } // Code implements Error. func (e *Err) Code(status connect.Code) Error { e.code = status return e } // Details implements Error. func (e *Err) Details(keysAndValues ...any) Error { e.details = keysAndValues return e } // Error implements Error. // // The error message is constructed by concatenating the message of the error, // discarding any empty and duplicate messages in the chain. // // The error messages are concatenated with a colon and a space. func (e *Err) Error() string { s := strings.Builder{} if e.message != "" { s.WriteString(e.message) if e.origin != nil { s.WriteString(": ") } } unwrap := errors.Unwrap(e) for unwrap != nil { var current string if e, ok := unwrap.(Error); ok && e.GetMessage() != "" { current = e.GetMessage() } else { current = unwrap.Error() } next := errors.Unwrap(unwrap) if next != nil { current, _ = strings.CutSuffix(current, next.Error()) current, _ = strings.CutSuffix(current, ": ") } if current != "" { s.WriteString(current) if next != nil { s.WriteString(": ") } } unwrap = next } return s.String() } // GetCaller implements Error. func (e *Err) GetCaller() caller.Caller { return e.caller } // GetCode implements Error. func (e *Err) GetCode() connect.Code { return e.code } // GetDetails implements Error. func (e *Err) GetDetails() []any { return e.details } // GetMessage implements Error. func (e *Err) GetMessage() string { return e.message } // Log implements Error. func (e *Err) Log(ctx context.Context) Error { panic("unimplemented") } // Message implements Error. func (e *Err) Message(msg string, args ...any) Error { e.message = fmt.Sprintf(msg, args...) return e } func Wrap(err error, message string) Error { return &Err{ origin: err, caller: caller.New(3), message: message, code: connect.CodeInternal, } } func Wrapw(err error, message string, details ...any) Error { return &Err{ origin: err, details: details, message: message, caller: caller.New(3), code: connect.CodeInternal, } } func Wrapf(err error, message string, args ...any) Error { message = fmt.Sprintf(message, args...) return &Err{ origin: err, message: message, caller: caller.New(3), code: connect.CodeInternal, } } func Fail(message string, details ...any) Error { return &Err{ origin: errors.New(message), details: details, caller: caller.New(3), code: connect.CodeInternal, } } func Failf(message string, args ...any) Error { return &Err{ origin: fmt.Errorf(message, args...), caller: caller.New(3), code: connect.CodeInternal, } } func IntoConnectError(err error) error { if err == nil { return nil } if e := FindError(err); e != nil { return connect.NewError(e.GetCode(), e) } return connect.NewError(connect.CodeUnknown, err) } func FindError(err error) Error { for { if e, ok := err.(Error); ok { return e } if e, ok := err.(interface{ Unwrap() error }); ok { err = e.Unwrap() continue } return nil } } func DrillToError(err error) error { e := FindError(err) if e != nil { return e } return err }