v0.5.0-rc5: check release

This commit is contained in:
yusing
2024-09-19 20:40:03 +08:00
parent be7a766cb2
commit 4a2d42bfa9
68 changed files with 1971 additions and 1107 deletions

View File

@@ -6,16 +6,23 @@ import (
)
type Builder struct {
message string
errors []error
*builder
}
type builder struct {
message string
errors []NestedError
severity Severity
sync.Mutex
}
func NewBuilder(format string, args ...any) *Builder {
return &Builder{message: fmt.Sprintf(format, args...)}
func NewBuilder(format string, args ...any) Builder {
return Builder{&builder{message: fmt.Sprintf(format, args...)}}
}
func (b *Builder) Add(err error) *Builder {
// adding nil / nil is no-op,
// you may safely pass expressions returning error to it
func (b Builder) Add(err NestedError) Builder {
if err != nil {
b.Lock()
b.errors = append(b.errors, err)
@@ -24,8 +31,17 @@ func (b *Builder) Add(err error) *Builder {
return b
}
func (b *Builder) Addf(format string, args ...any) *Builder {
return b.Add(fmt.Errorf(format, args...))
func (b Builder) AddE(err error) Builder {
return b.Add(From(err))
}
func (b Builder) Addf(format string, args ...any) Builder {
return b.Add(errorf(format, args...))
}
func (b Builder) WithSeverity(s Severity) Builder {
b.severity = s
return b
}
// Build builds a NestedError based on the errors collected in the Builder.
@@ -35,9 +51,21 @@ func (b *Builder) Addf(format string, args ...any) *Builder {
//
// Returns:
// - NestedError: the built NestedError.
func (b *Builder) Build() NestedError {
func (b Builder) Build() NestedError {
if len(b.errors) == 0 {
return Nil()
return nil
}
return Join(b.message, b.errors...)
return Join(b.message, b.errors...).Severity(b.severity)
}
func (b Builder) To(ptr *NestedError) {
if *ptr == nil {
*ptr = b.Build()
} else {
**ptr = *b.Build()
}
}
func (b Builder) HasError() bool {
return len(b.errors) > 0
}

View File

@@ -1,13 +1,38 @@
package error
import "testing"
import (
"testing"
func TestBuilder(t *testing.T) {
. "github.com/yusing/go-proxy/utils/testing"
)
func TestBuilderEmpty(t *testing.T) {
eb := NewBuilder("qwer")
ExpectTrue(t, eb.Build() == nil)
ExpectTrue(t, eb.Build().NoError())
ExpectFalse(t, eb.HasError())
}
func TestBuilderAddNil(t *testing.T) {
eb := NewBuilder("asdf")
var err NestedError
for range 3 {
eb.Add(nil)
}
for range 3 {
eb.Add(err)
}
ExpectTrue(t, eb.Build() == nil)
ExpectTrue(t, eb.Build().NoError())
ExpectFalse(t, eb.HasError())
}
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().Error()
got := eb.Build().String()
expected1 :=
(`error occurred:
- Action 1 failed:

View File

@@ -7,35 +7,37 @@ import (
)
type (
// NestedError is an error with an inner error
// and a list of extra nested errors.
//
// It is designed to be non nil.
//
// You can use it to join multiple errors,
// or to set a inner reason for a nested error.
//
// When a method returns both valid values and errors,
// You should return (Slice/Map, NestedError).
// Caller then should handle the nested error,
// and continue with the valid values.
NestedError struct {
subject string
err error // can be nil
extras []NestedError
NestedError = *nestedError
nestedError struct {
subject string
err error // can be nil
extras []nestedError
severity Severity
}
errorInterface struct {
*nestedError
}
Severity uint8
)
func Nil() NestedError { return NestedError{} }
const (
SeverityFatal Severity = iota
SeverityWarning
)
func (e errorInterface) Error() string {
return e.String()
}
func From(err error) NestedError {
if IsNil(err) {
return nil
}
switch err := err.(type) {
case nil:
return Nil()
case NestedError:
return err
case errorInterface:
return err.nestedError
default:
return NestedError{err: err}
return &nestedError{err: err}
}
}
@@ -45,40 +47,84 @@ func Check[T any](obj T, err error) (T, NestedError) {
return obj, From(err)
}
func Join(message string, err ...error) NestedError {
extras := make([]NestedError, 0, len(err))
func Join(message string, err ...NestedError) NestedError {
extras := make([]nestedError, len(err))
nErr := 0
for _, e := range err {
if err == nil {
for i, e := range err {
if e == nil {
continue
}
extras = append(extras, From(e))
extras[i] = *e
nErr += 1
}
if nErr == 0 {
return Nil()
return nil
}
return NestedError{
return &nestedError{
err: errors.New(message),
extras: extras,
}
}
func (ne NestedError) Error() string {
func JoinE(message string, err ...error) NestedError {
b := NewBuilder(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 NestedError) String() string {
var buf strings.Builder
ne.writeToSB(&buf, 0, "")
return buf.String()
}
func (ne NestedError) Is(err error) bool {
return errors.Is(ne.err, err)
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 NestedError) IsNot(err error) bool {
return !ne.Is(err)
}
func (ne NestedError) Error() error {
if ne == nil {
return nil
}
return errorInterface{ne}
}
func (ne NestedError) With(s any) NestedError {
if ne == nil {
return ne
}
var msg string
switch ss := s.(type) {
case nil:
return ne
case *nestedError:
return ne.withError(ss.Error())
case error:
return ne.withError(ss)
case string:
@@ -92,10 +138,13 @@ func (ne NestedError) With(s any) NestedError {
}
func (ne NestedError) Extraf(format string, args ...any) NestedError {
return ne.With(fmt.Errorf(format, args...))
return ne.With(errorf(format, args...))
}
func (ne NestedError) Subject(s any) NestedError {
if ne == nil {
return ne
}
switch ss := s.(type) {
case string:
ne.subject = ss
@@ -108,6 +157,9 @@ func (ne NestedError) Subject(s any) NestedError {
}
func (ne NestedError) Subjectf(format string, args ...any) NestedError {
if ne == nil {
return ne
}
if strings.Contains(format, "%q") {
panic("Subjectf format should not contain %q")
}
@@ -118,12 +170,36 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
return ne
}
func (ne NestedError) Severity(s Severity) NestedError {
if ne == nil {
return ne
}
ne.severity = s
return ne
}
func (ne NestedError) Warn() NestedError {
if ne == nil {
return ne
}
ne.severity = SeverityWarning
return ne
}
func (ne NestedError) NoError() bool {
return ne.err == nil
return ne == nil
}
func (ne NestedError) HasError() bool {
return ne.err != nil
return ne != nil
}
func (ne NestedError) IsFatal() bool {
return ne != nil && ne.severity == SeverityFatal
}
func (ne NestedError) IsWarning() bool {
return ne != nil && ne.severity == SeverityWarning
}
func errorf(format string, args ...any) NestedError {
@@ -131,11 +207,13 @@ func errorf(format string, args ...any) NestedError {
}
func (ne NestedError) withError(err error) NestedError {
ne.extras = append(ne.extras, From(err))
if ne != nil && IsNotNil(err) {
ne.extras = append(ne.extras, *From(err))
}
return ne
}
func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string) {
func (ne NestedError) writeToSB(sb *strings.Builder, level int, prefix string) {
ne.writeIndents(sb, level)
sb.WriteString(prefix)
@@ -146,7 +224,7 @@ func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string)
sb.WriteString(ne.err.Error())
if ne.subject != "" {
if ne.err != nil {
if IsNotNil(ne.err) {
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
} else {
sb.WriteString(fmt.Sprint(ne.subject))
@@ -161,7 +239,7 @@ func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string)
}
}
func (ne *NestedError) writeIndents(sb *strings.Builder, level int) {
func (ne NestedError) writeIndents(sb *strings.Builder, level int) {
for i := 0; i < level; i++ {
sb.WriteString(" ")
}

View File

@@ -4,7 +4,7 @@ import (
"testing"
. "github.com/yusing/go-proxy/error"
. "github.com/yusing/go-proxy/utils"
. "github.com/yusing/go-proxy/utils/testing"
)
func TestErrorIs(t *testing.T) {
@@ -16,27 +16,53 @@ func TestErrorIs(t *testing.T) {
ExpectTrue(t, Invalid("foo", "bar").Is(ErrInvalid))
ExpectFalse(t, Invalid("foo", "bar").Is(ErrFailure))
ExpectTrue(t, Nil().Is(nil))
ExpectFalse(t, Nil().Is(ErrInvalid))
ExpectFalse(t, Invalid("foo", "bar").Is(nil))
}
func TestNil(t *testing.T) {
ExpectTrue(t, Nil().NoError())
ExpectFalse(t, Nil().HasError())
ExpectEqual(t, Nil().Error(), "nil")
func TestErrorNestedIs(t *testing.T) {
var err NestedError
ExpectTrue(t, err.Is(nil))
err = Failure("some reason")
ExpectTrue(t, err.Is(ErrFailure))
ExpectFalse(t, err.Is(ErrAlreadyExist))
err.With(AlreadyExist("something", ""))
ExpectTrue(t, err.Is(ErrFailure))
ExpectTrue(t, err.Is(ErrAlreadyExist))
ExpectFalse(t, err.Is(ErrInvalid))
}
func TestIsNil(t *testing.T) {
var err NestedError
ExpectTrue(t, err.Is(nil))
ExpectFalse(t, err.HasError())
ExpectTrue(t, err == nil)
ExpectTrue(t, err.NoError())
eb := NewBuilder("")
returnNil := func() error {
return eb.Build().Error()
}
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.Error(), "foo bar failed")
ExpectEqual(t, ne.String(), "foo bar failed")
ne = ne.Subject("baz")
ExpectEqual(t, ne.Error(), "foo bar failed for \"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.Error(), "foo failed:\n - bar\n - baz")
ExpectEqual(t, ne.String(), "foo failed:\n - bar\n - baz")
}
func TestErrorNested(t *testing.T) {
@@ -72,5 +98,5 @@ func TestErrorNested(t *testing.T) {
- inner3 failed for "action 3":
- 3
- 3`
ExpectEqual(t, ne.Error(), want)
ExpectEqual(t, ne.String(), want)
}

View File

@@ -5,33 +5,48 @@ import (
)
var (
ErrFailure = stderrors.New("failed")
ErrInvalid = stderrors.New("invalid")
ErrUnsupported = stderrors.New("unsupported")
ErrNotExists = stderrors.New("does not exist")
ErrDuplicated = stderrors.New("duplicated")
ErrFailure = stderrors.New("failed")
ErrInvalid = stderrors.New("invalid")
ErrUnsupported = stderrors.New("unsupported")
ErrUnexpected = stderrors.New("unexpected")
ErrNotExists = stderrors.New("does not exist")
ErrAlreadyExist = stderrors.New("already exist")
)
const fmtSubjectWhat = "%w %v: %v"
func Failure(what string) NestedError {
return errorf("%s %w", what, ErrFailure)
}
func FailureWhy(what string, why string) NestedError {
func FailedWhy(what string, why string) NestedError {
return errorf("%s %w because %s", what, ErrFailure, why)
}
func FailWith(what string, err any) NestedError {
return Failure(what).With(err)
}
func Invalid(subject, what any) NestedError {
return errorf("%w %v - %v", ErrInvalid, subject, what)
return errorf(fmtSubjectWhat, ErrInvalid, subject, what)
}
func Unsupported(subject, what any) NestedError {
return errorf("%w %v - %v", ErrUnsupported, subject, what)
return errorf(fmtSubjectWhat, ErrUnsupported, subject, what)
}
func NotExists(subject, what any) NestedError {
return errorf("%s %v - %v", subject, ErrNotExists, what)
func Unexpected(subject, what any) NestedError {
return errorf(fmtSubjectWhat, ErrUnexpected, subject, what)
}
func Duplicated(subject, what any) NestedError {
return errorf("%w %v: %v", ErrDuplicated, subject, what)
func UnexpectedError(err error) NestedError {
return errorf("%w error: %w", ErrUnexpected, err)
}
func NotExist(subject, what any) NestedError {
return errorf("%v %w: %v", subject, ErrNotExists, what)
}
func AlreadyExist(subject, what any) NestedError {
return errorf("%v %w: %v", subject, ErrAlreadyExist, what)
}