mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 08:48:32 +02:00
migrated from logrus to zerolog, improved error formatting, fixed concurrent map write, fixed crash on rapid page refresh for idle containers, fixed infinite recursion on gotfiy error, fixed websocket connection problem when using idlewatcher
This commit is contained in:
@@ -1,317 +1,31 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
type Error interface {
|
||||
error
|
||||
|
||||
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}
|
||||
// Is is a wrapper for errors.Is when there is no sub-error.
|
||||
//
|
||||
// When there are sub-errors, they will also be checked.
|
||||
Is(other error) bool
|
||||
// With appends a sub-error to the error.
|
||||
With(extra error) Error
|
||||
// Withf is a wrapper for With(fmt.Errorf(format, args...)).
|
||||
Withf(format string, args ...any) Error
|
||||
// Subject prepends the given subject with a colon and space to the error message.
|
||||
//
|
||||
// If there is already a subject in the error message, the subject will be
|
||||
// prepended to the existing subject with " > ".
|
||||
//
|
||||
// Subject empty string is ignored.
|
||||
Subject(subject string) Error
|
||||
// Subjectf is a wrapper for Subject(fmt.Sprintf(format, args...)).
|
||||
Subjectf(format string, args ...any) Error
|
||||
}
|
||||
|
||||
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
|
||||
// this makes JSON marshalling work,
|
||||
// as the builtin one doesn't.
|
||||
type errStr string
|
||||
|
||||
func (err errStr) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user