Skip to content

Commit

Permalink
Add errors wrapper
Browse files Browse the repository at this point in the history
Add a new error that can wrap multiple errors with additional context
information explaining the issue.
  • Loading branch information
HeavyWombat committed Jul 14, 2019
1 parent d1044e4 commit 8aceb6a
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 3 deletions.
65 changes: 62 additions & 3 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@

package wrap

import "fmt"
import (
"errors"
"fmt"
"strings"
)

// ContextError interface describes the simple type that is able to provide a
// textual context as well as the cause explaining the underlying error.
Expand All @@ -47,12 +51,67 @@ func (e *wrappedError) Cause() error {
return e.cause
}

// ListOfErrors interface describes a list of errors with additional context
// information with an explanation.
type ListOfErrors interface {
Context() string
Errors() []error
}

// wrappedErrors describes a list of errors with context information
type wrappedErrors struct {
context string
errors []error
}

func (e *wrappedErrors) Error() string {
tmp := make([]string, len(e.errors))
for i, err := range e.errors {
tmp[i] = fmt.Sprintf("- %s", err.Error())
}

return fmt.Sprintf("%s:\n%s", e.context, strings.Join(tmp, "\n"))
}

func (e *wrappedErrors) Context() string {
return e.context
}

func (e *wrappedErrors) Errors() []error {
return e.errors
}

// Error creates an error with additional context
func Error(err error, context string) error {
return &wrappedError{context, err}
switch {
case err == nil:
return errors.New(context)

default:
return &wrappedError{context, err}
}
}

// Errorf creates an error with additional formatted context
func Errorf(err error, format string, a ...interface{}) error {
return &wrappedError{fmt.Sprintf(format, a...), err}
return Error(err, fmt.Sprintf(format, a...))
}

// Errors creates a list of errors with additional context
func Errors(errs []error, context string) error {
switch {
case errs == nil:
return errors.New(context)

case len(errs) == 1:
return Error(errs[0], context)

default:
return &wrappedErrors{context, errs}
}
}

// Errorsf creates a list of errors with additional formatted context
func Errorsf(errors []error, format string, a ...interface{}) error {
return Errors(errors, fmt.Sprintf(format, a...))
}
66 changes: 66 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ var _ = Describe("wrap package tests", func() {
Expect(err.Error()).To(BeEquivalentTo("issue setting up z: failed to do x, because of y"))
})

It("should fall back to a simple error if no cause is provided", func() {
err := Error(nil, "failed to do thing A")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(BeEquivalentTo("failed to do thing A"))
})

It("should be able to just extract the context string", func() {
switch contextError := err.(type) {
case ContextError:
Expand All @@ -64,6 +70,66 @@ var _ = Describe("wrap package tests", func() {
})
})

Context("wrapping multiple errors with context", func() {
var (
err = Errorsf(
[]error{
fmt.Errorf("issue setting up x"),
fmt.Errorf("issue setting up y"),
fmt.Errorf("issue setting up z"),
},
"failed to setup component %s", "A",
)
)

It("should behave and render like a standard error", func() {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(BeEquivalentTo("failed to setup component A:\n- issue setting up x\n- issue setting up y\n- issue setting up z"))
})

It("should fall back to a simple error if no cause is provided", func() {
err := Errors(nil, "failed to do thing A")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(BeEquivalentTo("failed to do thing A"))
})

It("should render like a simple wrapped error if there is only one error list entry", func() {
err := Errorsf(
[]error{
fmt.Errorf("issue setting up x"),
},
"failed to setup component %s", "A",
)

Expect(err).To(HaveOccurred())
Expect(err.Error()).To(BeEquivalentTo("failed to setup component A: issue setting up x"))
})

It("should be able to just extract the context string", func() {
switch contextError := err.(type) {
case ListOfErrors:
Expect(contextError.Context()).To(BeEquivalentTo("failed to setup component A"))

default:
Fail("failed to type cast to ContextError")
}
})

It("should be able to just extract the list of errors", func() {
switch contextError := err.(type) {
case ListOfErrors:
Expect(contextError.Errors()).To(BeEquivalentTo([]error{
fmt.Errorf("issue setting up x"),
fmt.Errorf("issue setting up y"),
fmt.Errorf("issue setting up z"),
}))

default:
Fail("failed to type cast to ContextError")
}
})
})

Context("projects using wrap package", func() {
It("should be possible to use an error to wrap with context", func() {
err := Errorf(exampleErr,
Expand Down

0 comments on commit 8aceb6a

Please sign in to comment.