zen: update error handling
This commit is contained in:
parent
f20b4a6359
commit
34e7ed06cb
|
@ -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() {
|
||||
|
|
110
core/zerr/err.go
110
core/zerr/err.go
|
@ -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 {
|
||||
|
|
|
@ -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, ": ")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue