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"
|
||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
|
|
||||||
|
_ "gitlab.bareksa.com/backend/zen/core/zerr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Serve() {
|
func Serve() {
|
||||||
|
|
108
core/zerr/err.go
108
core/zerr/err.go
|
@ -3,12 +3,14 @@ package zerr
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
|
"github.com/pborman/indent"
|
||||||
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
"gitlab.bareksa.com/backend/zen/core/zoptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,73 +30,81 @@ type Err struct {
|
||||||
sequence *Sequence
|
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 {
|
func (mu *Err) Error() string {
|
||||||
if len(mu.errs) == 0 {
|
if len(mu.errs) == 0 {
|
||||||
return "[nil]"
|
return "[nil]"
|
||||||
}
|
}
|
||||||
s := strings.Builder{}
|
s := &strings.Builder{}
|
||||||
if len(mu.errs) == 1 {
|
if mu.message != "" {
|
||||||
str := getNestedErrorString(mu.errs[0])
|
|
||||||
str, found := strings.CutPrefix(str, mu.message)
|
|
||||||
if found {
|
|
||||||
str, _ = strings.CutPrefix(str, ": ")
|
|
||||||
}
|
|
||||||
s.WriteString(mu.message)
|
s.WriteString(mu.message)
|
||||||
|
}
|
||||||
|
if len(mu.errs) == 1 {
|
||||||
|
err := mu.errs[0]
|
||||||
|
if wb, ok := err.(interface{ WriteBuilder(io.Writer) }); ok {
|
||||||
|
wb.WriteBuilder(s)
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
next := strings.TrimPrefix(err.Error(), mu.message)
|
||||||
|
if next == "" {
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
s.WriteString(": ")
|
s.WriteString(": ")
|
||||||
s.WriteString(str)
|
s.WriteString(next)
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
s.WriteString(mu.message)
|
if mu.message != "" {
|
||||||
|
s.WriteString(":\n")
|
||||||
|
}
|
||||||
|
w := indent.New(s, " ")
|
||||||
for i, e := range mu.errs {
|
for i, e := range mu.errs {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
s.WriteString("\n")
|
_, _ = io.WriteString(w, "\n")
|
||||||
}
|
}
|
||||||
s.WriteString(strconv.Itoa(i + 1))
|
_, _ = io.WriteString(w, strconv.Itoa(i+1))
|
||||||
s.WriteString(". ")
|
_, _ = io.WriteString(w, ". ")
|
||||||
s.WriteString(getNestedErrorString(e))
|
if wb, ok := e.(interface{ WriteBuilder(io.Writer) }); ok {
|
||||||
}
|
wb.WriteBuilder(w)
|
||||||
return s.String()
|
} else {
|
||||||
}
|
_, _ = io.WriteString(w, e.Error())
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogValue returns log fields to be consumed by logger.
|
// LogValue returns log fields to be consumed by logger.
|
||||||
func (mu *Err) LogValue() slog.Value {
|
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
|
i := 0
|
||||||
for _, err := range mu.errs {
|
for _, err := range mu.errs {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
// WriteBuilder writes the error to the builder.
|
// WriteBuilder writes the error to the builder.
|
||||||
//
|
//
|
||||||
// w is guaranteed to never return error because
|
// 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) {
|
func (er *Err) WriteBuilder(w io.Writer) {
|
||||||
if len(er.errs) == 0 {
|
if len(er.errs) == 0 {
|
||||||
_, _ = io.WriteString(w, "[nil]")
|
_, _ = io.WriteString(w, "[nil]")
|
||||||
|
@ -21,7 +21,7 @@ func (er *Err) WriteBuilder(w io.Writer) {
|
||||||
if err := er.sequence.Outer(); err != nil && !err.IsMulti() {
|
if err := er.sequence.Outer(); err != nil && !err.IsMulti() {
|
||||||
previous = err.GetMessage()
|
previous = err.GetMessage()
|
||||||
}
|
}
|
||||||
current, _ := strings.CutPrefix(er.message, previous)
|
current := strings.TrimPrefix(er.message, previous)
|
||||||
if current != "" {
|
if current != "" {
|
||||||
if previous != "" {
|
if previous != "" {
|
||||||
_, _ = io.WriteString(w, ": ")
|
_, _ = 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.
|
// ID is used to identify the error. Used by Zen to consider if an error is the same as another.
|
||||||
//
|
//
|
||||||
// Current implementation
|
// If unset, the ID will be automatically generated depending
|
||||||
ID(msg string, args ...any) Error
|
// on the backend logging and notification.
|
||||||
|
ID(s string, args ...any) Error
|
||||||
GetID() string
|
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
|
Log(ctx context.Context) Error
|
||||||
Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error
|
Notify(ctx context.Context, opts ...zoptions.NotifyOption) Error
|
||||||
|
|
||||||
|
@ -66,6 +80,7 @@ type Error interface {
|
||||||
IsMulti() bool
|
IsMulti() bool
|
||||||
|
|
||||||
// Resolve returns error (self) if all the errors
|
// Resolve returns error (self) if all the errors
|
||||||
// are valid.
|
// are valid (not nil) and Error contains at least one error
|
||||||
|
// wrapped.
|
||||||
Resolve() error
|
Resolve() error
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,97 +2,79 @@ package zerr
|
||||||
|
|
||||||
import "iter"
|
import "iter"
|
||||||
|
|
||||||
// Sequence is the implementation of zerr.Sequence.
|
// Sequence keeps track of the sequence of errors.
|
||||||
//
|
//
|
||||||
// The implementation is reversed linked list.
|
// 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 {
|
type Sequence struct {
|
||||||
prev *Sequence
|
parent *Sequence
|
||||||
next *Sequence
|
children []*Sequence
|
||||||
err Error
|
err Error
|
||||||
index int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set prepends the next Sequence to the current Sequence.
|
// WrapBy prepends the next Sequence to the current Sequence.
|
||||||
func (se *Sequence) Set(next *Sequence) {
|
func (se *Sequence) WrapBy(parent *Sequence) {
|
||||||
next.next = se
|
parent.children = append(parent.children, se)
|
||||||
se.prev = next
|
se.parent = parent
|
||||||
se.index = next.index + 1
|
|
||||||
if next.next != nil {
|
|
||||||
next.next.Set(se)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outer returns the error that wraps this Error.
|
// Outer returns the error that wraps this Error.
|
||||||
// Return nil if this Error is not wrapped
|
// Return nil if this Error is not wrapped
|
||||||
// or wrapping outer is not a zerr.Error.
|
// or wrapping outer is not a zerr.Error.
|
||||||
func (se *Sequence) Outer() Error {
|
func (se *Sequence) Outer() Error {
|
||||||
if se.prev != nil {
|
if se.parent != nil {
|
||||||
return se.prev.err
|
return se.parent.err
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// Root returns the outermost Error in the sequence.
|
||||||
//
|
//
|
||||||
// Returns self if there is no outermost.
|
// Returns self if there is no outermost.
|
||||||
func (se *Sequence) Root() *Sequence {
|
func (se *Sequence) Root() *Sequence {
|
||||||
root := se
|
root := se
|
||||||
for root.prev != nil {
|
for root.parent != nil {
|
||||||
root = root.prev
|
root = root.parent
|
||||||
}
|
}
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRoot checks if the Error is the outermost Error in the sequence.
|
||||||
func (se *Sequence) IsRoot() bool {
|
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.
|
// 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 will skip any nil values in the sequence.
|
||||||
//
|
//
|
||||||
// Iterator does not guarantee idempotent behavior.
|
// Iteration is traversed depth first.
|
||||||
// Next call to iterator may start from different root
|
|
||||||
// because the parent in the tree may be wrapped
|
|
||||||
// by another zerr.Error.
|
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// for _, e := range err.Sequence().Iter() {
|
// for e := range err.Sequence().Iter() {
|
||||||
// // e is zerr.Error
|
// // e is zerr.Error
|
||||||
// // do something with e
|
// // do something with e
|
||||||
// }
|
// }
|
||||||
func (se *Sequence) Iter() iter.Seq[Error] {
|
func (se *Sequence) Iter() iter.Seq[Error] {
|
||||||
root := se.Root()
|
return func(yield func(Error) bool) {
|
||||||
return iter.Seq[Error](func(yield func(V Error) bool) {
|
if se.err != nil && !yield(se.err) {
|
||||||
for next := root; next != nil; next = next.next {
|
return
|
||||||
if next.err != nil {
|
}
|
||||||
if !yield(next.err) {
|
for _, child := range se.children {
|
||||||
|
for e := range child.Iter() {
|
||||||
|
if !yield(e) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue