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:
yusing
2024-10-29 11:34:58 +08:00
parent cfa74d69ae
commit e5bbb18414
137 changed files with 2640 additions and 2348 deletions

46
internal/error/base.go Normal file
View File

@@ -0,0 +1,46 @@
package error
import (
"errors"
"fmt"
)
// baseError is an immutable wrapper around an error.
type baseError struct {
Err error `json:"err"`
}
func (err *baseError) Unwrap() error {
return err.Err
}
func (err *baseError) Is(other error) bool {
if other, ok := other.(*baseError); ok {
return errors.Is(err.Err, other.Err)
}
return errors.Is(err.Err, other)
}
func (err baseError) Subject(subject string) Error {
err.Err = PrependSubject(subject, err.Err)
return &err
}
func (err *baseError) Subjectf(format string, args ...any) Error {
if len(args) > 0 {
return err.Subject(fmt.Sprintf(format, args...))
}
return err.Subject(format)
}
func (err baseError) With(extra error) Error {
return &nestedError{&err, []error{extra}}
}
func (err baseError) Withf(format string, args ...any) Error {
return &nestedError{&err, []error{fmt.Errorf(format, args...)}}
}
func (err *baseError) Error() string {
return err.Err.Error()
}

View File

@@ -1,104 +1,104 @@
package error
import (
"errors"
"fmt"
"sync"
)
type Builder struct {
*builder
}
type builder struct {
message string
errors []Error
about string
errs []error
sync.Mutex
}
func NewBuilder(format string, args ...any) Builder {
if len(args) > 0 {
return Builder{&builder{message: fmt.Sprintf(format, args...)}}
func NewBuilder(about string) *Builder {
return &Builder{about: about}
}
func (b *Builder) About() string {
if !b.HasError() {
return ""
}
return Builder{&builder{message: format}}
return b.about
}
//go:inline
func (b *Builder) HasError() bool {
return len(b.errs) > 0
}
func (b *Builder) Error() Error {
if !b.HasError() {
return nil
}
if len(b.errs) == 1 {
return From(b.errs[0])
}
return &nestedError{Err: New(b.about), Extras: b.errs}
}
func (b *Builder) String() string {
if !b.HasError() {
return ""
}
return (&nestedError{Err: New(b.about), Extras: b.errs}).Error()
}
// Add adds an error to the Builder.
//
// adding nil is no-op,
//
// flatten is a boolean flag to flatten the NestedError.
func (b Builder) Add(err Error, flatten ...bool) {
if err != nil {
b.Lock()
if len(flatten) > 0 && flatten[0] {
for _, e := range err.extras {
b.errors = append(b.errors, &e)
}
func (b *Builder) Add(err error) *Builder {
if err == nil {
return b
}
b.Lock()
defer b.Unlock()
switch err := err.(type) {
case *baseError:
b.errs = append(b.errs, err.Err)
case *nestedError:
if err.Err == nil {
b.errs = append(b.errs, err.Extras...)
} else {
b.errors = append(b.errors, err)
b.errs = append(b.errs, err)
}
b.Unlock()
}
}
func (b Builder) AddE(err error) {
b.Add(From(err))
}
func (b Builder) Addf(format string, args ...any) {
if len(args) > 0 {
b.Add(errorf(format, args...))
} else {
b.AddE(errors.New(format))
}
}
func (b Builder) AddRange(errs ...Error) {
b.Lock()
defer b.Unlock()
for _, err := range errs {
b.errors = append(b.errors, err)
}
}
func (b Builder) AddRangeE(errs ...error) {
b.Lock()
defer b.Unlock()
for _, err := range errs {
b.errors = append(b.errors, From(err))
}
}
// Build builds a NestedError based on the errors collected in the Builder.
//
// If there are no errors in the Builder, it returns a Nil() NestedError.
// Otherwise, it returns a NestedError with the message and the errors collected.
//
// Returns:
// - NestedError: the built NestedError.
func (b Builder) Build() Error {
if len(b.errors) == 0 {
return nil
}
return Join(b.message, b.errors...)
}
func (b Builder) To(ptr *Error) {
switch {
case ptr == nil:
return
case *ptr == nil:
*ptr = b.Build()
default:
(*ptr).extras = append((*ptr).extras, *b.Build())
b.errs = append(b.errs, err)
}
return b
}
func (b Builder) String() string {
return b.Build().String()
func (b *Builder) Adds(err string) *Builder {
b.Lock()
defer b.Unlock()
b.errs = append(b.errs, newError(err))
return b
}
func (b Builder) HasError() bool {
return len(b.errors) > 0
func (b *Builder) Addf(format string, args ...any) *Builder {
if len(args) > 0 {
b.Lock()
defer b.Unlock()
b.errs = append(b.errs, fmt.Errorf(format, args...))
} else {
b.Adds(format)
}
return b
}
func (b *Builder) AddRange(errs ...error) *Builder {
b.Lock()
defer b.Unlock()
for _, err := range errs {
if err != nil {
b.errs = append(b.errs, err)
}
}
return b
}

View File

@@ -1,6 +1,9 @@
package error_test
import (
"context"
"errors"
"io"
"testing"
. "github.com/yusing/go-proxy/internal/error"
@@ -8,14 +11,13 @@ import (
)
func TestBuilderEmpty(t *testing.T) {
eb := NewBuilder("qwer")
ExpectTrue(t, eb.Build() == nil)
ExpectTrue(t, eb.Build().NoError())
eb := NewBuilder("foo")
ExpectTrue(t, errors.Is(eb.Error(), nil))
ExpectFalse(t, eb.HasError())
}
func TestBuilderAddNil(t *testing.T) {
eb := NewBuilder("asdf")
eb := NewBuilder("foo")
var err Error
for range 3 {
eb.Add(nil)
@@ -23,41 +25,31 @@ func TestBuilderAddNil(t *testing.T) {
for range 3 {
eb.Add(err)
}
ExpectTrue(t, eb.Build() == nil)
ExpectTrue(t, eb.Build().NoError())
eb.AddRange(nil, nil, err)
ExpectFalse(t, eb.HasError())
ExpectTrue(t, eb.Error() == nil)
}
func TestBuilderIs(t *testing.T) {
eb := NewBuilder("foo")
eb.Add(context.Canceled)
eb.Add(io.ErrShortBuffer)
ExpectTrue(t, eb.HasError())
ExpectError(t, io.ErrShortBuffer, eb.Error())
ExpectError(t, context.Canceled, eb.Error())
}
func TestBuilderNested(t *testing.T) {
eb := NewBuilder("error occurred")
eb.Add(Failure("Action 1").With(Invalid("Inner", "1")).With(Invalid("Inner", "2")))
eb.Add(Failure("Action 2").With(Invalid("Inner", "3")))
got := eb.Build().String()
expected1 := (`error occurred:
- Action 1 failed:
- invalid Inner: 1
- invalid Inner: 2
- Action 2 failed:
- invalid Inner: 3`)
expected2 := (`error occurred:
- Action 1 failed:
- invalid Inner: "1"
- invalid Inner: "2"
- Action 2 failed:
- invalid Inner: "3"`)
ExpectEqualAny(t, got, []string{expected1, expected2})
}
func TestBuilderTo(t *testing.T) {
eb := NewBuilder("error occurred")
eb.Addf("abcd")
var err Error
eb.To(&err)
got := err.String()
expected := (`error occurred:
- abcd`)
eb := NewBuilder("action failed")
eb.Add(New("Action 1").Withf("Inner: 1").Withf("Inner: 2"))
eb.Add(New("Action 2").Withf("Inner: 3"))
got := eb.String()
expected := `action failed
• Action 1
• Inner: 1
• Inner: 2
• Action 2
• Inner: 3`
ExpectEqual(t, got, expected)
}

View File

@@ -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)
}

View File

@@ -1,107 +1,157 @@
package error_test
package error
import (
"errors"
"strings"
"testing"
. "github.com/yusing/go-proxy/internal/error"
. "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestBaseString(t *testing.T) {
ExpectEqual(t, New("error").Error(), "error")
}
func TestBaseWithSubject(t *testing.T) {
err := New("error")
withSubject := err.Subject("foo")
withSubjectf := err.Subjectf("%s %s", "foo", "bar")
ExpectError(t, err, withSubject)
ExpectStrEqual(t, withSubject.Error(), "foo: error")
ExpectTrue(t, withSubject.Is(err))
ExpectError(t, err, withSubjectf)
ExpectStrEqual(t, withSubjectf.Error(), "foo bar: error")
ExpectTrue(t, withSubjectf.Is(err))
}
func TestBaseWithExtra(t *testing.T) {
err := New("error")
extra := New("bar").Subject("baz")
withExtra := err.With(extra)
ExpectTrue(t, withExtra.Is(extra))
ExpectTrue(t, withExtra.Is(err))
ExpectTrue(t, errors.Is(withExtra, extra))
ExpectTrue(t, errors.Is(withExtra, err))
ExpectTrue(t, strings.Contains(withExtra.Error(), err.Error()))
ExpectTrue(t, strings.Contains(withExtra.Error(), extra.Error()))
ExpectTrue(t, strings.Contains(withExtra.Error(), "baz"))
}
func TestBaseUnwrap(t *testing.T) {
err := errors.New("err")
wrapped := From(err)
ExpectError(t, err, errors.Unwrap(wrapped))
}
func TestNestedUnwrap(t *testing.T) {
err := errors.New("err")
err2 := New("err2")
wrapped := From(err).Subject("foo").With(err2.Subject("bar"))
unwrapper, ok := wrapped.(interface{ Unwrap() []error })
ExpectTrue(t, ok)
ExpectError(t, err, wrapped)
ExpectError(t, err2, wrapped)
ExpectEqual(t, len(unwrapper.Unwrap()), 2)
}
func TestErrorIs(t *testing.T) {
ExpectTrue(t, Failure("foo").Is(ErrFailure))
ExpectTrue(t, Failure("foo").With("bar").Is(ErrFailure))
ExpectFalse(t, Failure("foo").With("bar").Is(ErrInvalid))
ExpectFalse(t, Failure("foo").With("bar").With("baz").Is(ErrInvalid))
from := errors.New("error")
err := From(from)
ExpectError(t, from, err)
ExpectTrue(t, Invalid("foo", "bar").Is(ErrInvalid))
ExpectFalse(t, Invalid("foo", "bar").Is(ErrFailure))
ExpectTrue(t, err.Is(from))
ExpectFalse(t, err.Is(New("error")))
ExpectFalse(t, Invalid("foo", "bar").Is(nil))
ExpectTrue(t, errors.Is(Failure("foo").Error(), ErrFailure))
ExpectTrue(t, errors.Is(Failure("foo").With(Invalid("bar", "baz")).Error(), ErrInvalid))
ExpectTrue(t, errors.Is(Failure("foo").With(Invalid("bar", "baz")).Error(), ErrFailure))
ExpectFalse(t, errors.Is(Failure("foo").With(Invalid("bar", "baz")).Error(), ErrNotExists))
ExpectTrue(t, errors.Is(err.Subject("foo"), from))
ExpectTrue(t, errors.Is(err.Withf("foo"), from))
ExpectTrue(t, errors.Is(err.Subject("foo").Withf("bar"), from))
}
func TestErrorNestedIs(t *testing.T) {
var err Error
ExpectTrue(t, err.Is(nil))
func TestErrorImmutability(t *testing.T) {
err := New("err")
err2 := New("err2")
err = Failure("some reason")
ExpectTrue(t, err.Is(ErrFailure))
ExpectFalse(t, err.Is(ErrDuplicated))
for range 3 {
// t.Logf("%d: %v %T %s", i, errors.Unwrap(err), err, err)
err.Subject("foo")
ExpectFalse(t, strings.Contains(err.Error(), "foo"))
err.With(Duplicated("something", ""))
ExpectTrue(t, err.Is(ErrFailure))
ExpectTrue(t, err.Is(ErrDuplicated))
ExpectFalse(t, err.Is(ErrInvalid))
}
err.With(err2)
ExpectFalse(t, strings.Contains(err.Error(), "extra"))
ExpectFalse(t, err.Is(err2))
func TestIsNil(t *testing.T) {
var err Error
ExpectTrue(t, err.Is(nil))
ExpectTrue(t, err == nil)
ExpectTrue(t, err.NoError())
eb := NewBuilder("")
returnNil := func() error {
return eb.Build().Error()
err = err.Subject("bar").Withf("baz")
ExpectTrue(t, err != nil)
}
ExpectTrue(t, IsNil(returnNil()))
ExpectTrue(t, returnNil() == nil)
ExpectTrue(t, (err.
Subject("any").
With("something").
Extraf("foo %s", "bar")) == nil)
}
func TestErrorSimple(t *testing.T) {
ne := Failure("foo bar")
ExpectEqual(t, ne.String(), "foo bar failed")
ne = ne.Subject("baz")
ExpectEqual(t, ne.String(), "foo bar failed for \"baz\"")
}
func TestErrorWith(t *testing.T) {
ne := Failure("foo").With("bar").With("baz")
ExpectEqual(t, ne.String(), "foo failed:\n - bar\n - baz")
err1 := New("err1")
err2 := New("err2")
err3 := err1.With(err2)
ExpectTrue(t, err3.Is(err1))
ExpectTrue(t, err3.Is(err2))
err2.Subject("foo")
ExpectTrue(t, err3.Is(err1))
ExpectTrue(t, err3.Is(err2))
// check if err3 is affected by err2.Subject
ExpectFalse(t, strings.Contains(err3.Error(), "foo"))
}
func TestErrorNested(t *testing.T) {
inner := Failure("inner").
With("1").
With("1")
inner2 := Failure("inner2").
func TestErrorStringSimple(t *testing.T) {
errFailure := New("generic failure")
ne := errFailure.Subject("foo bar")
ExpectStrEqual(t, ne.Error(), "foo bar: generic failure")
ne = ne.Subject("baz")
ExpectStrEqual(t, ne.Error(), "baz > foo bar: generic failure")
}
func TestErrorStringNested(t *testing.T) {
errFailure := New("generic failure")
inner := errFailure.Subject("inner").
Withf("1").
Withf("1")
inner2 := errFailure.Subject("inner2").
Subject("action 2").
With("2").
With("2")
inner3 := Failure("inner3").
Withf("2").
Withf("2")
inner3 := errFailure.Subject("inner3").
Subject("action 3").
With("3").
With("3")
ne := Failure("foo").
With("bar").
With("baz").
Withf("3").
Withf("3")
ne := errFailure.
Subject("foo").
Withf("bar").
Withf("baz").
With(inner).
With(inner.With(inner2.With(inner3)))
want := `foo failed:
- bar
- baz
- inner failed:
- 1
- 1
- inner failed:
- 1
- 1
- inner2 failed for "action 2":
- 2
- 2
- inner3 failed for "action 3":
- 3
- 3`
ExpectEqual(t, ne.String(), want)
ExpectEqual(t, ne.Error().Error(), want)
want := `foo: generic failure
bar
baz
inner: generic failure
1
1
inner: generic failure
1
1
• action 2 > inner2: generic failure
2
2
• action 3 > inner3: generic failure
3
3`
ExpectStrEqual(t, ne.Error(), want)
}

View File

@@ -1,83 +0,0 @@
package error
import (
stderrors "errors"
"fmt"
"reflect"
)
var (
ErrFailure = stderrors.New("failed")
ErrInvalid = stderrors.New("invalid")
ErrUnsupported = stderrors.New("unsupported")
ErrUnexpected = stderrors.New("unexpected")
ErrNotExists = stderrors.New("does not exist")
ErrMissing = stderrors.New("missing")
ErrDuplicated = stderrors.New("duplicated")
ErrOutOfRange = stderrors.New("out of range")
ErrTypeError = stderrors.New("type error")
ErrTypeMismatch = stderrors.New("type mismatch")
ErrPanicRecv = stderrors.New("panic recovered from")
)
const fmtSubjectWhat = "%w %v: %q"
func Failure(what string) Error {
return errorf("%s %w", what, ErrFailure)
}
func FailedWhy(what string, why string) Error {
return Failure(what).With(why)
}
func FailWith(what string, err any) Error {
return Failure(what).With(err)
}
func Invalid(subject, what any) Error {
return errorf(fmtSubjectWhat, ErrInvalid, subject, what)
}
func Unsupported(subject, what any) Error {
return errorf(fmtSubjectWhat, ErrUnsupported, subject, what)
}
func Unexpected(subject, what any) Error {
return errorf(fmtSubjectWhat, ErrUnexpected, subject, what)
}
func UnexpectedError(err error) Error {
return errorf("%w error: %w", ErrUnexpected, err)
}
func NotExist(subject, what any) Error {
return errorf("%v %w: %v", subject, ErrNotExists, what)
}
func Missing(subject any) Error {
return errorf("%w %v", ErrMissing, subject)
}
func Duplicated(subject, what any) Error {
return errorf("%w %v: %v", ErrDuplicated, subject, what)
}
func OutOfRange(subject any, value any) Error {
return errorf("%v %w: %v", subject, ErrOutOfRange, value)
}
func TypeError(subject any, from, to reflect.Type) Error {
return errorf("%v %w: %s -> %s\n", subject, ErrTypeError, from, to)
}
func TypeError2(subject any, from, to reflect.Value) Error {
return TypeError(subject, from.Type(), to.Type())
}
func TypeMismatch[Expect any](value any) Error {
return errorf("%w: expect %s got %T", ErrTypeMismatch, reflect.TypeFor[Expect](), value)
}
func PanicRecv(format string, args ...any) Error {
return errorf("%w %s", ErrPanicRecv, fmt.Sprintf(format, args...))
}

43
internal/error/log.go Normal file
View File

@@ -0,0 +1,43 @@
package error
import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/logging"
)
func getLogger(logger ...*zerolog.Logger) *zerolog.Logger {
if len(logger) > 0 {
return logger[0]
}
return logging.GetLogger()
}
//go:inline
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Fatal().Msg(err.Error())
}
//go:inline
func LogError(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Error().Msg(err.Error())
}
//go:inline
func LogWarn(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Warn().Msg(err.Error())
}
//go:inline
func LogPanic(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Panic().Msg(err.Error())
}
//go:inline
func LogInfo(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Info().Msg(err.Error())
}
//go:inline
func LogDebug(msg string, err error, logger ...*zerolog.Logger) {
getLogger(logger...).Debug().Msg(err.Error())
}

View File

@@ -0,0 +1,120 @@
package error
import (
"errors"
"fmt"
"strings"
)
type nestedError struct {
Err error `json:"err"`
Extras []error `json:"extras"`
}
func (err nestedError) Subject(subject string) Error {
if err.Err == nil {
err.Err = newError(subject)
} else {
err.Err = PrependSubject(subject, err.Err)
}
return &err
}
func (err *nestedError) Subjectf(format string, args ...any) Error {
if len(args) > 0 {
return err.Subject(fmt.Sprintf(format, args...))
}
return err.Subject(format)
}
func (err nestedError) With(extra error) Error {
if extra != nil {
err.Extras = append(err.Extras, extra)
}
return &err
}
func (err nestedError) Withf(format string, args ...any) Error {
if len(args) > 0 {
err.Extras = append(err.Extras, fmt.Errorf(format, args...))
} else {
err.Extras = append(err.Extras, newError(format))
}
return &err
}
func (err *nestedError) Unwrap() []error {
if err.Err == nil {
if len(err.Extras) == 0 {
return nil
}
return err.Extras
}
return append([]error{err.Err}, err.Extras...)
}
func (err *nestedError) Is(other error) bool {
if errors.Is(err.Err, other) {
return true
}
for _, e := range err.Extras {
if errors.Is(e, other) {
return true
}
}
return false
}
func (err *nestedError) Error() string {
return buildError(err, 0)
}
//go:inline
func makeLine(err string, level int) string {
const bulletPrefix = "• "
const spaces = " "
if level == 0 {
return err
}
return spaces[:2*level] + bulletPrefix + err
}
func makeLines(errs []error, level int) []string {
if len(errs) == 0 {
return nil
}
lines := make([]string, 0, len(errs))
for _, err := range errs {
switch err := err.(type) {
case *nestedError:
if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), level))
}
if extras := makeLines(err.Extras, level+1); len(extras) > 0 {
lines = append(lines, extras...)
}
default:
lines = append(lines, makeLine(err.Error(), level))
}
}
return lines
}
func buildError(err error, level int) string {
switch err := err.(type) {
case nil:
return makeLine("<nil>", level)
case *nestedError:
lines := make([]string, 0, 1+len(err.Extras))
if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), level))
}
if extras := makeLines(err.Extras, level+1); len(extras) > 0 {
lines = append(lines, extras...)
}
return strings.Join(lines, "\n")
default:
return makeLine(err.Error(), level)
}
}

50
internal/error/subject.go Normal file
View File

@@ -0,0 +1,50 @@
package error
import (
"strings"
"github.com/yusing/go-proxy/internal/utils/strutils/ansi"
)
type withSubject struct {
Subject string `json:"subject"`
Err error `json:"err"`
}
const subjectSep = " > "
func highlight(subject string) string {
return ansi.HighlightRed + subject + ansi.Reset
}
func PrependSubject(subject string, err error) *withSubject {
switch err := err.(type) {
case *withSubject:
return err.Prepend(subject)
case *baseError:
return PrependSubject(subject, err.Err)
default:
return &withSubject{subject, err}
}
}
func (err withSubject) Prepend(subject string) *withSubject {
if subject != "" {
err.Subject = subject + subjectSep + err.Subject
}
return &err
}
func (err *withSubject) Is(other error) bool {
return err.Err == other
}
func (err *withSubject) Unwrap() error {
return err.Err
}
func (err *withSubject) Error() string {
subjects := strings.Split(err.Subject, subjectSep)
subjects[len(subjects)-1] = highlight(subjects[len(subjects)-1])
return strings.Join(subjects, subjectSep) + ": " + err.Err.Error()
}

71
internal/error/utils.go Normal file
View File

@@ -0,0 +1,71 @@
package error
import (
"errors"
"fmt"
)
var ErrInvalidErrorJson = errors.New("invalid error json")
func newError(message string) error {
return errStr(message)
}
func New(message string) Error {
if message == "" {
return nil
}
return &baseError{newError(message)}
}
func Errorf(format string, args ...any) Error {
return &baseError{fmt.Errorf(format, args...)}
}
func From(err error) Error {
if err == nil {
return nil
}
if err, ok := err.(Error); ok {
return err
}
return &baseError{err}
}
func Must[T any](v T, err error) T {
if err != nil {
LogPanic("must failed", err)
}
return v
}
func Join(errors ...error) Error {
n := 0
for _, err := range errors {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
errs := make([]error, 0, n)
for _, err := range errors {
if err != nil {
errs = append(errs, err)
}
}
return &nestedError{Extras: errs}
}
func Collect[T any, Err error, Arg any, Func func(Arg) (T, Err)](eb *Builder, fn Func, arg Arg) T {
result, err := fn(arg)
eb.Add(err)
return result
}
func Collect2[T any, Err error, Arg1 any, Arg2 any, Func func(Arg1, Arg2) (T, Err)](eb *Builder, fn Func, arg1 Arg1, arg2 Arg2) T {
result, err := fn(arg1, arg2)
eb.Add(err)
return result
}