feat: enhanced error handling module

This commit is contained in:
yusing 2025-04-16 12:10:54 +08:00
parent 18ab6c52ec
commit 4615d7dd4e
8 changed files with 112 additions and 30 deletions

View file

@ -1,9 +1,10 @@
package gperr
import (
"encoding/json"
"errors"
"fmt"
"github.com/yusing/go-proxy/pkg/json"
)
// baseError is an immutable wrapper around an error.
@ -48,17 +49,6 @@ func (err *baseError) Error() string {
return err.Err.Error()
}
// MarshalJSON implements the json.Marshaler interface.
func (err *baseError) MarshalJSON() ([]byte, error) {
//nolint:errorlint
switch err := err.Err.(type) {
case Error, *withSubject:
return json.Marshal(err)
case json.Marshaler:
return err.MarshalJSON()
case interface{ MarshalText() ([]byte, error) }:
return err.MarshalText()
default:
return json.Marshal(err.Error())
}
func (err *baseError) MarshalJSONTo(buf []byte) []byte {
return json.MarshalTo(err.Err, buf)
}

View file

@ -24,6 +24,10 @@ type Builder struct {
rwLock
}
type multiline struct {
*Builder
}
// NewBuilder creates a new Builder.
//
// If about is not provided, the Builder will not have a subject
@ -78,12 +82,15 @@ func (b *Builder) Add(err error) *Builder {
return b
}
wrapped := wrap(err)
b.Lock()
defer b.Unlock()
switch err := wrapped.(type) {
b.add(err)
return b
}
func (b *Builder) add(err error) {
switch err := err.(type) {
case *baseError:
b.errs = append(b.errs, err.Err)
case *nestedError:
@ -92,11 +99,11 @@ func (b *Builder) Add(err error) *Builder {
} else {
b.errs = append(b.errs, err)
}
case *MultilineError:
b.add(&err.nestedError)
default:
panic("bug: should not reach here")
b.errs = append(b.errs, err)
}
return b
}
func (b *Builder) Adds(err string) *Builder {
@ -144,8 +151,9 @@ func (b *Builder) AddRange(errs ...error) *Builder {
b.Lock()
defer b.Unlock()
b.errs = append(b.errs, nonNilErrs...)
for _, err := range nonNilErrs {
b.add(err)
}
return b
}

View file

@ -6,14 +6,14 @@ import (
"github.com/yusing/go-proxy/internal/logging"
)
func log(msg string, err error, level zerolog.Level, logger ...*zerolog.Logger) {
func log(_ string, err error, level zerolog.Level, logger ...*zerolog.Logger) {
var l *zerolog.Logger
if len(logger) > 0 {
l = logger[0]
} else {
l = logging.GetLogger()
}
l.WithLevel(level).Msg(msg + ": " + err.Error())
l.WithLevel(level).Msg(err.Error())
}
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {

View file

@ -0,0 +1,45 @@
package gperr
import (
"fmt"
"reflect"
)
type MultilineError struct {
nestedError
}
func Multiline() *MultilineError {
return &MultilineError{}
}
func (m *MultilineError) add(err error) {
m.Extras = append(m.Extras, err)
}
func (m *MultilineError) Addf(format string, args ...any) *MultilineError {
m.add(fmt.Errorf(format, args...))
return m
}
func (m *MultilineError) Adds(s string) *MultilineError {
m.add(newError(s))
return m
}
func (m *MultilineError) AddLines(lines any) *MultilineError {
v := reflect.ValueOf(lines)
if v.Kind() == reflect.Slice {
for i := range v.Len() {
switch v := v.Index(i).Interface().(type) {
case string:
m.add(newError(v))
case error:
m.add(v)
default:
m.add(fmt.Errorf("%v", v))
}
}
}
return m
}

View file

@ -0,0 +1,38 @@
package gperr
import (
"net"
"testing"
"github.com/stretchr/testify/require"
)
func TestMultiline(t *testing.T) {
multiline := Multiline()
multiline.Addf("line 1 %s", "test")
multiline.Adds("line 2")
multiline.AddLines([]any{1, "2", 3.0, net.IPv4(127, 0, 0, 1)})
t.Error(New("result").With(multiline))
t.Error(multiline.Subject("subject").Withf("inner"))
}
func TestWrapMultiline(t *testing.T) {
multiline := Multiline()
var wrapper error = wrap(multiline)
_, ok := wrapper.(*MultilineError)
if !ok {
t.Errorf("wrapper is not a MultilineError")
}
}
func TestPrependSubjectMultiline(t *testing.T) {
multiline := Multiline()
multiline.Addf("line 1 %s", "test")
multiline.Adds("line 2")
multiline.AddLines([]any{1, "2", 3.0, net.IPv4(127, 0, 0, 1)})
multiline.Subject("subject")
builder := NewBuilder()
builder.Add(multiline)
require.Equal(t, len(builder.errs), len(multiline.Extras), builder.errs)
}

View file

@ -15,7 +15,7 @@ type nestedError struct {
func (err nestedError) Subject(subject string) Error {
if err.Err == nil {
err.Err = newError(subject)
err.Err = PrependSubject(subject, errStr(""))
} else {
err.Err = PrependSubject(subject, err.Err)
}

View file

@ -1,11 +1,12 @@
package gperr
import (
"encoding/json"
"errors"
"slices"
"strings"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/utils/strutils/ansi"
)
@ -93,8 +94,7 @@ func (err *withSubject) Error() string {
return sb.String()
}
// MarshalJSON implements the json.Marshaler interface.
func (err *withSubject) MarshalJSON() ([]byte, error) {
func (err *withSubject) MarshalJSONTo(buf []byte) []byte {
subjects := slices.Clone(err.Subjects)
slices.Reverse(subjects)
@ -102,5 +102,5 @@ func (err *withSubject) MarshalJSON() ([]byte, error) {
"subjects": subjects,
"err": err.Err,
}
return json.Marshal(reversed)
return json.MarshalTo(reversed, buf)
}

View file

@ -1,9 +1,10 @@
package gperr
import (
"encoding/json"
"errors"
"fmt"
"github.com/yusing/go-proxy/pkg/json"
)
func newError(message string) error {