mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
317 lines
5.7 KiB
Go
317 lines
5.7 KiB
Go
package error
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
Error = *ErrorImpl
|
|
ErrorImpl struct {
|
|
subject string
|
|
err error
|
|
extras []ErrorImpl
|
|
}
|
|
ErrorJSONMarshaller struct {
|
|
Subject string `json:"subject"`
|
|
Err string `json:"error"`
|
|
Extras []ErrorJSONMarshaller `json:"extras,omitempty"`
|
|
}
|
|
)
|
|
|
|
func From(err error) Error {
|
|
if IsNil(err) {
|
|
return nil
|
|
}
|
|
return &ErrorImpl{err: err}
|
|
}
|
|
|
|
func FromJSON(data []byte) (Error, bool) {
|
|
var j ErrorJSONMarshaller
|
|
if err := json.Unmarshal(data, &j); err != nil {
|
|
return nil, false
|
|
}
|
|
if j.Err == "" {
|
|
return nil, false
|
|
}
|
|
extras := make([]ErrorImpl, len(j.Extras))
|
|
for i, e := range j.Extras {
|
|
extra, ok := fromJSONObject(e)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
extras[i] = *extra
|
|
}
|
|
return &ErrorImpl{
|
|
subject: j.Subject,
|
|
err: errors.New(j.Err),
|
|
extras: extras,
|
|
}, true
|
|
}
|
|
|
|
func TryUnwrap(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if unwrapped := errors.Unwrap(err); unwrapped != nil {
|
|
return unwrapped
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Check is a helper function that
|
|
// convert (T, error) to (T, NestedError).
|
|
func Check[T any](obj T, err error) (T, Error) {
|
|
return obj, From(err)
|
|
}
|
|
|
|
func Join(message string, err ...Error) Error {
|
|
extras := make([]ErrorImpl, len(err))
|
|
nErr := 0
|
|
for i, e := range err {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
extras[i] = *e
|
|
nErr++
|
|
}
|
|
if nErr == 0 {
|
|
return nil
|
|
}
|
|
return &ErrorImpl{
|
|
err: errors.New(message),
|
|
extras: extras,
|
|
}
|
|
}
|
|
|
|
func JoinE(message string, err ...error) Error {
|
|
b := NewBuilder("%s", message)
|
|
for _, e := range err {
|
|
b.AddE(e)
|
|
}
|
|
return b.Build()
|
|
}
|
|
|
|
func IsNil(err error) bool {
|
|
return err == nil
|
|
}
|
|
|
|
func IsNotNil(err error) bool {
|
|
return err != nil
|
|
}
|
|
|
|
func (ne Error) String() string {
|
|
var buf strings.Builder
|
|
ne.writeToSB(&buf, 0, "")
|
|
return buf.String()
|
|
}
|
|
|
|
func (ne Error) Is(err error) bool {
|
|
if ne == nil {
|
|
return err == nil
|
|
}
|
|
// return errors.Is(ne.err, err)
|
|
if errors.Is(ne.err, err) {
|
|
return true
|
|
}
|
|
for _, e := range ne.extras {
|
|
if e.Is(err) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ne Error) IsNot(err error) bool {
|
|
return !ne.Is(err)
|
|
}
|
|
|
|
func (ne Error) Error() error {
|
|
if ne == nil {
|
|
return nil
|
|
}
|
|
return ne.buildError(0, "")
|
|
}
|
|
|
|
func (ne Error) With(s any) Error {
|
|
if ne == nil {
|
|
return ne
|
|
}
|
|
var msg string
|
|
switch ss := s.(type) {
|
|
case nil:
|
|
return ne
|
|
case *ErrorImpl:
|
|
if len(ss.extras) == 1 {
|
|
ne.extras = append(ne.extras, ss.extras[0])
|
|
return ne
|
|
}
|
|
return ne.withError(ss)
|
|
case error:
|
|
// unwrap only once
|
|
return ne.withError(From(TryUnwrap(ss)))
|
|
case string:
|
|
msg = ss
|
|
case fmt.Stringer:
|
|
return ne.appendMsg(ss.String())
|
|
default:
|
|
return ne.appendMsg(fmt.Sprint(s))
|
|
}
|
|
return ne.withError(From(errors.New(msg)))
|
|
}
|
|
|
|
func (ne Error) Extraf(format string, args ...any) Error {
|
|
return ne.With(errorf(format, args...))
|
|
}
|
|
|
|
func (ne Error) Subject(s any, sep ...string) Error {
|
|
if ne == nil {
|
|
return ne
|
|
}
|
|
var subject string
|
|
switch ss := s.(type) {
|
|
case string:
|
|
subject = ss
|
|
case fmt.Stringer:
|
|
subject = ss.String()
|
|
default:
|
|
subject = fmt.Sprint(s)
|
|
}
|
|
switch {
|
|
case ne.subject == "":
|
|
ne.subject = subject
|
|
case len(sep) > 0:
|
|
ne.subject = fmt.Sprintf("%s%s%s", subject, sep[0], ne.subject)
|
|
default:
|
|
ne.subject = fmt.Sprintf("%s > %s", subject, ne.subject)
|
|
}
|
|
return ne
|
|
}
|
|
|
|
func (ne Error) Subjectf(format string, args ...any) Error {
|
|
if ne == nil {
|
|
return ne
|
|
}
|
|
return ne.Subject(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (ne Error) JSONObject() ErrorJSONMarshaller {
|
|
extras := make([]ErrorJSONMarshaller, len(ne.extras))
|
|
for i, e := range ne.extras {
|
|
extras[i] = e.JSONObject()
|
|
}
|
|
return ErrorJSONMarshaller{
|
|
Subject: ne.subject,
|
|
Err: ne.err.Error(),
|
|
Extras: extras,
|
|
}
|
|
}
|
|
|
|
func (ne Error) JSON() []byte {
|
|
b, err := json.MarshalIndent(ne.JSONObject(), "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (ne Error) NoError() bool {
|
|
return ne == nil
|
|
}
|
|
|
|
func (ne Error) HasError() bool {
|
|
return ne != nil
|
|
}
|
|
|
|
func errorf(format string, args ...any) Error {
|
|
for i, arg := range args {
|
|
if err, ok := arg.(error); ok {
|
|
if unwrapped := errors.Unwrap(err); unwrapped != nil {
|
|
args[i] = unwrapped
|
|
}
|
|
}
|
|
}
|
|
return From(fmt.Errorf(format, args...))
|
|
}
|
|
|
|
func fromJSONObject(obj ErrorJSONMarshaller) (Error, bool) {
|
|
data, err := json.Marshal(obj)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
return FromJSON(data)
|
|
}
|
|
|
|
func (ne Error) withError(err Error) Error {
|
|
if ne != nil && err != nil {
|
|
ne.extras = append(ne.extras, *err)
|
|
}
|
|
return ne
|
|
}
|
|
|
|
func (ne Error) appendMsg(msg string) Error {
|
|
if ne == nil {
|
|
return nil
|
|
}
|
|
ne.err = fmt.Errorf("%w %s", ne.err, msg)
|
|
return ne
|
|
}
|
|
|
|
func (ne Error) writeToSB(sb *strings.Builder, level int, prefix string) {
|
|
for range level {
|
|
sb.WriteString(" ")
|
|
}
|
|
sb.WriteString(prefix)
|
|
|
|
if ne.NoError() {
|
|
sb.WriteString("nil")
|
|
return
|
|
}
|
|
|
|
if ne.subject != "" {
|
|
sb.WriteString(ne.subject)
|
|
sb.WriteRune(' ')
|
|
}
|
|
sb.WriteString(ne.err.Error())
|
|
if len(ne.extras) > 0 {
|
|
sb.WriteRune(':')
|
|
for _, extra := range ne.extras {
|
|
sb.WriteRune('\n')
|
|
extra.writeToSB(sb, level+1, "- ")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ne Error) buildError(level int, prefix string) error {
|
|
var res error
|
|
var sb strings.Builder
|
|
|
|
for range level {
|
|
sb.WriteString(" ")
|
|
}
|
|
sb.WriteString(prefix)
|
|
|
|
if ne.NoError() {
|
|
sb.WriteString("nil")
|
|
return errors.New(sb.String())
|
|
}
|
|
|
|
res = fmt.Errorf("%s%w", sb.String(), ne.err)
|
|
sb.Reset()
|
|
|
|
if ne.subject != "" {
|
|
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
|
|
}
|
|
if len(ne.extras) > 0 {
|
|
sb.WriteRune(':')
|
|
res = fmt.Errorf("%w%s", res, sb.String())
|
|
for _, extra := range ne.extras {
|
|
res = errors.Join(res, extra.buildError(level+1, "- "))
|
|
}
|
|
} else {
|
|
res = fmt.Errorf("%w%s", res, sb.String())
|
|
}
|
|
return res
|
|
}
|