refactor: rename module 'err' to 'gperr'

This commit is contained in:
yusing 2025-03-28 05:57:43 +08:00
parent 47ab6b8a92
commit 40aa937f54
12 changed files with 262 additions and 71 deletions

View file

@ -1,43 +0,0 @@
package err
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())
}

106
internal/gperr/README.md Normal file
View file

@ -0,0 +1,106 @@
# gperr
gperr is an error interface that supports nested structure and subject highlighting.
## Usage
### gperr.Error
The error interface.
### gperr.New
Like `errors.New`, but returns a `gperr.Error`.
### gperr.Wrap
Like `fmt.Errorf("%s: %w", message, err)`, but returns a `gperr.Error`.
### gperr.Error.Subject
Returns a new error with the subject prepended to the error message. The main subject is highlighted.
```go
err := gperr.New("error message")
err = err.Subject("bar")
err = err.Subject("foo")
```
Output:
<code>foo > <span style="color: red;">bar</span>: error message</code>
### gperr.Error.Subjectf
Like `gperr.Error.Subject`, but formats the subject with `fmt.Sprintf`.
### gperr.PrependSubject
Prepends the subject to the error message like `gperr.Error.Subject`.
```go
err := gperr.New("error message")
err = gperr.PrependSubject(err, "foo")
err = gperr.PrependSubject(err, "bar")
```
Output:
<code>bar > <span style="color: red;">foo</span>: error message</code>
### gperr.Error.With
Adds a new error to the error chain.
```go
err := gperr.New("error message")
err = err.With(gperr.New("inner error"))
err = err.With(gperr.New("inner error 2").With(gperr.New("inner inner error")))
```
Output:
```
error message:
• inner error
• inner error 2
• inner inner error
```
### gperr.Error.Withf
Like `gperr.Error.With`, but formats the error with `fmt.Errorf`.
### gperr.Error.Is
Returns true if the error is equal to the given error.
### gperr.Builder
A builder for `gperr.Error`.
```go
builder := gperr.NewBuilder("foo")
builder.Add(gperr.New("error message"))
builder.Addf("error message: %s", "foo")
builder.AddRange(gperr.New("error message 1"), gperr.New("error message 2"))
```
Output:
```
foo:
• error message
• error message: foo
• error message 1
• error message 2
```
### gperr.Builder.Build
Builds a `gperr.Error` from the builder.
## When to return gperr.Error
- When you want to return multiple errors
- When the error has a subject

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"encoding/json"

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"fmt"
@ -36,7 +36,7 @@ func (b *Builder) error() Error {
func (b *Builder) Error() Error {
if len(b.errs) == 1 {
return From(b.errs[0])
return wrap(b.errs[0])
}
return b.error()
}
@ -60,7 +60,7 @@ func (b *Builder) Add(err error) *Builder {
b.Lock()
defer b.Unlock()
switch err := From(err).(type) {
switch err := wrap(err).(type) {
case *baseError:
b.errs = append(b.errs, err.Err)
case *nestedError:
@ -122,3 +122,9 @@ func (b *Builder) AddRange(errs ...error) *Builder {
return b
}
func (b *Builder) ForEach(fn func(error)) {
for _, err := range b.errs {
fn(err)
}
}

View file

@ -1,4 +1,4 @@
package err_test
package gperr_test
import (
"context"
@ -6,7 +6,7 @@ import (
"io"
"testing"
. "github.com/yusing/go-proxy/internal/error"
. "github.com/yusing/go-proxy/internal/gperr"
. "github.com/yusing/go-proxy/internal/utils/testing"
)

View file

@ -1,4 +1,4 @@
package err
package gperr
type Error interface {
error

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"errors"
@ -44,7 +44,7 @@ func TestBaseWithExtra(t *testing.T) {
func TestBaseUnwrap(t *testing.T) {
err := errors.New("err")
wrapped := From(err)
wrapped := Wrap(err)
ExpectError(t, err, errors.Unwrap(wrapped))
}
@ -52,7 +52,7 @@ func TestBaseUnwrap(t *testing.T) {
func TestNestedUnwrap(t *testing.T) {
err := errors.New("err")
err2 := New("err2")
wrapped := From(err).Subject("foo").With(err2.Subject("bar"))
wrapped := Wrap(err).Subject("foo").With(err2.Subject("bar"))
unwrapper, ok := wrapped.(interface{ Unwrap() []error })
ExpectTrue(t, ok)
@ -64,7 +64,7 @@ func TestNestedUnwrap(t *testing.T) {
func TestErrorIs(t *testing.T) {
from := errors.New("error")
err := From(from)
err := Wrap(from)
ExpectError(t, from, err)
ExpectTrue(t, err.Is(from))

40
internal/gperr/log.go Normal file
View file

@ -0,0 +1,40 @@
package gperr
import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging"
)
func log(msg 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())
}
func LogFatal(msg string, err error, logger ...*zerolog.Logger) {
if common.IsDebug {
LogPanic(msg, err, logger...)
}
log(msg, err, zerolog.FatalLevel, logger...)
}
func LogError(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.ErrorLevel, logger...)
}
func LogWarn(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.WarnLevel, logger...)
}
func LogPanic(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.PanicLevel, logger...)
}
func LogDebug(msg string, err error, logger ...*zerolog.Logger) {
log(msg, err, zerolog.DebugLevel, logger...)
}

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"errors"
@ -99,7 +99,7 @@ func makeLines(errs []error, level int) []string {
}
lines := make([]string, 0, len(errs))
for _, err := range errs {
switch err := From(err).(type) {
switch err := wrap(err).(type) {
case *nestedError:
if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), level))

View file

@ -1,4 +1,4 @@
package err
package gperr
import (
"encoding/json"

View file

@ -1,6 +1,8 @@
package err
package gperr
import (
"encoding/json"
"errors"
"fmt"
)
@ -19,27 +21,50 @@ func Errorf(format string, args ...any) Error {
return &baseError{fmt.Errorf(format, args...)}
}
// Wrap wraps message in front of the error message.
func Wrap(err error, message ...string) Error {
if len(message) == 0 || message[0] == "" {
return From(err)
if err == nil {
return nil
}
return Errorf("%w: %s", err, message[0])
if len(message) == 0 || message[0] == "" {
return wrap(err)
}
//nolint:errorlint
switch err := err.(type) {
case *baseError:
err.Err = fmt.Errorf("%s: %w", message[0], err.Err)
return err
case *nestedError:
err.Err = fmt.Errorf("%s: %w", message[0], err.Err)
return err
}
return &baseError{fmt.Errorf("%s: %w", message[0], err)}
}
func From(err error) Error {
func wrap(err error) Error {
if err == nil {
return nil
}
//nolint:errorlint
switch err := err.(type) {
case *baseError:
return err
case *nestedError:
case Error:
return err
}
return &baseError{err}
}
func IsJSONMarshallable(err error) bool {
switch err := err.(type) {
case *nestedError, *withSubject:
return true
case *baseError:
return IsJSONMarshallable(err.Err)
default:
var v json.Marshaler
return errors.As(err, &v)
}
}
func Join(errors ...error) Error {
n := 0
for _, err := range errors {
@ -66,9 +91,3 @@ func Collect[T any, Err error, Arg any, Func func(Arg) (T, Err)](eb *Builder, fn
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
}

View file

@ -0,0 +1,63 @@
package gperr
import (
"errors"
"testing"
)
type testErr struct{}
func (e *testErr) Error() string {
return "test error"
}
func (e *testErr) MarshalJSON() ([]byte, error) {
return nil, nil
}
func TestIsJSONMarshallable(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{
name: "testErr",
err: &testErr{},
want: true,
},
{
name: "baseError",
err: &baseError{},
want: false,
},
{
name: "baseError with json marshallable error",
err: &baseError{&testErr{}},
want: true,
},
{
name: "nestedError",
err: &nestedError{},
want: true,
},
{
name: "withSubject",
err: &withSubject{},
want: true,
},
{
name: "standard error",
err: errors.New("test error"),
want: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := IsJSONMarshallable(test.err); got != test.want {
t.Errorf("IsJSONMarshallable(%v) = %v, want %v", test.err, got, test.want)
}
})
}
}