Skip to content

Commit

Permalink
Merge pull request #515 from xibz/master
Browse files Browse the repository at this point in the history
Allows for multiple errors to be logged and printed
  • Loading branch information
xibz committed Jan 19, 2016
2 parents 3ad0b07 + 56005bf commit 9ddfa04
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 22 deletions.
22 changes: 22 additions & 0 deletions aws/awserr/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ type Error interface {
OrigErr() error
}

// BatchError is a batch of errors which also wraps lower level errors with code, message,
// and original errors. Calling Error() will only return the error that is at the end
// of the list.
type BatchError interface {
// Satisfy the generic error interface.
error

// Returns the short phrase depicting the classification of the error.
Code() string

// Returns the error details message.
Message() string

// Returns the original error if one was set. Nil is returned if not set.
OrigErrs() []error
}

// New returns an Error object described by the code, message, and origErr.
//
// If origErr satisfies the Error interface it will not be wrapped within a new
Expand All @@ -53,6 +70,11 @@ func New(code, message string, origErr error) Error {
return newBaseError(code, message, origErr)
}

// NewBatchError returns an baseError with an expectation of an array of errors
func NewBatchError(code, message string, errs []error) BatchError {
return newBaseErrors(code, message, errs)
}

// A RequestFailure is an interface to extract request failure information from
// an Error such as the request ID of the failed request returned by a service.
// RequestFailures may not always have a requestID value if the request failed
Expand Down
74 changes: 68 additions & 6 deletions aws/awserr/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type baseError struct {

// Optional original error this error is based off of. Allows building
// chained errors.
origErr error
errs []error
}

// newBaseError returns an error object for the code, message, and err.
Expand All @@ -43,11 +43,34 @@ type baseError struct {
//
// origErr is the error object which will be nested under the new error to be returned.
func newBaseError(code, message string, origErr error) *baseError {
return &baseError{
b := &baseError{
code: code,
message: message,
origErr: origErr,
}

if origErr != nil {
b.errs = append(b.errs, origErr)
}

return b
}

// newBaseErrors returns an error object for the code, message, and errors.
//
// code is a short no whitespace phrase depicting the classification of
// the error that is being created.
//
// message is the free flow string containing detailed information about the error.
//
// origErrs is the error objects which will be nested under the new errors to be returned.
func newBaseErrors(code, message string, origErrs []error) *baseError {
b := &baseError{
code: code,
message: message,
errs: origErrs,
}

return b
}

// Error returns the string representation of the error.
Expand All @@ -56,7 +79,12 @@ func newBaseError(code, message string, origErr error) *baseError {
//
// Satisfies the error interface.
func (b baseError) Error() string {
return SprintError(b.code, b.message, "", b.origErr)
size := len(b.errs)
if size > 0 {
return SprintError(b.code, b.message, "", errorList(b.errs))
}

return SprintError(b.code, b.message, "", nil)
}

// String returns the string representation of the error.
Expand All @@ -76,9 +104,20 @@ func (b baseError) Message() string {
}

// OrigErr returns the original error if one was set. Nil is returned if no error
// was set.
// was set. This only returns the first element in the list. If the full list is
// needed, use BatchError
func (b baseError) OrigErr() error {
return b.origErr
if size := len(b.errs); size > 0 {
return b.errs[0]
}

return nil
}

// OrigErrs returns the original errors if one was set. An empty slice is returned if
// no error was set:w
func (b baseError) OrigErrs() []error {
return b.errs
}

// So that the Error interface type can be included as an anonymous field
Expand Down Expand Up @@ -133,3 +172,26 @@ func (r requestError) StatusCode() int {
func (r requestError) RequestID() string {
return r.requestID
}

// An error list that satisfies the golang interface
type errorList []error

// Error returns the string representation of the error.
//
// Satisfies the error interface.
func (e errorList) Error() string {
msg := ""
// How do we want to handle the array size being zero
if size := len(e); size > 0 {
for i := 0; i < size; i++ {
msg += fmt.Sprintf("%s", e[i].Error())
// We check the next index to see if it is within the slice.
// If it is, then we append a newline. We do this, because unit tests
// could be broken with the additional '\n'
if i+1 < size {
msg += "\n"
}
}
}
return msg
}
17 changes: 5 additions & 12 deletions aws/credentials/chain_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
)

var (
// ErrNoValidProvidersFoundInChain Is returned when there are no valid
// providers in the ChainProvider.
//
// @readonly
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil)
)

// A ChainProvider will search for a provider which returns credentials
// and cache that provider until Retrieve is called again.
//
Expand Down Expand Up @@ -63,17 +55,18 @@ func NewChainCredentials(providers []Provider) *Credentials {
// If a provider is found it will be cached and any calls to IsExpired()
// will return the expired state of the cached provider.
func (c *ChainProvider) Retrieve() (Value, error) {
var errs []error
for _, p := range c.Providers {
if creds, err := p.Retrieve(); err == nil {
creds, err := p.Retrieve()
if err == nil {
c.curr = p
return creds, nil
}
errs = append(errs, err)
}
c.curr = nil

// TODO better error reporting. maybe report error for each failed retrieve?

return Value{}, ErrNoValidProvidersFoundInChain
return Value{}, awserr.NewBatchError("NoCredentialProviders", "no valid providers in chain", errs)
}

// IsExpired will returned the expired state of the currently cached provider
Expand Down
19 changes: 15 additions & 4 deletions aws/credentials/chain_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,29 @@ func TestChainProviderWithNoProvider(t *testing.T) {

assert.True(t, p.IsExpired(), "Expect expired with no providers")
_, err := p.Retrieve()
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")
assert.Equal(t,
awserr.NewBatchError("NoCredentialProviders", "no valid providers in chain", nil),
err,
"Expect no providers error returned")
}

func TestChainProviderWithNoValidProvider(t *testing.T) {
errs := []error{
awserr.New("FirstError", "first provider error", nil),
awserr.New("SecondError", "second provider error", nil),
}
p := &ChainProvider{
Providers: []Provider{
&stubProvider{err: awserr.New("FirstError", "first provider error", nil)},
&stubProvider{err: awserr.New("SecondError", "second provider error", nil)},
&stubProvider{err: errs[0]},
&stubProvider{err: errs[1]},
},
}

assert.True(t, p.IsExpired(), "Expect expired with no providers")
_, err := p.Retrieve()
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")

assert.Equal(t,
awserr.NewBatchError("NoCredentialProviders", "no valid providers in chain", errs),
err,
"Expect no providers error returned")
}

0 comments on commit 9ddfa04

Please sign in to comment.