261 lines
5.2 KiB
Go
261 lines
5.2 KiB
Go
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
|
|
}
|
|
|
|
type originErr struct {
|
|
err error
|
|
}
|
|
|
|
func (or originErr) LogValue() slog.Value {
|
|
if or.err == nil {
|
|
return slog.AnyValue(or.err)
|
|
}
|
|
if lv, ok := or.err.(slog.LogValuer); ok {
|
|
return lv.LogValue()
|
|
}
|
|
t := reflect.TypeOf(or.err).String()
|
|
if t == "*errors.errorString" {
|
|
return slog.StringValue(or.err.Error())
|
|
}
|
|
return slog.GroupValue(
|
|
slog.String("type", t),
|
|
slog.String("message", or.err.Error()),
|
|
slog.Any("data", or.err),
|
|
)
|
|
}
|
|
|
|
// 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...))
|
|
}
|
|
values = append(values, slog.Attr{Key: "error", Value: (originErr{er.origin}).LogValue()})
|
|
|
|
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 Failw(message string, details ...any) Error {
|
|
return &Err{
|
|
origin: errors.New(message),
|
|
details: details,
|
|
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
|
|
}
|