Recoverable and Non-Recoverable errors (#232)

GraphConnector exports 2 error types. Recoverable and NonRecoverable. The package also implements error checks to confirm if errors are one of the exported types.
This commit is contained in:
Danny 2022-06-27 20:29:15 -04:00 committed by GitHub
parent 60eb8eec08
commit 3ee7ff0c0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 18 deletions

View File

@ -199,7 +199,7 @@ func (gc *GraphConnector) RestoreMessages(ctx context.Context, dc DataCollection
sentMessage, err := gc.client.UsersById(user).MailFoldersById(address).Messages().Post(clone) sentMessage, err := gc.client.UsersById(user).MailFoldersById(address).Messages().Post(clone)
if err != nil { if err != nil {
errs = support.WrapAndAppend(data.UUID()+": "+ errs = support.WrapAndAppend(data.UUID()+": "+
support.ConnectorStackErrorTrace(ctx, err), err, errs) support.ConnectorStackErrorTrace(err), err, errs)
continue continue
// TODO: Add to retry Handler for the for failure // TODO: Add to retry Handler for the for failure
} }
@ -275,7 +275,8 @@ func (gc *GraphConnector) serializeMessages(ctx context.Context, user string) ([
} }
err = objectWriter.WriteObjectValue("", message) err = objectWriter.WriteObjectValue("", message)
if err != nil { if err != nil {
errs = support.WrapAndAppend(*message.GetId(), err, errs) errs = support.WrapAndAppend(*message.GetId(), support.SetNonRecoverableError(err),
errs)
return true return true
} }
byteArray, err = objectWriter.GetSerializedContent() byteArray, err = objectWriter.GetSerializedContent()
@ -321,3 +322,15 @@ func (gc *GraphConnector) Status() string {
} }
return gc.status.String() return gc.status.String()
} }
// IsRecoverableError returns true iff error is a RecoverableGCEerror
func IsRecoverableError(e error) bool {
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
return errors.As(e, &nonRecoverable)
}

View File

@ -46,12 +46,12 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector() {
suite.NotNil(suite.connector) suite.NotNil(suite.connector)
} }
type DiconnectedGraphConnectorSuite struct { type DisconnectedGraphConnectorSuite struct {
suite.Suite suite.Suite
} }
func TestDisconnectedGraphSuite(t *testing.T) { func TestDisconnectedGraphSuite(t *testing.T) {
suite.Run(t, new(DiconnectedGraphConnectorSuite)) suite.Run(t, new(DisconnectedGraphConnectorSuite))
} }
func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_setTenantUsers() { func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_setTenantUsers() {
@ -92,7 +92,7 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_restoreMessages(
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
} }
func (suite *DiconnectedGraphConnectorSuite) TestBadConnection() { func (suite *DisconnectedGraphConnectorSuite) TestBadConnection() {
table := []struct { table := []struct {
name string name string
@ -144,7 +144,7 @@ func Contains(elems []string, value string) bool {
return false return false
} }
func (suite *DiconnectedGraphConnectorSuite) TestBuild() { func (suite *DisconnectedGraphConnectorSuite) TestBuild() {
names := make(map[string]string) names := make(map[string]string)
names["Al"] = "Bundy" names["Al"] = "Bundy"
names["Ellen"] = "Ripley" names["Ellen"] = "Ripley"
@ -160,7 +160,7 @@ func (suite *DiconnectedGraphConnectorSuite) TestBuild() {
} }
func (suite *DiconnectedGraphConnectorSuite) TestInterfaceAlignment() { func (suite *DisconnectedGraphConnectorSuite) TestInterfaceAlignment() {
var dc DataCollection var dc DataCollection
concrete := NewExchangeDataCollection("Check", []string{"interface", "works"}) concrete := NewExchangeDataCollection("Check", []string{"interface", "works"})
dc = &concrete dc = &concrete
@ -168,7 +168,7 @@ func (suite *DiconnectedGraphConnectorSuite) TestInterfaceAlignment() {
} }
func (suite *DiconnectedGraphConnectorSuite) TestGraphConnector_Status() { func (suite *DisconnectedGraphConnectorSuite) TestGraphConnector_Status() {
gc := GraphConnector{} gc := GraphConnector{}
suite.Equal(len(gc.Status()), 0) suite.Equal(len(gc.Status()), 0)
status, err := support.CreateStatus(support.Restore, 12, 9, 8, status, err := support.CreateStatus(support.Restore, 12, 9, 8,
@ -177,3 +177,50 @@ func (suite *DiconnectedGraphConnectorSuite) TestGraphConnector_Status() {
gc.SetStatus(*status) gc.SetStatus(*status)
suite.Greater(len(gc.Status()), 0) suite.Greater(len(gc.Status()), 0)
} }
func (suite *DisconnectedGraphConnectorSuite) TestGraphConnector_ErrorChecking() {
tests := []struct {
name string
err error
returnRecoverable bool
returnNonRecoverable bool
}{
{
name: "Neither Option",
err: errors.New("regular error"),
returnRecoverable: false,
returnNonRecoverable: false,
},
{
name: "Validate Recoverable",
err: support.SetRecoverableError(errors.New("Recoverable")),
returnRecoverable: true,
returnNonRecoverable: false,
},
{name: "Validate NonRecoverable",
err: support.SetNonRecoverableError(errors.New("Non-recoverable")),
returnRecoverable: false,
returnNonRecoverable: true,
},
{
name: "Wrapped Recoverable",
err: support.SetRecoverableError(support.WrapAndAppend(
"Wrapped Recoverable", errors.New("Recoverable"), nil)),
returnRecoverable: true,
returnNonRecoverable: false,
},
{
name: "On Nil",
err: nil,
returnRecoverable: false,
returnNonRecoverable: 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)
})
}
}

View File

@ -1,7 +1,6 @@
package support package support
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -9,10 +8,35 @@ import (
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
msgraph_errors "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" msgraph_errors "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/pkg/logger"
) )
// 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()
}
func SetRecoverableError(e error) error {
return &RecoverableGCError{err: e}
}
// NonRecoverableGCError is a permanent query error
type NonRecoverableGCError struct {
err error
}
func (nrgc *NonRecoverableGCError) Error() string {
return nrgc.err.Error()
}
func SetNonRecoverableError(e error) error {
return &NonRecoverableGCError{err: e}
}
// WrapErrorAndAppend helper function used to attach identifying information to an error // WrapErrorAndAppend helper function used to attach identifying information to an error
// and return it as a mulitierror // and return it as a mulitierror
func WrapAndAppend(identifier string, e error, previous error) error { func WrapAndAppend(identifier string, e error, previous error) error {
@ -42,10 +66,10 @@ func GetNumberOfErrors(err error) int {
// ListErrors is a helper method used to return the string of errors when // ListErrors is a helper method used to return the string of errors when
// the multiError library is used. // the multiError library is used.
// depends on ConnectorStackErrorTrace // depends on ConnectorStackErrorTrace
func ListErrors(ctx context.Context, multi multierror.Error) string { func ListErrors(multi multierror.Error) string {
aString := "" aString := ""
for idx, err := range multi.Errors { for idx, err := range multi.Errors {
detail := ConnectorStackErrorTrace(ctx, err) detail := ConnectorStackErrorTrace(err)
if detail == "" { if detail == "" {
detail = fmt.Sprintf("%v", err) detail = fmt.Sprintf("%v", err)
} }
@ -67,7 +91,7 @@ func concatenateStringFromPointers(orig string, pointers []*string) string {
// ConnectorStackErrorTrace is a helper function that wraps the // ConnectorStackErrorTrace is a helper function that wraps the
// stack trace for oDataError types from querying the M365 back store. // stack trace for oDataError types from querying the M365 back store.
func ConnectorStackErrorTrace(ctx context.Context, e error) string { func ConnectorStackErrorTrace(e error) string {
eMessage := "" eMessage := ""
if oDataError, ok := e.(msgraph_errors.ODataErrorable); ok { if oDataError, ok := e.(msgraph_errors.ODataErrorable); ok {
// Get MainError // Get MainError
@ -99,7 +123,6 @@ func ConnectorStackErrorTrace(ctx context.Context, e error) string {
} }
} }
if inners != nil { if inners != nil {
logger.Ctx(ctx).Debug("error contains inner errors")
eMessage = eMessage + "\nConnector Section:" eMessage = eMessage + "\nConnector Section:"
client := inners.GetClientRequestId() client := inners.GetClientRequestId()
rId := inners.GetRequestId() rId := inners.GetRequestId()

View File

@ -1,7 +1,6 @@
package support package support
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -27,8 +26,8 @@ func (suite *GraphConnectorErrorSuite) TestWrapAndAppend() {
suite.True(strings.Contains(returnErr.Error(), "arc376")) suite.True(strings.Contains(returnErr.Error(), "arc376"))
suite.Error(returnErr) suite.Error(returnErr)
multi := &multierror.Error{Errors: []error{err1, err2}} multi := &multierror.Error{Errors: []error{err1, err2}}
suite.True(strings.Contains(ListErrors(context.Background(), *multi), "two")) // Does not contain the wrapped information suite.True(strings.Contains(ListErrors(*multi), "two")) // Does not contain the wrapped information
suite.T().Log(ListErrors(context.Background(), *multi)) suite.T().Log(ListErrors(*multi))
} }
func (suite *GraphConnectorErrorSuite) TestWrapAndAppend_OnVar() { func (suite *GraphConnectorErrorSuite) TestWrapAndAppend_OnVar() {