zen: update error handling

This commit is contained in:
Tigor Hutasuhut 2024-08-26 16:24:41 +07:00
parent f20b4a6359
commit 34e7ed06cb
5 changed files with 113 additions and 104 deletions

View file

@ -10,6 +10,8 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
_ "gitlab.bareksa.com/backend/zen/core/zerr"
)
func Serve() {

View file

@ -3,12 +3,14 @@ package zerr
import (
"context"
"fmt"
"io"
"log/slog"
"strconv"
"strings"
"time"
"connectrpc.com/connect"
"github.com/pborman/indent"
"gitlab.bareksa.com/backend/zen/core/zoptions"
)
@ -28,73 +30,81 @@ type Err struct {
sequence *Sequence
}
// Join implements Error.
func (mu *Err) Join(errs ...error) Error {
for _, err := range errs {
if err != nil {
mu.errs = append(mu.errs, err)
}
}
return mu
}
// Resolve implements Error.
func (mu *Err) Resolve() error {
if len(mu.errs) == 0 {
return nil
}
return mu
}
func (mu *Err) Error() string {
if len(mu.errs) == 0 {
return "[nil]"
}
s := strings.Builder{}
if len(mu.errs) == 1 {
str := getNestedErrorString(mu.errs[0])
str, found := strings.CutPrefix(str, mu.message)
if found {
str, _ = strings.CutPrefix(str, ": ")
}
s := &strings.Builder{}
if mu.message != "" {
s.WriteString(mu.message)
s.WriteString(": ")
s.WriteString(str)
}
if len(mu.errs) == 1 {
err := mu.errs[0]
if wb, ok := err.(interface{ WriteBuilder(io.Writer) }); ok {
wb.WriteBuilder(s)
return s.String()
}
s.WriteString(mu.message)
next := strings.TrimPrefix(err.Error(), mu.message)
if next == "" {
return s.String()
}
s.WriteString(": ")
s.WriteString(next)
return s.String()
}
if mu.message != "" {
s.WriteString(":\n")
}
w := indent.New(s, " ")
for i, e := range mu.errs {
if i > 0 {
s.WriteString("\n")
_, _ = io.WriteString(w, "\n")
}
s.WriteString(strconv.Itoa(i + 1))
s.WriteString(". ")
s.WriteString(getNestedErrorString(e))
}
return s.String()
}
func getNestedErrorString(err error) string {
if err == nil {
return "[nil]"
}
s := strings.Builder{}
if e, ok := err.(interface{ Unwrap() error }); ok {
current := err.Error()
if err := e.Unwrap(); err != nil {
next := getNestedErrorString(err)
hasPrefix := strings.HasPrefix(next, current)
if !hasPrefix {
s.WriteString(current)
s.WriteString(": ")
}
s.WriteString(next)
_, _ = io.WriteString(w, strconv.Itoa(i+1))
_, _ = io.WriteString(w, ". ")
if wb, ok := e.(interface{ WriteBuilder(io.Writer) }); ok {
wb.WriteBuilder(w)
} else {
_, _ = io.WriteString(w, e.Error())
}
}
if errs, ok := err.(interface{ Unwrap() []error }); ok {
for i, e := range errs.Unwrap() {
if i > 0 {
s.WriteString("\n")
}
s.WriteString(strconv.Itoa(i + 1))
s.WriteString(". ")
s.WriteString(getNestedErrorString(e))
}
}
if s.Len() == 0 {
s.WriteString(err.Error())
}
return s.String()
}
// LogValue returns log fields to be consumed by logger.
func (mu *Err) LogValue() slog.Value {
attrs := make([]slog.Attr, 0, len(mu.errs))
attrs := make([]slog.Attr, 0, 8)
if len(mu.message) > 0 {
attrs = append(attrs, slog.String("message", mu.message))
}
if len(mu.publicMessage) > 0 {
attrs = append(attrs, slog.String("public", mu.message))
}
if mu.code != connect.CodeUnknown && mu.code != connect.CodeInternal {
attrs = append(attrs, slog.String("code", mu.code.String()))
}
if mu.sequence.IsRoot() {
}
if len(mu.errs) == 1 {
}
i := 0
for _, err := range mu.errs {
if err == nil {

View file

@ -11,7 +11,7 @@ import (
// WriteBuilder writes the error to the builder.
//
// w is guaranteed to never return error because
// the underlying most writer is a strings.Builder.
// the bottom most writer is a strings.Builder.
func (er *Err) WriteBuilder(w io.Writer) {
if len(er.errs) == 0 {
_, _ = io.WriteString(w, "[nil]")
@ -21,7 +21,7 @@ func (er *Err) WriteBuilder(w io.Writer) {
if err := er.sequence.Outer(); err != nil && !err.IsMulti() {
previous = err.GetMessage()
}
current, _ := strings.CutPrefix(er.message, previous)
current := strings.TrimPrefix(er.message, previous)
if current != "" {
if previous != "" {
_, _ = io.WriteString(w, ": ")

View file

@ -52,10 +52,24 @@ type Error interface {
//
// ID is used to identify the error. Used by Zen to consider if an error is the same as another.
//
// Current implementation
ID(msg string, args ...any) Error
// If unset, the ID will be automatically generated depending
// on the backend logging and notification.
ID(s string, args ...any) Error
GetID() string
// Join appends given errors to this zerr.Error.
//
// Join discards nil errors.
//
// When searching for Code, Message, PublicMessage, etc,
// only the first error containing valid values will be used.
//
// The searching method is depth-first.
//
// It's undefined behavior to call Join when another thread calls
// .Sequence().Iter() on this error.
Join(errs ...error) Error
Log(ctx context.Context) Error
Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error
@ -66,6 +80,7 @@ type Error interface {
IsMulti() bool
// Resolve returns error (self) if all the errors
// are valid.
// are valid (not nil) and Error contains at least one error
// wrapped.
Resolve() error
}

View file

@ -2,97 +2,79 @@ package zerr
import "iter"
// Sequence is the implementation of zerr.Sequence.
// Sequence keeps track of the sequence of errors.
//
// The implementation is reversed linked list.
//
// New Sequence value will be added to the start of the list.
// When wrapping errors, the wrapping error
// will be the parent of the wrapped Error
// if the wrapped Error is a zerr.Error.
type Sequence struct {
prev *Sequence
next *Sequence
parent *Sequence
children []*Sequence
err Error
index int
}
// Set prepends the next Sequence to the current Sequence.
func (se *Sequence) Set(next *Sequence) {
next.next = se
se.prev = next
se.index = next.index + 1
if next.next != nil {
next.next.Set(se)
}
// WrapBy prepends the next Sequence to the current Sequence.
func (se *Sequence) WrapBy(parent *Sequence) {
parent.children = append(parent.children, se)
se.parent = parent
}
// Outer returns the error that wraps this Error.
// Return nil if this Error is not wrapped
// or wrapping outer is not a zerr.Error.
func (se *Sequence) Outer() Error {
if se.prev != nil {
return se.prev.err
if se.parent != nil {
return se.parent.err
}
return nil
}
// Inner returns the inner zerr.Error in the sequence.
//
// if there is no inner zerr.Error, it returns nil.
//
// if the inner error is not a zerr.Error, this returns nil.
func (se *Sequence) Inner() Error {
if se.next != nil {
return se.next.err
}
return nil
}
// Index returns the index of this Error in the sequence.
func (se *Sequence) Index() int {
return se.index
}
// Root returns the outermost Error in the sequence.
//
// Returns self if there is no outermost.
func (se *Sequence) Root() *Sequence {
root := se
for root.prev != nil {
root = root.prev
for root.parent != nil {
root = root.parent
}
return root
}
// IsRoot checks if the Error is the outermost Error in the sequence.
func (se *Sequence) IsRoot() bool {
return se.prev == nil
return se.parent == nil
}
// Error returns the Error in the sequence.
func (se *Sequence) Error() Error {
return se.err
}
// Iter returns an iterator to traverse the sequence.
//
// The iterator will start from outermost zerr.Error (root) to the last zerr.Error
// in depth-first order.
//
// Iterator will skip any nil values in the sequence.
//
// Iterator does not guarantee idempotent behavior.
// Next call to iterator may start from different root
// because the parent in the tree may be wrapped
// by another zerr.Error.
// Iteration is traversed depth first.
//
// Example:
//
// for _, e := range err.Sequence().Iter() {
// for e := range err.Sequence().Iter() {
// // e is zerr.Error
// // do something with e
// }
func (se *Sequence) Iter() iter.Seq[Error] {
root := se.Root()
return iter.Seq[Error](func(yield func(V Error) bool) {
for next := root; next != nil; next = next.next {
if next.err != nil {
if !yield(next.err) {
return func(yield func(Error) bool) {
if se.err != nil && !yield(se.err) {
return
}
for _, child := range se.children {
for e := range child.Iter() {
if !yield(e) {
return
}
}
}
})
}
}