v0.5: fixed nil dereference for empty autocert config, fixed and simplified 'error' module, small readme and docs update

This commit is contained in:
default
2024-08-13 04:59:34 +08:00
parent 2fc82c3790
commit 85fb637551
20 changed files with 209 additions and 235 deletions

28
src/error/builder_test.go Normal file
View File

@@ -0,0 +1,28 @@
package error
import "testing"
func TestBuilder(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()
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 - 2
- invalid Inner - 1
- Action 2 failed:
- invalid Inner - 3`)
if got != expected1 && got != expected2 {
t.Errorf("expected \n%s, got \n%s", expected1, got)
}
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"strings"
"sync"
)
type (
@@ -20,27 +19,24 @@ type (
// You should return (Slice/Map, NestedError).
// Caller then should handle the nested error,
// and continue with the valid values.
NestedError struct{ *nestedError }
nestedError struct {
neBase
sync.Mutex
}
neBase struct {
NestedError struct {
subject any
err error // can be nil
extras []neBase
inner *neBase // can be nil
level int
extras []NestedError
}
)
func Nil() NestedError { return NestedError{} }
func From(err error) NestedError {
if err == nil {
switch err := err.(type) {
case nil:
return Nil()
case NestedError:
return err
default:
return NestedError{err: err}
}
return NestedError{&nestedError{neBase: *copyFrom(err)}}
}
// Check is a helper function that
@@ -50,73 +46,41 @@ func Check[T any](obj T, err error) (T, NestedError) {
}
func Join(message string, err ...error) NestedError {
extras := make([]neBase, 0, len(err))
extras := make([]NestedError, 0, len(err))
nErr := 0
for _, e := range err {
if err == nil {
continue
}
extras = append(extras, *copyFrom(e))
extras = append(extras, From(e))
nErr += 1
}
if nErr == 0 {
return Nil()
}
return NestedError{&nestedError{
neBase: neBase{
err: errors.New(message),
extras: extras,
},
}}
}
func copyFrom(err error) *neBase {
if err == nil {
return nil
return NestedError{
err: errors.New(message),
extras: extras,
}
switch base := err.(type) {
case *neBase:
copy := *base
return &copy
}
return &neBase{err: err}
}
func new(message ...string) NestedError {
if len(message) == 0 {
return From(nil)
}
return From(errors.New(strings.Join(message, " ")))
}
func errorf(format string, args ...any) NestedError {
return From(fmt.Errorf(format, args...))
}
func (ne *neBase) Error() string {
func (ne NestedError) Error() string {
var buf strings.Builder
ne.writeToSB(&buf, ne.level, "")
ne.writeToSB(&buf, 0, "")
return buf.String()
}
func (ne NestedError) ExtraError(err error) NestedError {
if err != nil {
ne.Lock()
ne.extras = append(ne.extras, From(err).addLevel(ne.Level()+1))
ne.Unlock()
}
return ne
func (ne NestedError) Is(err error) bool {
return errors.Is(ne.err, err)
}
func (ne NestedError) Extra(s string) NestedError {
return ne.ExtraError(errors.New(s))
}
func (ne NestedError) ExtraAny(s any) NestedError {
func (ne NestedError) With(s any) NestedError {
var msg string
switch ss := s.(type) {
case nil:
return ne
case error:
return ne.ExtraError(ss)
return ne.withError(ss)
case string:
msg = ss
case fmt.Stringer:
@@ -124,11 +88,11 @@ func (ne NestedError) ExtraAny(s any) NestedError {
default:
msg = fmt.Sprint(s)
}
return ne.ExtraError(errors.New(msg))
return ne.withError(errors.New(msg))
}
func (ne NestedError) Extraf(format string, args ...any) NestedError {
return ne.ExtraError(fmt.Errorf(format, args...))
return ne.With(fmt.Errorf(format, args...))
}
func (ne NestedError) Subject(s any) NestedError {
@@ -146,71 +110,47 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
return ne.Subject(fmt.Sprintf(format, args...))
}
func (ne NestedError) Level() int {
return ne.level
func (ne NestedError) IsNil() bool {
return ne.err == nil
}
func (ne *nestedError) IsNil() bool {
return ne == nil
func (ne NestedError) IsNotNil() bool {
return ne.err != nil
}
func (ne *nestedError) IsNotNil() bool {
return ne != nil
func errorf(format string, args ...any) NestedError {
return From(fmt.Errorf(format, args...))
}
func (ne NestedError) With(inner error) NestedError {
ne.Lock()
defer ne.Unlock()
if ne.inner == nil {
ne.inner = copyFrom(inner)
} else {
ne.ExtraError(inner)
}
root := &ne.neBase
for root.inner != nil {
root.inner.level = root.level + 1
root = root.inner
}
func (ne NestedError) withError(err error) NestedError {
ne.extras = append(ne.extras, From(err))
return ne
}
func (ne *neBase) addLevel(level int) neBase {
ret := *ne
ret.level += level
if ret.inner != nil {
inner := ret.inner.addLevel(level)
ret.inner = &inner
}
return ret
}
func (ne *neBase) 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)
if ne.err != nil {
sb.WriteString(ne.err.Error())
sb.WriteRune(' ')
}
if ne.subject != nil {
sb.WriteString(fmt.Sprintf("for %q", ne.subject))
if ne.err != nil {
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
} else {
sb.WriteString(fmt.Sprint(ne.subject))
}
}
if ne.inner != nil || len(ne.extras) > 0 {
sb.WriteString(":\n")
}
level += 1
for _, extra := range ne.extras {
extra.writeToSB(sb, level, "- ")
sb.WriteRune('\n')
}
if ne.inner != nil {
ne.inner.writeToSB(sb, level, "- ")
if len(ne.extras) > 0 {
sb.WriteRune(':')
for _, extra := range ne.extras {
sb.WriteRune('\n')
extra.writeToSB(sb, level+1, "- ")
}
}
}
func (ne *neBase) 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,63 +4,67 @@ import (
"testing"
)
func AssertEq(t *testing.T, got, want string) {
func AssertEq[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("expected %q, got %q", want, got)
t.Errorf("expected:\n%v, got\n%v", want, got)
}
}
func TestErrorIs(t *testing.T) {
AssertEq(t, Failure("foo").Is(ErrFailure), true)
AssertEq(t, Failure("foo").With("bar").Is(ErrFailure), true)
AssertEq(t, Failure("foo").With("bar").Is(ErrInvalid), false)
AssertEq(t, Failure("foo").With("bar").With("baz").Is(ErrInvalid), false)
AssertEq(t, Invalid("foo", "bar").Is(ErrInvalid), true)
AssertEq(t, Invalid("foo", "bar").Is(ErrFailure), false)
}
func TestErrorSimple(t *testing.T) {
ne := new("foo bar")
AssertEq(t, ne.Error(), "foo bar")
ne.Subject("baz")
AssertEq(t, ne.Error(), "baz: foo bar")
ne := Failure("foo bar")
AssertEq(t, ne.Error(), "foo bar failed")
ne = ne.Subject("baz")
AssertEq(t, ne.Error(), "foo bar failed for \"baz\"")
}
func TestErrorSubjectOnly(t *testing.T) {
ne := new().Subject("bar")
AssertEq(t, ne.Error(), "bar")
}
func TestErrorExtra(t *testing.T) {
ne := new("foo").Extra("bar").Extra("baz")
AssertEq(t, ne.Error(), "foo:\n - bar\n - baz\n")
func TestErrorWith(t *testing.T) {
ne := Failure("foo").With("bar").With("baz")
AssertEq(t, ne.Error(), "foo failed:\n - bar\n - baz")
}
func TestErrorNested(t *testing.T) {
inner := new("inner").
Extra("123").
Extra("456")
inner2 := new("inner").
Subject("2").
Extra("456").
Extra("789")
inner3 := new("inner").
Subject("3").
Extra("456").
Extra("789")
ne := new("foo").
Extra("bar").
Extra("baz").
ExtraError(inner).
inner := Failure("inner").
With("1").
With("1")
inner2 := Failure("inner2").
Subject("action 2").
With("2").
With("2")
inner3 := Failure("inner3").
Subject("action 3").
With("3").
With("3")
ne := Failure("foo").
With("bar").
With("baz").
With(inner).
With(inner.With(inner2.With(inner3)))
want :=
`foo:
`foo failed:
- bar
- baz
- inner:
- 123
- 456
- inner:
- 123
- 456
- 2: inner:
- 456
- 789
- 3: inner:
- 456
- 789
`
- inner failed:
- 1
- 1
- inner failed:
- 1
- 1
- inner2 failed for "action 2":
- 2
- 2
- inner3 failed for "action 3":
- 3
- 3`
AssertEq(t, ne.Error(), want)
}

View File

@@ -1,30 +1,33 @@
package error
import (
stderrors "errors"
)
var (
ErrAlreadyStarted = new("already started")
ErrNotStarted = new("not started")
ErrInvalid = new("invalid")
ErrUnsupported = new("unsupported")
ErrNotExists = new("does not exist")
ErrDuplicated = new("duplicated")
ErrFailure = stderrors.New("failed")
ErrInvalid = stderrors.New("invalid")
ErrUnsupported = stderrors.New("unsupported")
ErrNotExists = stderrors.New("does not exist")
ErrDuplicated = stderrors.New("duplicated")
)
func Failure(what string) NestedError {
return errorf("%s failed", what)
return errorf("%s %w", what, ErrFailure)
}
func Invalid(subject, what any) NestedError {
return errorf("%w %s: %q", ErrInvalid, subject, what)
return errorf("%w %v - %v", ErrInvalid, subject, what)
}
func Unsupported(subject, what any) NestedError {
return errorf("%w %s: %q", ErrUnsupported, subject, what)
return errorf("%w %v - %v", ErrUnsupported, subject, what)
}
func NotExists(subject, what any) NestedError {
return errorf("%s %w: %q", subject, ErrNotExists, what)
return errorf("%s %v - %v", subject, ErrNotExists, what)
}
func Duplicated(subject, what any) NestedError {
return errorf("%w %s: %q", ErrDuplicated, subject, what)
return errorf("%w %v: %v", ErrDuplicated, subject, what)
}