Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor errdefs with an API more similar to the standard library #14

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ package errdefs
import (
"context"
"errors"
"fmt"
"io"

"github.com/containerd/errdefs/internal/types"
)

// Definitions of common error types used throughout containerd. All containerd
Expand Down Expand Up @@ -409,3 +413,67 @@ func (c customMessage) As(target any) bool {
func (c customMessage) Error() string {
return c.msg
}

// errorValue is a general purpose container for errors.
//
// It is constructed through New and is used to ensure
// the proper formatting behavior for the contents of
// the error.
type errorValue struct {
error
}

func (e errorValue) Format(st fmt.State, verb rune) {
format := fmt.FormatString(st, verb)
fmt.Fprintf(st, format, e.error)
if verb == 'v' && st.Flag('+') {
printStackTraces(st, e.error)
}
}

func (e errorValue) Unwrap() error {
return e.error
}

// New constructs a new error with the given format string.
func New(format string, args ...interface{}) error {
return &errorValue{
error: fmt.Errorf(format, args...),
}
}

func printStackTraces(w io.Writer, err error) {
// Collect all stack traces from the error.
// Stored in a stack for efficiency and to prevent
// a recursive stack from piling up.
unvisited := []error{err}
for len(unvisited) > 0 {
// Pop the end.
cur := unvisited[len(unvisited)-1]
unvisited = unvisited[:len(unvisited)-1]

// Print the stack trace if this is one.
if _, ok := cur.(types.CollapsibleError); ok {
fmt.Fprintf(w, "\n%+v", cur)
}

switch cur := cur.(type) {
case interface{ Unwrap() error }:
if err := cur.Unwrap(); err != nil {
unvisited = append(unvisited, err)
}
case interface{ Unwrap() []error }:
errs := cur.Unwrap()
if len(errs) > 0 {
// Append in the proper order just for
// memory efficiency and then reverse the
// contents since we want to pop the first
// error first.
unvisited = append(unvisited, errs...)
for i, j := len(unvisited)-len(errs), len(unvisited)-1; i < j; i, j = i+1, j-1 {
unvisited[i], unvisited[j] = unvisited[j], unvisited[i]
}
}
}
}
}
74 changes: 74 additions & 0 deletions join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package errdefs

import (
"fmt"
"strings"

"github.com/containerd/errdefs/internal/types"
)

type joinError struct {
errs []error
}

// Join will join the errors together and ensure stack traces
// are appropriately formatted.
func Join(errs ...error) error {
var e error
n := 0
for _, err := range errs {
if err != nil {
e = err
n++
}
}

switch n {
case 0:
return nil
case 1:
switch e.(type) {
case *errorValue, *joinError:
// Don't wrap the types defined by this package
// as that could interfere with the formatting.
return e
}
return &errorValue{e}
}

joined := make([]error, 0, n)
for _, err := range errs {
if err != nil {
joined = append(joined, err)
}
}
return &joinError{errs: joined}
}

func (e *joinError) Error() string {
var b strings.Builder
fmt.Fprintf(&b, "%v", e)
return b.String()
}

func (e *joinError) Format(st fmt.State, verb rune) {
format := fmt.FormatString(st, verb)
collapsed := verb == 'v' && st.Flag('+')
first := true
for _, err := range e.errs {
if !collapsed {
if _, ok := err.(types.CollapsibleError); ok {
continue
}
}
if !first {
fmt.Fprintln(st)
}
fmt.Fprintf(st, format, err)
first = false
}
}

func (e *joinError) Unwrap() []error {
return e.errs
}
Loading
Loading