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 ( import (
"encoding/json" "encoding/json"

View file

@ -1,4 +1,4 @@
package err package gperr
import ( import (
"fmt" "fmt"
@ -36,7 +36,7 @@ func (b *Builder) error() Error {
func (b *Builder) Error() Error { func (b *Builder) Error() Error {
if len(b.errs) == 1 { if len(b.errs) == 1 {
return From(b.errs[0]) return wrap(b.errs[0])
} }
return b.error() return b.error()
} }
@ -60,7 +60,7 @@ func (b *Builder) Add(err error) *Builder {
b.Lock() b.Lock()
defer b.Unlock() defer b.Unlock()
switch err := From(err).(type) { switch err := wrap(err).(type) {
case *baseError: case *baseError:
b.errs = append(b.errs, err.Err) b.errs = append(b.errs, err.Err)
case *nestedError: case *nestedError:
@ -122,3 +122,9 @@ func (b *Builder) AddRange(errs ...error) *Builder {
return b 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 ( import (
"context" "context"
@ -6,7 +6,7 @@ import (
"io" "io"
"testing" "testing"
. "github.com/yusing/go-proxy/internal/error" . "github.com/yusing/go-proxy/internal/gperr"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )

View file

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

View file

@ -1,4 +1,4 @@
package err package gperr
import ( import (
"errors" "errors"
@ -44,7 +44,7 @@ func TestBaseWithExtra(t *testing.T) {
func TestBaseUnwrap(t *testing.T) { func TestBaseUnwrap(t *testing.T) {
err := errors.New("err") err := errors.New("err")
wrapped := From(err) wrapped := Wrap(err)
ExpectError(t, err, errors.Unwrap(wrapped)) ExpectError(t, err, errors.Unwrap(wrapped))
} }
@ -52,7 +52,7 @@ func TestBaseUnwrap(t *testing.T) {
func TestNestedUnwrap(t *testing.T) { func TestNestedUnwrap(t *testing.T) {
err := errors.New("err") err := errors.New("err")
err2 := New("err2") 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 }) unwrapper, ok := wrapped.(interface{ Unwrap() []error })
ExpectTrue(t, ok) ExpectTrue(t, ok)
@ -64,7 +64,7 @@ func TestNestedUnwrap(t *testing.T) {
func TestErrorIs(t *testing.T) { func TestErrorIs(t *testing.T) {
from := errors.New("error") from := errors.New("error")
err := From(from) err := Wrap(from)
ExpectError(t, from, err) ExpectError(t, from, err)
ExpectTrue(t, err.Is(from)) 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 ( import (
"errors" "errors"
@ -99,7 +99,7 @@ func makeLines(errs []error, level int) []string {
} }
lines := make([]string, 0, len(errs)) lines := make([]string, 0, len(errs))
for _, err := range errs { for _, err := range errs {
switch err := From(err).(type) { switch err := wrap(err).(type) {
case *nestedError: case *nestedError:
if err.Err != nil { if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), level)) lines = append(lines, makeLine(err.Err.Error(), level))

View file

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

View file

@ -1,6 +1,8 @@
package err package gperr
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
) )
@ -19,27 +21,50 @@ func Errorf(format string, args ...any) Error {
return &baseError{fmt.Errorf(format, args...)} return &baseError{fmt.Errorf(format, args...)}
} }
// Wrap wraps message in front of the error message.
func Wrap(err error, message ...string) Error { func Wrap(err error, message ...string) Error {
if len(message) == 0 || message[0] == "" { if err == nil {
return From(err) 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 { if err == nil {
return nil return nil
} }
//nolint:errorlint //nolint:errorlint
switch err := err.(type) { switch err := err.(type) {
case *baseError: case Error:
return err
case *nestedError:
return err return err
} }
return &baseError{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 { func Join(errors ...error) Error {
n := 0 n := 0
for _, err := range errors { 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) eb.Add(err)
return result 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)
}
})
}
}