GC custom errors to inherit from internal/common (#337)
Custom error package added and extended to the internal/connector package
This commit is contained in:
parent
ecdfbbc1a8
commit
5a9f2e4601
57
src/internal/common/errors.go
Normal file
57
src/internal/common/errors.go
Normal file
@ -0,0 +1,57 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Err provides boiler-plate functions that other types of errors can use
|
||||
// if they wish to be compared with `errors.As()`. This struct ensures that
|
||||
// stack traces are printed when requested (if present) and that Err
|
||||
// chains `errors.As()`, `errors.Is()`, and `errors.Cause()` calls properly.
|
||||
//
|
||||
// When using errors.As, note that the variable that is passed as the second
|
||||
// parameter must be a pointer to a type that exactly matches the returned type of the error previously. For
|
||||
// example, if a struct was returned, the second parameter should be a pointer
|
||||
// to said struct. If a pointer to a struct was returned, then a pointer to a
|
||||
// pointer of the struct should be passed.
|
||||
type Err struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func EncapsulateError(e error) *Err {
|
||||
return &Err{Err: e}
|
||||
}
|
||||
func (e Err) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e Err) Cause() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e Err) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// Format complies with the Formatter interface and gives pretty printing when
|
||||
// functions like `fmt.Printf("%+v")` are called. Implementing this allows Err
|
||||
// to print stack traces from the encapsulated error.
|
||||
func (e Err) Format(s fmt.State, verb rune) {
|
||||
if f, ok := e.Err.(fmt.Formatter); ok {
|
||||
f.Format(s, verb)
|
||||
return
|
||||
}
|
||||
// Formatting magic courtesy of github.com/pkg/errors.
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", e.Cause())
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
// nolint:errcheck
|
||||
_, _ = io.WriteString(s, e.Error())
|
||||
}
|
||||
}
|
||||
78
src/internal/common/errors_test.go
Normal file
78
src/internal/common/errors_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
)
|
||||
|
||||
type testErr struct {
|
||||
common.Err
|
||||
}
|
||||
|
||||
type testErr2 struct {
|
||||
common.Err
|
||||
}
|
||||
|
||||
type ErrorsUnitSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestErrorsUnitSuite(t *testing.T) {
|
||||
suite.Run(t, new(ErrorsUnitSuite))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestPropagatesCause() {
|
||||
err := assert.AnError
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
te2 := testErr2{*common.EncapsulateError(te)}
|
||||
|
||||
assert.Equal(suite.T(), assert.AnError, errors.Cause(te2))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestPropagatesIs() {
|
||||
err := assert.AnError
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
te2 := testErr2{*common.EncapsulateError(te)}
|
||||
assert.True(suite.T(), errors.Is(te2, err))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestPropagatesAs() {
|
||||
err := assert.AnError
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
te2 := testErr2{*common.EncapsulateError(te)}
|
||||
var tmp testErr
|
||||
assert.True(suite.T(), errors.As(te2, &tmp))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestAs() {
|
||||
err := assert.AnError
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
te2 := testErr2{*common.EncapsulateError(te)}
|
||||
var tmp testErr2
|
||||
assert.True(suite.T(), errors.As(te2, &tmp))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestAsIsUnique() {
|
||||
err := assert.AnError
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
var tmp testErr2
|
||||
assert.False(suite.T(), errors.As(te, &tmp))
|
||||
}
|
||||
|
||||
func (suite *ErrorsUnitSuite) TestPrintsStack() {
|
||||
err := assert.AnError
|
||||
err = errors.Wrap(err, "wrapped error")
|
||||
te := testErr{*common.EncapsulateError(err)}
|
||||
te2 := testErr2{*common.EncapsulateError(te)}
|
||||
|
||||
out := fmt.Sprintf("%+v", te2)
|
||||
|
||||
// Stack trace should include a line noting that we're running testify.
|
||||
assert.Contains(suite.T(), out, "testify")
|
||||
}
|
||||
@ -397,12 +397,12 @@ func (gc *GraphConnector) PrintableStatus() string {
|
||||
|
||||
// IsRecoverableError returns true iff error is a RecoverableGCEerror
|
||||
func IsRecoverableError(e error) bool {
|
||||
var recoverable *support.RecoverableGCError
|
||||
var recoverable support.RecoverableGCError
|
||||
return errors.As(e, &recoverable)
|
||||
}
|
||||
|
||||
// IsNonRecoverableError returns true iff error is a NonRecoverableGCEerror
|
||||
func IsNonRecoverableError(e error) bool {
|
||||
var nonRecoverable *support.NonRecoverableGCError
|
||||
var nonRecoverable support.NonRecoverableGCError
|
||||
return errors.As(e, &nonRecoverable)
|
||||
}
|
||||
|
||||
@ -178,46 +178,49 @@ func (suite *DisconnectedGraphConnectorSuite) TestGraphConnector_ErrorChecking()
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
returnRecoverable bool
|
||||
returnNonRecoverable bool
|
||||
returnRecoverable assert.BoolAssertionFunc
|
||||
returnNonRecoverable assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "Neither Option",
|
||||
err: errors.New("regular error"),
|
||||
returnRecoverable: false,
|
||||
returnNonRecoverable: false,
|
||||
returnRecoverable: assert.False,
|
||||
returnNonRecoverable: assert.False,
|
||||
},
|
||||
{
|
||||
name: "Validate Recoverable",
|
||||
err: support.SetRecoverableError(errors.New("Recoverable")),
|
||||
returnRecoverable: true,
|
||||
returnNonRecoverable: false,
|
||||
returnRecoverable: assert.True,
|
||||
returnNonRecoverable: assert.False,
|
||||
},
|
||||
{name: "Validate NonRecoverable",
|
||||
err: support.SetNonRecoverableError(errors.New("Non-recoverable")),
|
||||
returnRecoverable: false,
|
||||
returnNonRecoverable: true,
|
||||
returnRecoverable: assert.False,
|
||||
returnNonRecoverable: assert.True,
|
||||
},
|
||||
{
|
||||
name: "Wrapped Recoverable",
|
||||
err: support.SetRecoverableError(support.WrapAndAppend(
|
||||
"Wrapped Recoverable", errors.New("Recoverable"), nil)),
|
||||
returnRecoverable: true,
|
||||
returnNonRecoverable: false,
|
||||
err: support.WrapAndAppend(
|
||||
"Wrapped Recoverable",
|
||||
support.SetRecoverableError(errors.New("Recoverable")),
|
||||
nil),
|
||||
returnRecoverable: assert.True,
|
||||
returnNonRecoverable: assert.False,
|
||||
},
|
||||
{
|
||||
name: "On Nil",
|
||||
err: nil,
|
||||
returnRecoverable: false,
|
||||
returnNonRecoverable: false,
|
||||
returnRecoverable: assert.False,
|
||||
returnNonRecoverable: assert.False,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
recoverable := IsRecoverableError(test.err)
|
||||
nonRecoverable := IsNonRecoverableError(test.err)
|
||||
suite.Equal(recoverable, test.returnRecoverable, "Expected: %v received %v", test.returnRecoverable, recoverable)
|
||||
suite.Equal(nonRecoverable, test.returnNonRecoverable)
|
||||
test.returnRecoverable(suite.T(), recoverable, "Test: %s Recoverable-received %v", test.name, recoverable)
|
||||
test.returnNonRecoverable(suite.T(), nonRecoverable, "Test: %s non-recoverable: %v", test.name, nonRecoverable)
|
||||
t.Logf("Is nil: %v", test.err == nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,33 +8,27 @@ import (
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
msgraph_errors "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
)
|
||||
|
||||
// GraphConnector has two types of errors that are exported
|
||||
// RecoverableGCError is a query error that can be overcome with time
|
||||
type RecoverableGCError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (rgc *RecoverableGCError) Error() string {
|
||||
return rgc.err.Error()
|
||||
common.Err
|
||||
}
|
||||
|
||||
func SetRecoverableError(e error) error {
|
||||
return &RecoverableGCError{err: e}
|
||||
return RecoverableGCError{*common.EncapsulateError(e)}
|
||||
}
|
||||
|
||||
// NonRecoverableGCError is a permanent query error
|
||||
type NonRecoverableGCError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (nrgc *NonRecoverableGCError) Error() string {
|
||||
return nrgc.err.Error()
|
||||
common.Err
|
||||
}
|
||||
|
||||
func SetNonRecoverableError(e error) error {
|
||||
return &NonRecoverableGCError{err: e}
|
||||
return NonRecoverableGCError{*common.EncapsulateError(e)}
|
||||
}
|
||||
|
||||
// WrapErrorAndAppend helper function used to attach identifying information to an error
|
||||
|
||||
@ -37,6 +37,22 @@ func (suite *GraphConnectorErrorSuite) TestWrapAndAppend_OnVar() {
|
||||
suite.True(strings.Contains(received.Error(), id))
|
||||
}
|
||||
|
||||
func (suite *GraphConnectorErrorSuite) TestAsRecoverableError() {
|
||||
err := assert.AnError
|
||||
var recover RecoverableGCError
|
||||
suite.False(errors.As(err, &recover))
|
||||
aRecoverable := SetRecoverableError(err)
|
||||
suite.True(errors.As(aRecoverable, &recover))
|
||||
}
|
||||
|
||||
func (suite *GraphConnectorErrorSuite) TestAsNonRecoverableError() {
|
||||
err := assert.AnError
|
||||
var noRecover NonRecoverableGCError
|
||||
suite.False(errors.As(err, &noRecover))
|
||||
nonRecoverable := SetNonRecoverableError(err)
|
||||
suite.True(errors.As(nonRecoverable, &noRecover))
|
||||
}
|
||||
|
||||
func (suite *GraphConnectorErrorSuite) TestWrapAndAppend_Add3() {
|
||||
errOneTwo := WrapAndAppend("user1", assert.AnError, assert.AnError)
|
||||
combined := WrapAndAppend("unix36", assert.AnError, errOneTwo)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user