mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-18 23:33:51 +01:00
preparing for v0.5
This commit is contained in:
43
src/error/builder.go
Normal file
43
src/error/builder.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
message string
|
||||
errors []error
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewBuilder(format string, args ...any) *Builder {
|
||||
return &Builder{message: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
func (b *Builder) Add(err error) *Builder {
|
||||
if err != nil {
|
||||
b.Lock()
|
||||
b.errors = append(b.errors, err)
|
||||
b.Unlock()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Builder) Addf(format string, args ...any) *Builder {
|
||||
return b.Add(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// Build builds a NestedError based on the errors collected in the Builder.
|
||||
//
|
||||
// If there are no errors in the Builder, it returns a Nil() NestedError.
|
||||
// Otherwise, it returns a NestedError with the message and the errors collected.
|
||||
//
|
||||
// Returns:
|
||||
// - NestedError: the built NestedError.
|
||||
func (b *Builder) Build() NestedError {
|
||||
if len(b.errors) == 0 {
|
||||
return Nil()
|
||||
}
|
||||
return Join(b.message, b.errors...)
|
||||
}
|
||||
217
src/error/error.go
Normal file
217
src/error/error.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
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{ *nestedError }
|
||||
nestedError struct {
|
||||
neBase
|
||||
sync.Mutex
|
||||
}
|
||||
neBase struct {
|
||||
subject any
|
||||
err error // can be nil
|
||||
extras []neBase
|
||||
inner *neBase // can be nil
|
||||
level int
|
||||
}
|
||||
)
|
||||
|
||||
func Nil() NestedError { return NestedError{} }
|
||||
|
||||
func From(err error) NestedError {
|
||||
if err == nil {
|
||||
return Nil()
|
||||
}
|
||||
return NestedError{&nestedError{neBase: *copyFrom(err)}}
|
||||
}
|
||||
|
||||
// Check is a helper function that
|
||||
// convert (T, error) to (T, NestedError).
|
||||
func Check[T any](obj T, err error) (T, NestedError) {
|
||||
return obj, From(err)
|
||||
}
|
||||
|
||||
func Join(message string, err ...error) NestedError {
|
||||
extras := make([]neBase, 0, len(err))
|
||||
nErr := 0
|
||||
for _, e := range err {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
extras = append(extras, *copyFrom(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
|
||||
}
|
||||
switch base := err.(type) {
|
||||
case *neBase:
|
||||
copy := *base
|
||||
return ©
|
||||
}
|
||||
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 {
|
||||
var buf strings.Builder
|
||||
ne.writeToSB(&buf, ne.level, "")
|
||||
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) Extra(s string) NestedError {
|
||||
return ne.ExtraError(errors.New(s))
|
||||
}
|
||||
|
||||
func (ne NestedError) ExtraAny(s any) NestedError {
|
||||
var msg string
|
||||
switch ss := s.(type) {
|
||||
case error:
|
||||
return ne.ExtraError(ss)
|
||||
case string:
|
||||
msg = ss
|
||||
case fmt.Stringer:
|
||||
msg = ss.String()
|
||||
default:
|
||||
msg = fmt.Sprint(s)
|
||||
}
|
||||
return ne.ExtraError(errors.New(msg))
|
||||
}
|
||||
|
||||
func (ne NestedError) Extraf(format string, args ...any) NestedError {
|
||||
return ne.ExtraError(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
func (ne NestedError) Subject(s any) NestedError {
|
||||
ne.subject = s
|
||||
return ne
|
||||
}
|
||||
|
||||
func (ne NestedError) Subjectf(format string, args ...any) NestedError {
|
||||
if strings.Contains(format, "%q") {
|
||||
panic("Subjectf format should not contain %q")
|
||||
}
|
||||
if strings.Contains(format, "%w") {
|
||||
panic("Subjectf format should not contain %w")
|
||||
}
|
||||
return ne.Subject(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (ne NestedError) Level() int {
|
||||
return ne.level
|
||||
}
|
||||
|
||||
func (ne *nestedError) IsNil() bool {
|
||||
return ne == nil
|
||||
}
|
||||
|
||||
func (ne *nestedError) IsNotNil() bool {
|
||||
return ne != nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
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.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, "- ")
|
||||
}
|
||||
}
|
||||
|
||||
func (ne *neBase) writeIndents(sb *strings.Builder, level int) {
|
||||
for i := 0; i < level; i++ {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
}
|
||||
66
src/error/error_test.go
Normal file
66
src/error/error_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func AssertEq(t *testing.T, got, want string) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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 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).
|
||||
With(inner.With(inner2.With(inner3)))
|
||||
want :=
|
||||
`foo:
|
||||
- bar
|
||||
- baz
|
||||
- inner:
|
||||
- 123
|
||||
- 456
|
||||
- inner:
|
||||
- 123
|
||||
- 456
|
||||
- 2: inner:
|
||||
- 456
|
||||
- 789
|
||||
- 3: inner:
|
||||
- 456
|
||||
- 789
|
||||
`
|
||||
AssertEq(t, ne.Error(), want)
|
||||
}
|
||||
30
src/error/errors.go
Normal file
30
src/error/errors.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package error
|
||||
|
||||
var (
|
||||
ErrAlreadyStarted = new("already started")
|
||||
ErrNotStarted = new("not started")
|
||||
ErrInvalid = new("invalid")
|
||||
ErrUnsupported = new("unsupported")
|
||||
ErrNotExists = new("does not exist")
|
||||
ErrDuplicated = new("duplicated")
|
||||
)
|
||||
|
||||
func Failure(what string) NestedError {
|
||||
return errorf("%s failed", what)
|
||||
}
|
||||
|
||||
func Invalid(subject, what any) NestedError {
|
||||
return errorf("%w %s: %q", ErrInvalid, subject, what)
|
||||
}
|
||||
|
||||
func Unsupported(subject, what any) NestedError {
|
||||
return errorf("%w %s: %q", ErrUnsupported, subject, what)
|
||||
}
|
||||
|
||||
func NotExists(subject, what any) NestedError {
|
||||
return errorf("%s %w: %q", subject, ErrNotExists, what)
|
||||
}
|
||||
|
||||
func Duplicated(subject, what any) NestedError {
|
||||
return errorf("%w %s: %q", ErrDuplicated, subject, what)
|
||||
}
|
||||
Reference in New Issue
Block a user