add fault & clues to exchange api (#2492)

## Does this PR need a docs update or release note?

- [x]  No 

## Type of change

- [x] 🧹 Tech Debt/Cleanup

## Issue(s)

* #1970

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-02-15 10:01:04 -07:00 committed by GitHub
parent e70bf25f6e
commit 26d286138b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 338 additions and 290 deletions

View File

@ -81,7 +81,7 @@ func handleGetCommand(cmd *cobra.Command, args []string) error {
return err return err
} }
err = runDisplayM365JSON(ctx, creds, user, m365ID) err = runDisplayM365JSON(ctx, creds, user, m365ID, fault.New(true))
if err != nil { if err != nil {
return Only(ctx, errors.Wrapf(err, "unable to create mock from M365: %s", m365ID)) return Only(ctx, errors.Wrapf(err, "unable to create mock from M365: %s", m365ID))
} }
@ -93,6 +93,7 @@ func runDisplayM365JSON(
ctx context.Context, ctx context.Context,
creds account.M365Config, creds account.M365Config,
user, itemID string, user, itemID string,
errs *fault.Errors,
) error { ) error {
var ( var (
bs []byte bs []byte
@ -108,11 +109,11 @@ func runDisplayM365JSON(
switch cat { switch cat {
case path.EmailCategory: case path.EmailCategory:
bs, err = getItem(ctx, ac.Mail(), user, itemID) bs, err = getItem(ctx, ac.Mail(), user, itemID, errs)
case path.EventsCategory: case path.EventsCategory:
bs, err = getItem(ctx, ac.Events(), user, itemID) bs, err = getItem(ctx, ac.Events(), user, itemID, errs)
case path.ContactsCategory: case path.ContactsCategory:
bs, err = getItem(ctx, ac.Contacts(), user, itemID) bs, err = getItem(ctx, ac.Contacts(), user, itemID, errs)
default: default:
return fmt.Errorf("unable to process category: %s", cat) return fmt.Errorf("unable to process category: %s", cat)
} }
@ -142,6 +143,7 @@ type itemer interface {
GetItem( GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, user, itemID string,
errs *fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) ) (serialization.Parsable, *details.ExchangeInfo, error)
Serialize( Serialize(
ctx context.Context, ctx context.Context,
@ -154,8 +156,9 @@ func getItem(
ctx context.Context, ctx context.Context,
itm itemer, itm itemer,
user, itemID string, user, itemID string,
errs *fault.Errors,
) ([]byte, error) { ) ([]byte, error) {
sp, _, err := itm.GetItem(ctx, user, itemID) sp, _, err := itm.GetItem(ctx, user, itemID, errs)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "getting item") return nil, errors.Wrap(err, "getting item")
} }

View File

@ -1,5 +1,7 @@
package ptr package ptr
import "time"
// ptr package is a common package used for pointer // ptr package is a common package used for pointer
// access and deserialization. // access and deserialization.
@ -19,3 +21,25 @@ func Val[T any](ptr *T) T {
return *ptr return *ptr
} }
// ValOK behaves the same as Val, except it also gives
// a boolean response for whether the pointer was nil
// (false) or non-nil (true).
func ValOK[T any](ptr *T) (T, bool) {
if ptr == nil {
return *new(T), false
}
return *ptr, true
}
// OrNow returns the value of the provided time, if the
// parameter is non-nil. Otherwise it returns the current
// time in UTC.
func OrNow(t *time.Time) time.Time {
if t == nil {
return time.Now().UTC()
}
return *t
}

View File

@ -3,12 +3,13 @@ package api
import ( import (
"context" "context"
"strings" "strings"
"time"
"github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
) )
@ -17,8 +18,6 @@ import (
// common types and consts // common types and consts
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const numberOfRetries = 3
// DeltaUpdate holds the results of a current delta token. It normally // DeltaUpdate holds the results of a current delta token. It normally
// gets produced when aggregating the addition and removal of items in // gets produced when aggregating the addition and removal of items in
// a delta-queriable folder. // a delta-queriable folder.
@ -122,34 +121,24 @@ func newLargeItemService(creds account.M365Config) (*graph.Service, error) {
// checkIDAndName is a helper function to ensure that // checkIDAndName is a helper function to ensure that
// the ID and name pointers are set prior to being called. // the ID and name pointers are set prior to being called.
func checkIDAndName(c graph.Container) error { func checkIDAndName(c graph.Container) error {
idPtr := c.GetId() id := ptr.Val(c.GetId())
if idPtr == nil || len(*idPtr) == 0 { if len(id) == 0 {
return errors.New("folder without ID") return errors.New("container missing ID")
} }
ptr := c.GetDisplayName() dn := ptr.Val(c.GetDisplayName())
if ptr == nil || len(*ptr) == 0 { if len(dn) == 0 {
return errors.Errorf("folder %s without display name", *idPtr) return clues.New("container missing display name").With("container_id", id)
} }
return nil return nil
} }
func orNow(t *time.Time) time.Time {
if t == nil {
return time.Now().UTC()
}
return *t
}
func HasAttachments(body models.ItemBodyable) bool { func HasAttachments(body models.ItemBodyable) bool {
if body.GetContent() == nil || body.GetContentType() == nil || if body.GetContent() == nil || body.GetContentType() == nil ||
*body.GetContentType() == models.TEXT_BODYTYPE || len(*body.GetContent()) == 0 { *body.GetContentType() == models.TEXT_BODYTYPE || len(*body.GetContent()) == 0 {
return false return false
} }
content := *body.GetContent() return strings.Contains(ptr.Val(body.GetContent()), "src=\"cid:")
return strings.Contains(content, "src=\"cid:")
} }

View File

@ -5,18 +5,16 @@ import (
"fmt" "fmt"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/hashicorp/go-multierror"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kioser "github.com/microsoft/kiota-serialization-json-go" kioser "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -47,7 +45,12 @@ func (c Contacts) CreateContactFolder(
temp := folderName temp := folderName
requestBody.SetDisplayName(&temp) requestBody.SetDisplayName(&temp)
return c.stable.Client().UsersById(user).ContactFolders().Post(ctx, requestBody, nil) mdl, err := c.stable.Client().UsersById(user).ContactFolders().Post(ctx, requestBody, nil)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return mdl, nil
} }
// DeleteContainer deletes the ContactFolder associated with the M365 ID if permissions are valid. // DeleteContainer deletes the ContactFolder associated with the M365 ID if permissions are valid.
@ -55,22 +58,23 @@ func (c Contacts) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
return c.stable.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil) err := c.stable.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil)
if err != nil {
return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return nil
} }
// GetItem retrieves a Contactable item. // GetItem retrieves a Contactable item.
func (c Contacts) GetItem( func (c Contacts) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, user, itemID string,
_ *fault.Errors, // no attachments to iterate over, so this goes unused
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
var ( cont, err := c.stable.Client().UsersById(user).ContactsById(itemID).Get(ctx, nil)
cont models.Contactable
err error
)
cont, err = c.stable.Client().UsersById(user).ContactsById(itemID).Get(ctx, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return cont, ContactInfo(cont), nil return cont, ContactInfo(cont), nil
@ -82,14 +86,15 @@ func (c Contacts) GetContainerByID(
) (graph.Container, error) { ) (graph.Container, error) {
ofcf, err := optionsForContactFolderByID([]string{"displayName", "parentFolderId"}) ofcf, err := optionsForContactFolderByID([]string{"displayName", "parentFolderId"})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "options for contact folder") return nil, clues.Wrap(err, "setting contact folder options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var resp models.ContactFolderable resp, err := c.stable.Client().UsersById(userID).ContactFoldersById(dirID).Get(ctx, ofcf)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
resp, err = c.stable.Client().UsersById(userID).ContactFoldersById(dirID).Get(ctx, ofcf) return resp, nil
return resp, err
} }
// EnumerateContainers iterates through all of the users current // EnumerateContainers iterates through all of the users current
@ -102,21 +107,21 @@ func (c Contacts) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
errs *fault.Errors,
) error { ) error {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return err return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var ( fields := []string{"displayName", "parentFolderId"}
errs *multierror.Error
resp models.ContactFolderCollectionResponseable
fields = []string{"displayName", "parentFolderId"}
)
ofcf, err := optionsForContactChildFolders(fields) ofcf, err := optionsForContactChildFolders(fields)
if err != nil { if err != nil {
return errors.Wrapf(err, "options for contact child folders: %v", fields) return clues.Wrap(err, "setting contact child folder options").
WithClues(ctx).
WithAll(graph.ErrData(err)...).
With("options_fields", fields)
} }
builder := service.Client(). builder := service.Client().
@ -125,32 +130,42 @@ func (c Contacts) EnumerateContainers(
ChildFolders() ChildFolders()
for { for {
resp, err = builder.Get(ctx, ofcf) resp, err := builder.Get(ctx, ofcf)
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
for _, fold := range resp.GetValue() { for _, fold := range resp.GetValue() {
if errs.Err() != nil {
return errs.Err()
}
if err := checkIDAndName(fold); err != nil { if err := checkIDAndName(fold); err != nil {
errs = multierror.Append(err, errs) errs.Add(clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...))
continue continue
} }
fctx := clues.AddAll(
ctx,
"container_id", ptr.Val(fold.GetId()),
"container_display_name", ptr.Val(fold.GetDisplayName()))
temp := graph.NewCacheFolder(fold, nil, nil) temp := graph.NewCacheFolder(fold, nil, nil)
if err := fn(temp); err != nil { if err := fn(temp); err != nil {
errs = multierror.Append(err, errs) errs.Add(clues.Stack(err).WithClues(fctx).WithAll(graph.ErrData(err)...))
continue continue
} }
} }
if resp.GetOdataNextLink() == nil { link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break break
} }
builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(*resp.GetOdataNextLink(), service.Adapter()) builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(link, service.Adapter())
} }
return errs.ErrorOrNil() return errs.Err()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -166,14 +181,12 @@ type contactPager struct {
} }
func (p *contactPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) { func (p *contactPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) {
var ( resp, err := p.builder.Get(ctx, p.options)
resp api.DeltaPageLinker if err != nil {
err error return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
) }
resp, err = p.builder.Get(ctx, p.options) return resp, nil
return resp, err
} }
func (p *contactPager) setNext(nextLink string) { func (p *contactPager) setNext(nextLink string) {
@ -190,41 +203,43 @@ func (c Contacts) GetAddedAndRemovedItemIDs(
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var ( var resetDelta bool
errs *multierror.Error
resetDelta bool
)
ctx = clues.AddAll( ctx = clues.AddAll(
ctx, ctx,
"category", selectors.ExchangeContact, "category", selectors.ExchangeContact,
"folder_id", directoryID) "container_id", directoryID)
options, err := optionsForContactFoldersItemDelta([]string{"parentFolderId"}) options, err := optionsForContactFoldersItemDelta([]string{"parentFolderId"})
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, errors.Wrap(err, "getting query options") return nil,
nil,
DeltaUpdate{},
clues.Wrap(err, "setting contact folder options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
if len(oldDelta) > 0 { if len(oldDelta) > 0 {
builder := users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, service.Adapter()) var (
pgr := &contactPager{service, builder, options} builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, service.Adapter())
pgr = &contactPager{service, builder, options}
)
added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr) added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr)
// note: happy path, not the error condition // note: happy path, not the error condition
if err == nil { if err == nil {
return added, removed, DeltaUpdate{deltaURL, false}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, false}, err
} }
// only return on error if it is NOT a delta issue. // only return on error if it is NOT a delta issue.
// on bad deltas we retry the call with the regular builder // on bad deltas we retry the call with the regular builder
if !graph.IsErrInvalidDelta(err) { if !graph.IsErrInvalidDelta(err) {
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
resetDelta = true resetDelta = true
errs = nil
} }
builder := service.Client().UsersById(user).ContactFoldersById(directoryID).Contacts().Delta() builder := service.Client().UsersById(user).ContactFoldersById(directoryID).Contacts().Delta()
@ -235,7 +250,7 @@ func (c Contacts) GetAddedAndRemovedItemIDs(
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, err
} }
return added, removed, DeltaUpdate{deltaURL, resetDelta}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, resetDelta}, nil
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -250,10 +265,10 @@ func (c Contacts) Serialize(
) ([]byte, error) { ) ([]byte, error) {
contact, ok := item.(models.Contactable) contact, ok := item.(models.Contactable)
if !ok { if !ok {
return nil, fmt.Errorf("expected Contactable, got %T", item) return nil, clues.Wrap(fmt.Errorf("parseable type: %T", item), "parsable is not a Contactable")
} }
ctx = clues.Add(ctx, "item_id", *contact.GetId()) ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
var ( var (
err error err error
@ -263,12 +278,12 @@ func (c Contacts) Serialize(
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", contact); err != nil { if err = writer.WriteObjectValue("", contact); err != nil {
return nil, clues.Stack(err).WithClues(ctx) return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
bs, err := writer.GetSerializedContent() bs, err := writer.GetSerializedContent()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "serializing contact") return nil, clues.Wrap(err, "serializing contact").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return bs, nil return bs, nil
@ -286,6 +301,6 @@ func ContactInfo(contact models.Contactable) *details.ExchangeInfo {
ItemType: details.ExchangeContact, ItemType: details.ExchangeContact,
ContactName: name, ContactName: name,
Created: created, Created: created,
Modified: orNow(contact.GetLastModifiedDateTime()), Modified: ptr.OrNow(contact.GetLastModifiedDateTime()),
} }
} }

View File

@ -6,22 +6,18 @@ import (
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/hashicorp/go-multierror"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kioser "github.com/microsoft/kiota-serialization-json-go" kioser "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors"
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -50,7 +46,12 @@ func (c Events) CreateCalendar(
requestbody := models.NewCalendar() requestbody := models.NewCalendar()
requestbody.SetName(&calendarName) requestbody.SetName(&calendarName)
return c.stable.Client().UsersById(user).Calendars().Post(ctx, requestbody, nil) mdl, err := c.stable.Client().UsersById(user).Calendars().Post(ctx, requestbody, nil)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return mdl, nil
} }
// DeleteContainer removes a calendar from user's M365 account // DeleteContainer removes a calendar from user's M365 account
@ -59,7 +60,12 @@ func (c Events) DeleteContainer(
ctx context.Context, ctx context.Context,
user, calendarID string, user, calendarID string,
) error { ) error {
return c.stable.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil) err := c.stable.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil)
if err != nil {
return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return nil
} }
func (c Events) GetContainerByID( func (c Events) GetContainerByID(
@ -68,20 +74,17 @@ func (c Events) GetContainerByID(
) (graph.Container, error) { ) (graph.Container, error) {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return nil, err return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
ofc, err := optionsForCalendarsByID([]string{"name", "owner"}) ofc, err := optionsForCalendarsByID([]string{"name", "owner"})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "options for event calendar") return nil, clues.Wrap(err, "setting event calendar options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var cal models.Calendarable cal, err := service.Client().UsersById(userID).CalendarsById(containerID).Get(ctx, ofc)
cal, err = service.Client().UsersById(userID).CalendarsById(containerID).Get(ctx, ofc)
if err != nil { if err != nil {
return nil, err return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return graph.CalendarDisplayable{Calendarable: cal}, nil return graph.CalendarDisplayable{Calendarable: cal}, nil
@ -91,47 +94,36 @@ func (c Events) GetContainerByID(
func (c Events) GetItem( func (c Events) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, user, itemID string,
errs *fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
var ( var (
event models.Eventable
err error err error
event models.Eventable
) )
event, err = c.stable.Client().UsersById(user).EventsById(itemID).Get(ctx, nil) event, err = c.stable.Client().UsersById(user).EventsById(itemID).Get(ctx, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var ( if *event.GetHasAttachments() || HasAttachments(event.GetBody()) {
errs *multierror.Error options := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
options = &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
Expand: []string{"microsoft.graph.itemattachment/item"}, Expand: []string{"microsoft.graph.itemattachment/item"},
}, },
} }
)
if *event.GetHasAttachments() || HasAttachments(event.GetBody()) {
for count := 0; count < numberOfRetries; count++ {
attached, err := c.largeItem. attached, err := c.largeItem.
Client(). Client().
UsersById(user). UsersById(user).
EventsById(itemID). EventsById(itemID).
Attachments(). Attachments().
Get(ctx, options) Get(ctx, options)
if err == nil {
event.SetAttachments(attached.GetValue())
break
}
logger.Ctx(ctx).Debugw("retrying event attachment download", "err", err)
errs = multierror.Append(errs, err)
}
if err != nil { if err != nil {
logger.Ctx(ctx).Errorw("event attachment download exceeded maximum retries", "err", errs) return nil, nil, clues.Wrap(err, "event attachment download").WithClues(ctx).WithAll(graph.ErrData(err)...)
return nil, nil, support.WrapAndAppend(itemID, errors.Wrap(err, "download event attachment"), nil)
} }
event.SetAttachments(attached.GetValue())
} }
return event, EventInfo(event), nil return event, EventInfo(event), nil
@ -147,57 +139,57 @@ func (c Events) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
errs *fault.Errors,
) error { ) error {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return err return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var (
resp models.CalendarCollectionResponseable
errs *multierror.Error
)
ofc, err := optionsForCalendars([]string{"name"}) ofc, err := optionsForCalendars([]string{"name"})
if err != nil { if err != nil {
return errors.Wrapf(err, "options for event calendars") return clues.Wrap(err, "setting calendar options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
builder := service.Client().UsersById(userID).Calendars() builder := service.Client().UsersById(userID).Calendars()
for { for {
var err error resp, err := builder.Get(ctx, ofc)
resp, err = builder.Get(ctx, ofc)
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
for _, cal := range resp.GetValue() { for _, cal := range resp.GetValue() {
cd := CalendarDisplayable{Calendarable: cal} cd := CalendarDisplayable{Calendarable: cal}
if err := checkIDAndName(cd); err != nil { if err := checkIDAndName(cd); err != nil {
errs = multierror.Append(err, errs) errs.Add(clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...))
continue continue
} }
fctx := clues.AddAll(
ctx,
"container_id", ptr.Val(cal.GetId()),
"container_name", ptr.Val(cal.GetName()))
temp := graph.NewCacheFolder( temp := graph.NewCacheFolder(
cd, cd,
path.Builder{}.Append(*cd.GetId()), // storage path path.Builder{}.Append(ptr.Val(cd.GetId())), // storage path
path.Builder{}.Append(*cd.GetDisplayName())) // display location path.Builder{}.Append(ptr.Val(cd.GetDisplayName()))) // display location
if err := fn(temp); err != nil { if err := fn(temp); err != nil {
errs = multierror.Append(err, errs) errs.Add(clues.Stack(err).WithClues(fctx).WithAll(graph.ErrData(err)...))
continue continue
} }
} }
if resp.GetOdataNextLink() == nil { link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break break
} }
builder = users.NewItemCalendarsRequestBuilder(*resp.GetOdataNextLink(), service.Adapter()) builder = users.NewItemCalendarsRequestBuilder(link, service.Adapter())
} }
return errs.ErrorOrNil() return errs.Err()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -217,14 +209,12 @@ type eventPager struct {
} }
func (p *eventPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) { func (p *eventPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) {
var ( resp, err := p.builder.Get(ctx, p.options)
resp api.DeltaPageLinker if err != nil {
err error return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
) }
resp, err = p.builder.Get(ctx, p.options) return resp, nil
return resp, err
} }
func (p *eventPager) setNext(nextLink string) { func (p *eventPager) setNext(nextLink string) {
@ -244,33 +234,30 @@ func (c Events) GetAddedAndRemovedItemIDs(
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, err
} }
var ( var resetDelta bool
resetDelta bool
errs *multierror.Error
)
ctx = clues.AddAll( ctx = clues.AddAll(
ctx, ctx,
"category", selectors.ExchangeEvent,
"calendar_id", calendarID) "calendar_id", calendarID)
if len(oldDelta) > 0 { if len(oldDelta) > 0 {
builder := users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, service.Adapter()) var (
pgr := &eventPager{service, builder, nil} builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, service.Adapter())
pgr = &eventPager{service, builder, nil}
)
added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr) added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr)
// note: happy path, not the error condition // note: happy path, not the error condition
if err == nil { if err == nil {
return added, removed, DeltaUpdate{deltaURL, false}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, false}, nil
} }
// only return on error if it is NOT a delta issue. // only return on error if it is NOT a delta issue.
// on bad deltas we retry the call with the regular builder // on bad deltas we retry the call with the regular builder
if !graph.IsErrInvalidDelta(err) { if !graph.IsErrInvalidDelta(err) {
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
resetDelta = true resetDelta = true
errs = nil
} }
// Graph SDK only supports delta queries against events on the beta version, so we're // Graph SDK only supports delta queries against events on the beta version, so we're
@ -291,7 +278,7 @@ func (c Events) GetAddedAndRemovedItemIDs(
} }
// Events don't have a delta endpoint so just return an empty string. // Events don't have a delta endpoint so just return an empty string.
return added, removed, DeltaUpdate{deltaURL, resetDelta}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, resetDelta}, nil
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -306,10 +293,10 @@ func (c Events) Serialize(
) ([]byte, error) { ) ([]byte, error) {
event, ok := item.(models.Eventable) event, ok := item.(models.Eventable)
if !ok { if !ok {
return nil, fmt.Errorf("expected Eventable, got %T", item) return nil, clues.Wrap(fmt.Errorf("parseable type: %T", item), "parsable is not an Eventable")
} }
ctx = clues.Add(ctx, "item_id", *event.GetId()) ctx = clues.Add(ctx, "item_id", ptr.Val(event.GetId()))
var ( var (
err error err error
@ -319,12 +306,12 @@ func (c Events) Serialize(
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", event); err != nil { if err = writer.WriteObjectValue("", event); err != nil {
return nil, clues.Stack(err).WithClues(ctx) return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
bs, err := writer.GetSerializedContent() bs, err := writer.GetSerializedContent()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "serializing calendar event") return nil, clues.Wrap(err, "serializing event").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return bs, nil return bs, nil
@ -368,22 +355,18 @@ func EventInfo(evt models.Eventable) *details.ExchangeInfo {
) )
if evt.GetOrganizer() != nil && if evt.GetOrganizer() != nil &&
evt.GetOrganizer().GetEmailAddress() != nil && evt.GetOrganizer().GetEmailAddress() != nil {
evt.GetOrganizer().GetEmailAddress().GetAddress() != nil { organizer = ptr.Val(evt.GetOrganizer().GetEmailAddress().GetAddress())
organizer = *evt.GetOrganizer().
GetEmailAddress().
GetAddress()
} }
if evt.GetRecurrence() != nil { if evt.GetRecurrence() != nil {
recurs = true recurs = true
} }
if evt.GetStart() != nil && if evt.GetStart() != nil && len(ptr.Val(evt.GetStart().GetDateTime())) > 0 {
evt.GetStart().GetDateTime() != nil {
// timeString has 'Z' literal added to ensure the stored // timeString has 'Z' literal added to ensure the stored
// DateTime is not: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) // DateTime is not: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
startTime := *evt.GetStart().GetDateTime() + "Z" startTime := ptr.Val(evt.GetStart().GetDateTime()) + "Z"
output, err := common.ParseTime(startTime) output, err := common.ParseTime(startTime)
if err == nil { if err == nil {
@ -391,11 +374,10 @@ func EventInfo(evt models.Eventable) *details.ExchangeInfo {
} }
} }
if evt.GetEnd() != nil && if evt.GetEnd() != nil && len(ptr.Val(evt.GetEnd().GetDateTime())) > 0 {
evt.GetEnd().GetDateTime() != nil {
// timeString has 'Z' literal added to ensure the stored // timeString has 'Z' literal added to ensure the stored
// DateTime is not: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) // DateTime is not: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
endTime := *evt.GetEnd().GetDateTime() + "Z" endTime := ptr.Val(evt.GetEnd().GetDateTime()) + "Z"
output, err := common.ParseTime(endTime) output, err := common.ParseTime(endTime)
if err == nil { if err == nil {
@ -411,6 +393,6 @@ func EventInfo(evt models.Eventable) *details.ExchangeInfo {
EventEnd: end, EventEnd: end,
EventRecurs: recurs, EventRecurs: recurs,
Created: created, Created: created,
Modified: orNow(evt.GetLastModifiedDateTime()), Modified: ptr.OrNow(evt.GetLastModifiedDateTime()),
} }
} }

View File

@ -5,19 +5,16 @@ import (
"fmt" "fmt"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/hashicorp/go-multierror"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kioser "github.com/microsoft/kiota-serialization-json-go" kioser "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -49,7 +46,12 @@ func (c Mail) CreateMailFolder(
requestBody.SetDisplayName(&folder) requestBody.SetDisplayName(&folder)
requestBody.SetIsHidden(&isHidden) requestBody.SetIsHidden(&isHidden)
return c.stable.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil) mdl, err := c.stable.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return mdl, nil
} }
func (c Mail) CreateMailFolderWithParent( func (c Mail) CreateMailFolderWithParent(
@ -58,7 +60,7 @@ func (c Mail) CreateMailFolderWithParent(
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return nil, err return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
isHidden := false isHidden := false
@ -66,12 +68,17 @@ func (c Mail) CreateMailFolderWithParent(
requestBody.SetDisplayName(&folder) requestBody.SetDisplayName(&folder)
requestBody.SetIsHidden(&isHidden) requestBody.SetIsHidden(&isHidden)
return service. mdl, err := service.
Client(). Client().
UsersById(user). UsersById(user).
MailFoldersById(parentID). MailFoldersById(parentID).
ChildFolders(). ChildFolders().
Post(ctx, requestBody, nil) Post(ctx, requestBody, nil)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return mdl, nil
} }
// DeleteContainer removes a mail folder with the corresponding M365 ID from the user's M365 Exchange account // DeleteContainer removes a mail folder with the corresponding M365 ID from the user's M365 Exchange account
@ -80,7 +87,12 @@ func (c Mail) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
return c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) err := c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil)
if err != nil {
return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
return nil
} }
func (c Mail) GetContainerByID( func (c Mail) GetContainerByID(
@ -89,19 +101,20 @@ func (c Mail) GetContainerByID(
) (graph.Container, error) { ) (graph.Container, error) {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return nil, err return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
ofmf, err := optionsForMailFoldersItem([]string{"displayName", "parentFolderId"}) ofmf, err := optionsForMailFoldersItem([]string{"displayName", "parentFolderId"})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "options for mail folder") return nil, clues.Wrap(err, "setting mail folder options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var resp graph.Container resp, err := service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf)
if err != nil {
return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
}
resp, err = service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf) return resp, nil
return resp, err
} }
// GetItem retrieves a Messageable item. If the item contains an attachment, that // GetItem retrieves a Messageable item. If the item contains an attachment, that
@ -109,45 +122,31 @@ func (c Mail) GetContainerByID(
func (c Mail) GetItem( func (c Mail) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, user, itemID string,
errs *fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
var ( mail, err := c.stable.Client().UsersById(user).MessagesById(itemID).Get(ctx, nil)
mail models.Messageable
err error
)
mail, err = c.stable.Client().UsersById(user).MessagesById(itemID).Get(ctx, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var errs *multierror.Error
if *mail.GetHasAttachments() || HasAttachments(mail.GetBody()) { if *mail.GetHasAttachments() || HasAttachments(mail.GetBody()) {
options := &users.ItemMessagesItemAttachmentsRequestBuilderGetRequestConfiguration{ options := &users.ItemMessagesItemAttachmentsRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemMessagesItemAttachmentsRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemMessagesItemAttachmentsRequestBuilderGetQueryParameters{
Expand: []string{"microsoft.graph.itemattachment/item"}, Expand: []string{"microsoft.graph.itemattachment/item"},
}, },
} }
for count := 0; count < numberOfRetries; count++ {
attached, err := c.largeItem. attached, err := c.largeItem.
Client(). Client().
UsersById(user). UsersById(user).
MessagesById(itemID). MessagesById(itemID).
Attachments(). Attachments().
Get(ctx, options) Get(ctx, options)
if err == nil {
mail.SetAttachments(attached.GetValue())
break
}
logger.Ctx(ctx).Debugw("retrying mail attachment download", "err", err)
errs = multierror.Append(errs, err)
}
if err != nil { if err != nil {
logger.Ctx(ctx).Errorw("mail attachment download exceeded maximum retries", "err", errs) return nil, nil, clues.Wrap(err, "mail attachment download").WithClues(ctx).WithAll(graph.ErrData(err)...)
return nil, nil, support.WrapAndAppend(itemID, errors.Wrap(err, "downloading mail attachment"), nil)
} }
mail.SetAttachments(attached.GetValue())
} }
return mail, MailInfo(mail), nil return mail, MailInfo(mail), nil
@ -163,46 +162,46 @@ func (c Mail) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
errs *fault.Errors,
) error { ) error {
service, err := c.service() service, err := c.service()
if err != nil { if err != nil {
return err return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
var ( builder := service.Client().
resp users.ItemMailFoldersDeltaResponseable
errs *multierror.Error
builder = service.Client().
UsersById(userID). UsersById(userID).
MailFolders(). MailFolders().
Delta() Delta()
)
for { for {
var err error resp, err := builder.Get(ctx, nil)
resp, err = builder.Get(ctx, nil)
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
for _, v := range resp.GetValue() { for _, v := range resp.GetValue() {
fctx := clues.AddAll(
ctx,
"container_id", ptr.Val(v.GetId()),
"container_name", ptr.Val(v.GetDisplayName()))
temp := graph.NewCacheFolder(v, nil, nil) temp := graph.NewCacheFolder(v, nil, nil)
if err := fn(temp); err != nil { if err := fn(temp); err != nil {
errs = multierror.Append(errs, errors.Wrap(err, "iterating mail folders delta")) errs.Add(clues.Stack(err).WithClues(fctx).WithAll(graph.ErrData(err)...))
continue continue
} }
} }
link := resp.GetOdataNextLink() link, ok := ptr.ValOK(resp.GetOdataNextLink())
if link == nil { if !ok {
break break
} }
builder = users.NewItemMailFoldersDeltaRequestBuilder(*link, service.Adapter()) builder = users.NewItemMailFoldersDeltaRequestBuilder(link, service.Adapter())
} }
return errs.ErrorOrNil() return errs.Err()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -218,14 +217,12 @@ type mailPager struct {
} }
func (p *mailPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) { func (p *mailPager) getPage(ctx context.Context) (api.DeltaPageLinker, error) {
var ( page, err := p.builder.Get(ctx, p.options)
page api.DeltaPageLinker if err != nil {
err error return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
) }
page, err = p.builder.Get(ctx, p.options) return page, nil
return page, err
} }
func (p *mailPager) setNext(nextLink string) { func (p *mailPager) setNext(nextLink string) {
@ -246,7 +243,6 @@ func (c Mail) GetAddedAndRemovedItemIDs(
} }
var ( var (
errs *multierror.Error
deltaURL string deltaURL string
resetDelta bool resetDelta bool
) )
@ -258,17 +254,22 @@ func (c Mail) GetAddedAndRemovedItemIDs(
options, err := optionsForFolderMessagesDelta([]string{"isRead"}) options, err := optionsForFolderMessagesDelta([]string{"isRead"})
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, errors.Wrap(err, "getting query options") return nil,
nil,
DeltaUpdate{},
clues.Wrap(err, "setting contact folder options").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
if len(oldDelta) > 0 { if len(oldDelta) > 0 {
builder := users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, service.Adapter()) var (
pgr := &mailPager{service, builder, options} builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, service.Adapter())
pgr = &mailPager{service, builder, options}
)
added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr) added, removed, deltaURL, err := getItemsAddedAndRemovedFromContainer(ctx, pgr)
// note: happy path, not the error condition // note: happy path, not the error condition
if err == nil { if err == nil {
return added, removed, DeltaUpdate{deltaURL, false}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, false}, err
} }
// only return on error if it is NOT a delta issue. // only return on error if it is NOT a delta issue.
// on bad deltas we retry the call with the regular builder // on bad deltas we retry the call with the regular builder
@ -277,7 +278,6 @@ func (c Mail) GetAddedAndRemovedItemIDs(
} }
resetDelta = true resetDelta = true
errs = nil
} }
builder := service.Client().UsersById(user).MailFoldersById(directoryID).Messages().Delta() builder := service.Client().UsersById(user).MailFoldersById(directoryID).Messages().Delta()
@ -288,7 +288,7 @@ func (c Mail) GetAddedAndRemovedItemIDs(
return nil, nil, DeltaUpdate{}, err return nil, nil, DeltaUpdate{}, err
} }
return added, removed, DeltaUpdate{deltaURL, resetDelta}, errs.ErrorOrNil() return added, removed, DeltaUpdate{deltaURL, resetDelta}, nil
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -303,10 +303,10 @@ func (c Mail) Serialize(
) ([]byte, error) { ) ([]byte, error) {
msg, ok := item.(models.Messageable) msg, ok := item.(models.Messageable)
if !ok { if !ok {
return nil, fmt.Errorf("expected Messageable, got %T", item) return nil, clues.Wrap(fmt.Errorf("parseable type: %T", item), "parsable is not a Messageable")
} }
ctx = clues.Add(ctx, "item_id", *msg.GetId()) ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId()))
var ( var (
err error err error
@ -316,12 +316,12 @@ func (c Mail) Serialize(
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", msg); err != nil { if err = writer.WriteObjectValue("", msg); err != nil {
return nil, clues.Stack(err).WithClues(ctx) return nil, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
bs, err := writer.GetSerializedContent() bs, err := writer.GetSerializedContent()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "serializing email") return nil, clues.Wrap(err, "serializing email").WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return bs, nil return bs, nil
@ -338,9 +338,8 @@ func MailInfo(msg models.Messageable) *details.ExchangeInfo {
created := ptr.Val(msg.GetCreatedDateTime()) created := ptr.Val(msg.GetCreatedDateTime())
if msg.GetSender() != nil && if msg.GetSender() != nil &&
msg.GetSender().GetEmailAddress() != nil && msg.GetSender().GetEmailAddress() != nil {
msg.GetSender().GetEmailAddress().GetAddress() != nil { sender = ptr.Val(msg.GetSender().GetEmailAddress().GetAddress())
sender = *msg.GetSender().GetEmailAddress().GetAddress()
} }
return &details.ExchangeInfo{ return &details.ExchangeInfo{
@ -349,6 +348,6 @@ func MailInfo(msg models.Messageable) *details.ExchangeInfo {
Subject: subject, Subject: subject,
Received: received, Received: received,
Created: created, Created: created,
Modified: orNow(msg.GetLastModifiedDateTime()), Modified: ptr.OrNow(msg.GetLastModifiedDateTime()),
} }
} }

View File

@ -2,12 +2,13 @@ package api
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
) )
@ -34,7 +35,7 @@ type getIDAndAddtler interface {
func toValues[T any](a any) ([]getIDAndAddtler, error) { func toValues[T any](a any) ([]getIDAndAddtler, error) {
gv, ok := a.(interface{ GetValue() []T }) gv, ok := a.(interface{ GetValue() []T })
if !ok { if !ok {
return nil, errors.Errorf("response of type [%T] does not comply with the GetValue() interface", a) return nil, clues.Wrap(fmt.Errorf("%T", a), "does not comply with the GetValue() interface")
} }
items := gv.GetValue() items := gv.GetValue()
@ -45,7 +46,7 @@ func toValues[T any](a any) ([]getIDAndAddtler, error) {
ri, ok := a.(getIDAndAddtler) ri, ok := a.(getIDAndAddtler)
if !ok { if !ok {
return nil, errors.Errorf("item of type [%T] does not comply with the getIDAndAddtler interface", item) return nil, clues.Wrap(fmt.Errorf("%T", item), "does not comply with the getIDAndAddtler interface")
} }
r = append(r, ri) r = append(r, ri)
@ -72,18 +73,14 @@ func getItemsAddedAndRemovedFromContainer(
// get the next page of data, check for standard errors // get the next page of data, check for standard errors
resp, err := pager.getPage(ctx) resp, err := pager.getPage(ctx)
if err != nil { if err != nil {
if graph.IsErrDeletedInFlight(err) || graph.IsErrInvalidDelta(err) { return nil, nil, deltaURL, clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
return nil, nil, deltaURL, err
}
return nil, nil, deltaURL, errors.Wrap(err, support.ConnectorStackErrorTrace(err))
} }
// each category type responds with a different interface, but all // each category type responds with a different interface, but all
// of them comply with GetValue, which is where we'll get our item data. // of them comply with GetValue, which is where we'll get our item data.
items, err := pager.valuesIn(resp) items, err := pager.valuesIn(resp)
if err != nil { if err != nil {
return nil, nil, "", err return nil, nil, "", clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
itemCount += len(items) itemCount += len(items)
@ -100,9 +97,9 @@ func getItemsAddedAndRemovedFromContainer(
// be 'changed' or 'deleted'. We don't really care about the cause: both // be 'changed' or 'deleted'. We don't really care about the cause: both
// cases are handled the same way in storage. // cases are handled the same way in storage.
if item.GetAdditionalData()[graph.AddtlDataRemoved] == nil { if item.GetAdditionalData()[graph.AddtlDataRemoved] == nil {
addedIDs = append(addedIDs, *item.GetId()) addedIDs = append(addedIDs, ptr.Val(item.GetId()))
} else { } else {
removedIDs = append(removedIDs, *item.GetId()) removedIDs = append(removedIDs, ptr.Val(item.GetId()))
} }
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -46,6 +47,7 @@ func (cfc *contactFolderCache) populateContactRoot(
// as of (Oct-07-2022) // as of (Oct-07-2022)
func (cfc *contactFolderCache) Populate( func (cfc *contactFolderCache) Populate(
ctx context.Context, ctx context.Context,
errs *fault.Errors,
baseID string, baseID string,
baseContainerPather ...string, baseContainerPather ...string,
) error { ) error {
@ -53,7 +55,7 @@ func (cfc *contactFolderCache) Populate(
return errors.Wrap(err, "initializing") return errors.Wrap(err, "initializing")
} }
err := cfc.enumer.EnumerateContainers(ctx, cfc.userID, baseID, cfc.addFolder) err := cfc.enumer.EnumerateContainers(ctx, cfc.userID, baseID, cfc.addFolder, errs)
if err != nil { if err != nil {
return errors.Wrap(err, "enumerating containers") return errors.Wrap(err, "enumerating containers")
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -27,6 +28,7 @@ type containersEnumerator interface {
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
errs *fault.Errors,
) error ) error
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -657,7 +658,8 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() {
m365, m365,
test.pathFunc1(t), test.pathFunc1(t),
folderName, folderName,
directoryCaches) directoryCaches,
fault.New(true))
require.NoError(t, err) require.NoError(t, err)
resolver := directoryCaches[test.category] resolver := directoryCaches[test.category]
@ -675,7 +677,8 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() {
m365, m365,
test.pathFunc2(t), test.pathFunc2(t),
parentContainer, parentContainer,
directoryCaches) directoryCaches,
fault.New(true))
require.NoError(t, err) require.NoError(t, err)
_, _, err = resolver.IDToPath(ctx, secondID, test.useIDForPath) _, _, err = resolver.IDToPath(ctx, secondID, test.useIDForPath)

View File

@ -264,7 +264,7 @@ func createCollections(
defer closer() defer closer()
defer close(foldersComplete) defer close(foldersComplete)
resolver, err := PopulateExchangeContainerResolver(ctx, qp) resolver, err := PopulateExchangeContainerResolver(ctx, qp, errs)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "populating container cache") return nil, errors.Wrap(err, "populating container cache")
} }

View File

@ -554,7 +554,7 @@ func (suite *DataCollectionsIntegrationSuite) TestEventsSerializationRegression(
return nil return nil
} }
require.NoError(suite.T(), ac.Events().EnumerateContainers(ctx, suite.user, DefaultCalendar, fn)) require.NoError(suite.T(), ac.Events().EnumerateContainers(ctx, suite.user, DefaultCalendar, fn, fault.New(true)))
tests := []struct { tests := []struct {
name, expected string name, expected string

View File

@ -7,6 +7,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -61,6 +62,7 @@ func (ecc *eventCalendarCache) populateEventRoot(ctx context.Context) error {
// @param baseID: ignored. Present to conform to interface // @param baseID: ignored. Present to conform to interface
func (ecc *eventCalendarCache) Populate( func (ecc *eventCalendarCache) Populate(
ctx context.Context, ctx context.Context,
errs *fault.Errors,
baseID string, baseID string,
baseContainerPath ...string, baseContainerPath ...string,
) error { ) error {
@ -72,7 +74,8 @@ func (ecc *eventCalendarCache) Populate(
ctx, ctx,
ecc.userID, ecc.userID,
"", "",
ecc.addFolder) ecc.addFolder,
errs)
if err != nil { if err != nil {
return errors.Wrap(err, "enumerating containers") return errors.Wrap(err, "enumerating containers")
} }

View File

@ -19,6 +19,7 @@ import (
"github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -43,6 +44,7 @@ type itemer interface {
GetItem( GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, user, itemID string,
errs *fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) ) (serialization.Parsable, *details.ExchangeInfo, error)
Serialize( Serialize(
ctx context.Context, ctx context.Context,
@ -250,7 +252,12 @@ func (col *Collection) streamItems(ctx context.Context) {
err error err error
) )
item, info, err = getItemWithRetries(ctx, user, id, col.items) item, info, err = getItemWithRetries(
ctx,
user,
id,
col.items,
fault.New(true)) // temporary way to force a failFast error
if err != nil { if err != nil {
// Don't report errors for deleted items as there's no way for us to // Don't report errors for deleted items as there's no way for us to
// back up data that is gone. Record it as a "success", since there's // back up data that is gone. Record it as a "success", since there's
@ -298,6 +305,7 @@ func getItemWithRetries(
ctx context.Context, ctx context.Context,
userID, itemID string, userID, itemID string,
items itemer, items itemer,
errs *fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
var ( var (
item serialization.Parsable item serialization.Parsable
@ -306,7 +314,7 @@ func getItemWithRetries(
) )
for i := 1; i <= numberOfRetries; i++ { for i := 1; i <= numberOfRetries; i++ {
item, info, err = items.GetItem(ctx, userID, itemID) item, info, err = items.GetItem(ctx, userID, itemID, errs)
if err == nil { if err == nil {
break break
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -29,12 +30,17 @@ type mockItemer struct {
func (mi *mockItemer) GetItem( func (mi *mockItemer) GetItem(
context.Context, context.Context,
string, string, string, string,
*fault.Errors,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
mi.getCount++ mi.getCount++
return nil, nil, mi.getErr return nil, nil, mi.getErr
} }
func (mi *mockItemer) Serialize(context.Context, serialization.Parsable, string, string) ([]byte, error) { func (mi *mockItemer) Serialize(
context.Context,
serialization.Parsable,
string, string,
) ([]byte, error) {
mi.serializeCount++ mi.serializeCount++
return nil, mi.serializeErr return nil, mi.serializeErr
} }
@ -224,7 +230,7 @@ func (suite *ExchangeDataCollectionSuite) TestGetItemWithRetries() {
defer flush() defer flush()
// itemer is mocked, so only the errors are configured atm. // itemer is mocked, so only the errors are configured atm.
_, _, err := getItemWithRetries(ctx, "userID", "itemID", test.items) _, _, err := getItemWithRetries(ctx, "userID", "itemID", test.items, fault.New(true))
test.expectErr(t, err) test.expectErr(t, err)
}) })
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/fault"
) )
type CacheResolverSuite struct { type CacheResolverSuite struct {
@ -119,7 +120,7 @@ func (suite *CacheResolverSuite) TestPopulate() {
for _, test := range tests { for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
resolver := test.resolverFunc(t) resolver := test.resolverFunc(t)
require.NoError(t, resolver.Populate(ctx, test.root, test.basePath)) require.NoError(t, resolver.Populate(ctx, fault.New(true), test.root, test.basePath))
_, isFound := resolver.PathInCache(test.folderInCache) _, isFound := resolver.PathInCache(test.folderInCache)
test.canFind(t, isFound) test.canFind(t, isFound)

View File

@ -7,6 +7,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -71,6 +72,7 @@ func (mc *mailFolderCache) populateMailRoot(ctx context.Context) error {
// for the base container in the cache. // for the base container in the cache.
func (mc *mailFolderCache) Populate( func (mc *mailFolderCache) Populate(
ctx context.Context, ctx context.Context,
errs *fault.Errors,
baseID string, baseID string,
baseContainerPath ...string, baseContainerPath ...string,
) error { ) error {
@ -78,7 +80,7 @@ func (mc *mailFolderCache) Populate(
return errors.Wrap(err, "initializing") return errors.Wrap(err, "initializing")
} }
err := mc.enumer.EnumerateContainers(ctx, mc.userID, "", mc.addFolder) err := mc.enumer.EnumerateContainers(ctx, mc.userID, "", mc.addFolder, errs)
if err != nil { if err != nil {
return errors.Wrap(err, "enumerating containers") return errors.Wrap(err, "enumerating containers")
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/fault"
) )
const ( const (
@ -92,7 +93,7 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
getter: acm, getter: acm,
} }
require.NoError(t, mfc.Populate(ctx, test.root, test.path...)) require.NoError(t, mfc.Populate(ctx, fault.New(true), test.root, test.path...))
p, l, err := mfc.IDToPath(ctx, testFolderID, true) p, l, err := mfc.IDToPath(ctx, testFolderID, true)
require.NoError(t, err) require.NoError(t, err)

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -35,6 +36,7 @@ func createService(credentials account.M365Config) (*graph.Service, error) {
func PopulateExchangeContainerResolver( func PopulateExchangeContainerResolver(
ctx context.Context, ctx context.Context,
qp graph.QueryParams, qp graph.QueryParams,
errs *fault.Errors,
) (graph.ContainerResolver, error) { ) (graph.ContainerResolver, error) {
var ( var (
res graph.ContainerResolver res graph.ContainerResolver
@ -78,7 +80,7 @@ func PopulateExchangeContainerResolver(
return nil, clues.New("no container resolver registered for category").WithClues(ctx) return nil, clues.New("no container resolver registered for category").WithClues(ctx)
} }
if err := res.Populate(ctx, cacheRoot); err != nil { if err := res.Populate(ctx, errs, cacheRoot); err != nil {
return nil, clues.Wrap(err, "populating directory resolver").WithClues(ctx) return nil, clues.Wrap(err, "populating directory resolver").WithClues(ctx)
} }

View File

@ -92,7 +92,7 @@ func (m mockResolver) IDToPath(context.Context, string, bool) (*path.Builder, *p
return nil, nil, nil return nil, nil, nil
} }
func (m mockResolver) PathInCache(string) (string, bool) { return "", false } func (m mockResolver) PathInCache(string) (string, bool) { return "", false }
func (m mockResolver) Populate(context.Context, string, ...string) error { return nil } func (m mockResolver) Populate(context.Context, *fault.Errors, string, ...string) error { return nil }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// tests // tests

View File

@ -330,7 +330,8 @@ func RestoreExchangeDataCollections(
creds, creds,
dc.FullPath(), dc.FullPath(),
dest.ContainerName, dest.ContainerName,
userCaches) userCaches,
errs)
if err != nil { if err != nil {
errs.Add(clues.Wrap(err, "creating destination").WithClues(ctx)) errs.Add(clues.Wrap(err, "creating destination").WithClues(ctx))
continue continue
@ -471,6 +472,7 @@ func CreateContainerDestination(
directory path.Path, directory path.Path,
destination string, destination string,
caches map[path.CategoryType]graph.ContainerResolver, caches map[path.CategoryType]graph.ContainerResolver,
errs *fault.Errors,
) (string, error) { ) (string, error) {
var ( var (
newCache = false newCache = false
@ -508,7 +510,8 @@ func CreateContainerDestination(
folders, folders,
directoryCache, directoryCache,
user, user,
newCache) newCache,
errs)
case path.ContactsCategory: case path.ContactsCategory:
folders := append([]string{destination}, directory.Folders()...) folders := append([]string{destination}, directory.Folders()...)
@ -531,7 +534,8 @@ func CreateContainerDestination(
folders, folders,
directoryCache, directoryCache,
user, user,
newCache) newCache,
errs)
case path.EventsCategory: case path.EventsCategory:
dest := destination dest := destination
@ -561,7 +565,8 @@ func CreateContainerDestination(
folders, folders,
directoryCache, directoryCache,
user, user,
newCache) newCache,
errs)
default: default:
return "", clues.Wrap(fmt.Errorf("%T", category), "not support for exchange cache").WithClues(ctx) return "", clues.Wrap(fmt.Errorf("%T", category), "not support for exchange cache").WithClues(ctx)
@ -580,6 +585,7 @@ func establishMailRestoreLocation(
mfc graph.ContainerResolver, mfc graph.ContainerResolver,
user string, user string,
isNewCache bool, isNewCache bool,
errs *fault.Errors,
) (string, error) { ) (string, error) {
// Process starts with the root folder in order to recreate // Process starts with the root folder in order to recreate
// the top-level folder with the same tactic // the top-level folder with the same tactic
@ -609,7 +615,7 @@ func establishMailRestoreLocation(
// newCache to false in this we'll only try to populate it once per function // newCache to false in this we'll only try to populate it once per function
// call even if we make a new cache. // call even if we make a new cache.
if isNewCache { if isNewCache {
if err := mfc.Populate(ctx, rootFolderAlias); err != nil { if err := mfc.Populate(ctx, errs, rootFolderAlias); err != nil {
return "", errors.Wrap(err, "populating folder cache") return "", errors.Wrap(err, "populating folder cache")
} }
@ -638,6 +644,7 @@ func establishContactsRestoreLocation(
cfc graph.ContainerResolver, cfc graph.ContainerResolver,
user string, user string,
isNewCache bool, isNewCache bool,
errs *fault.Errors,
) (string, error) { ) (string, error) {
cached, ok := cfc.PathInCache(folders[0]) cached, ok := cfc.PathInCache(folders[0])
if ok { if ok {
@ -654,7 +661,7 @@ func establishContactsRestoreLocation(
folderID := *temp.GetId() folderID := *temp.GetId()
if isNewCache { if isNewCache {
if err := cfc.Populate(ctx, folderID, folders[0]); err != nil { if err := cfc.Populate(ctx, errs, folderID, folders[0]); err != nil {
return "", errors.Wrap(err, "populating contact cache") return "", errors.Wrap(err, "populating contact cache")
} }
@ -673,6 +680,7 @@ func establishEventsRestoreLocation(
ecc graph.ContainerResolver, // eventCalendarCache ecc graph.ContainerResolver, // eventCalendarCache
user string, user string,
isNewCache bool, isNewCache bool,
errs *fault.Errors,
) (string, error) { ) (string, error) {
// Need to prefix with the "Other Calendars" folder so lookup happens properly. // Need to prefix with the "Other Calendars" folder so lookup happens properly.
cached, ok := ecc.PathInCache(folders[0]) cached, ok := ecc.PathInCache(folders[0])
@ -690,7 +698,7 @@ func establishEventsRestoreLocation(
folderID := *temp.GetId() folderID := *temp.GetId()
if isNewCache { if isNewCache {
if err = ecc.Populate(ctx, folderID, folders[0]); err != nil { if err = ecc.Populate(ctx, errs, folderID, folders[0]); err != nil {
return "", errors.Wrap(err, "populating event cache") return "", errors.Wrap(err, "populating event cache")
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -62,7 +63,7 @@ type ContainerResolver interface {
// @param ctx is necessary param for Graph API tracing // @param ctx is necessary param for Graph API tracing
// @param baseFolderID represents the M365ID base that the resolver will // @param baseFolderID represents the M365ID base that the resolver will
// conclude its search. Default input is "". // conclude its search. Default input is "".
Populate(ctx context.Context, baseFolderID string, baseContainerPather ...string) error Populate(ctx context.Context, errs *fault.Errors, baseFolderID string, baseContainerPather ...string) error
// PathInCache performs a look up of a path reprensentation // PathInCache performs a look up of a path reprensentation
// and returns the m365ID of directory iff the pathString // and returns the m365ID of directory iff the pathString

View File

@ -771,7 +771,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() {
ResourceOwner: suite.user, ResourceOwner: suite.user,
Credentials: m365, Credentials: m365,
} }
cr, err := exchange.PopulateExchangeContainerResolver(ctx, qp) cr, err := exchange.PopulateExchangeContainerResolver(ctx, qp, fault.New(true))
require.NoError(t, err, "populating %s container resolver", category) require.NoError(t, err, "populating %s container resolver", category)
for destName, dest := range gen.dests { for destName, dest := range gen.dests {
@ -890,7 +890,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() {
ResourceOwner: suite.user, ResourceOwner: suite.user,
Credentials: m365, Credentials: m365,
} }
cr, err := exchange.PopulateExchangeContainerResolver(ctx, qp) cr, err := exchange.PopulateExchangeContainerResolver(ctx, qp, fault.New(true))
require.NoError(t, err, "populating %s container resolver", category) require.NoError(t, err, "populating %s container resolver", category)
p, err := path.FromDataLayerPath(deets.Entries[0].RepoRef, true) p, err := path.FromDataLayerPath(deets.Entries[0].RepoRef, true)