diff --git a/src/internal/connector/exchange/api/api.go b/src/internal/connector/exchange/api/api.go index 3fd15409f..acaeb9c5e 100644 --- a/src/internal/connector/exchange/api/api.go +++ b/src/internal/connector/exchange/api/api.go @@ -87,6 +87,18 @@ func newService(creds account.M365Config) (*graph.Service, error) { return graph.NewService(adapter), nil } +func (c Client) Contacts() Contacts { + return Contacts{c} +} + +func (c Client) Events() Events { + return Events{c} +} + +func (c Client) Mail() Mail { + return Mail{c} +} + // --------------------------------------------------------------------------- // helper funcs // --------------------------------------------------------------------------- diff --git a/src/internal/connector/exchange/api/api_test.go b/src/internal/connector/exchange/api/api_test.go index 00291087f..d3fe51350 100644 --- a/src/internal/connector/exchange/api/api_test.go +++ b/src/internal/connector/exchange/api/api_test.go @@ -174,11 +174,11 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() { }{ { name: "GraphQuery: Get All ContactFolders", - function: c.GetAllContactFolderNamesForUser, + function: c.Contacts().GetAllContactFolderNamesForUser, }, { name: "GraphQuery: Get All Calendars for User", - function: c.GetAllCalendarNamesForUser, + function: c.Events().GetAllCalendarNamesForUser, }, } diff --git a/src/internal/connector/exchange/api/contacts.go b/src/internal/connector/exchange/api/contacts.go index 7da6f2cc5..0356d88ef 100644 --- a/src/internal/connector/exchange/api/contacts.go +++ b/src/internal/connector/exchange/api/contacts.go @@ -13,9 +13,21 @@ import ( "github.com/alcionai/corso/src/internal/connector/support" ) +// --------------------------------------------------------------------------- +// controller +// --------------------------------------------------------------------------- + +type Contacts struct { + Client +} + +// --------------------------------------------------------------------------- +// methods +// --------------------------------------------------------------------------- + // CreateContactFolder makes a contact folder with the displayName of folderName. // If successful, returns the created folder object. -func (c Client) CreateContactFolder( +func (c Contacts) CreateContactFolder( ctx context.Context, user, folderName string, ) (models.ContactFolderable, error) { @@ -28,7 +40,7 @@ func (c Client) CreateContactFolder( // DeleteContactFolder deletes the ContactFolder associated with the M365 ID if permissions are valid. // Errors returned if the function call was not successful. -func (c Client) DeleteContactFolder( +func (c Contacts) DeleteContactFolder( ctx context.Context, user, folderID string, ) error { @@ -36,7 +48,7 @@ func (c Client) DeleteContactFolder( } // RetrieveContactDataForUser is a GraphRetrievalFun that returns all associated fields. -func (c Client) RetrieveContactDataForUser( +func (c Contacts) RetrieveContactDataForUser( ctx context.Context, user, m365ID string, ) (serialization.Parsable, error) { @@ -46,7 +58,7 @@ func (c Client) RetrieveContactDataForUser( // GetAllContactFolderNamesForUser is a GraphQuery function for getting // ContactFolderId and display names for contacts. All other information is omitted. // Does not return the default Contact Folder -func (c Client) GetAllContactFolderNamesForUser( +func (c Contacts) GetAllContactFolderNamesForUser( ctx context.Context, user string, ) (serialization.Parsable, error) { @@ -58,16 +70,13 @@ func (c Client) GetAllContactFolderNamesForUser( return c.stable.Client().UsersById(user).ContactFolders().Get(ctx, options) } -func (c Client) GetContactFolderByID( +func (c Contacts) GetContainerByID( ctx context.Context, userID, dirID string, - optionalFields ...string, -) (models.ContactFolderable, error) { - fields := append([]string{"displayName", "parentFolderId"}, optionalFields...) - - ofcf, err := optionsForContactFolderByID(fields) +) (graph.Container, error) { + ofcf, err := optionsForContactFolderByID([]string{"displayName", "parentFolderId"}) if err != nil { - return nil, errors.Wrapf(err, "options for contact folder: %v", fields) + return nil, errors.Wrap(err, "options for contact folder") } return c.stable.Client(). @@ -76,13 +85,13 @@ func (c Client) GetContactFolderByID( Get(ctx, ofcf) } -// EnumerateContactsFolders iterates through all of the users current +// EnumerateContainers iterates through all of the users current // contacts folders, converting each to a graph.CacheFolder, and calling // fn(cf) on each one. If fn(cf) errors, the error is aggregated // into a multierror that gets returned to the caller. // Folder hierarchy is represented in its current state, and does // not contain historical data. -func (c Client) EnumerateContactsFolders( +func (c Contacts) EnumerateContainers( ctx context.Context, userID, baseDirID string, fn func(graph.CacheFolder) error, @@ -138,9 +147,7 @@ func (c Client) EnumerateContactsFolders( return errs.ErrorOrNil() } -// FetchContactIDsFromDirectory function that returns a list of all the m365IDs of the contacts -// of the targeted directory -func (c Client) FetchContactIDsFromDirectory( +func (c Contacts) GetAddedAndRemovedItemIDs( ctx context.Context, user, directoryID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { diff --git a/src/internal/connector/exchange/api/events.go b/src/internal/connector/exchange/api/events.go index 3563d6fc8..bd6a68893 100644 --- a/src/internal/connector/exchange/api/events.go +++ b/src/internal/connector/exchange/api/events.go @@ -14,9 +14,21 @@ import ( "github.com/alcionai/corso/src/pkg/path" ) +// --------------------------------------------------------------------------- +// controller +// --------------------------------------------------------------------------- + +type Events struct { + Client +} + +// --------------------------------------------------------------------------- +// methods +// --------------------------------------------------------------------------- + // CreateCalendar makes an event Calendar with the name in the user's M365 exchange account // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=go -func (c Client) CreateCalendar( +func (c Events) CreateCalendar( ctx context.Context, user, calendarName string, ) (models.Calendarable, error) { @@ -28,7 +40,7 @@ func (c Client) CreateCalendar( // DeleteCalendar removes calendar from user's M365 account // Reference: https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=go -func (c Client) DeleteCalendar( +func (c Events) DeleteCalendar( ctx context.Context, user, calendarID string, ) error { @@ -36,7 +48,7 @@ func (c Client) DeleteCalendar( } // RetrieveEventDataForUser is a GraphRetrievalFunc that returns event data. -func (c Client) RetrieveEventDataForUser( +func (c Events) RetrieveEventDataForUser( ctx context.Context, user, m365ID string, ) (serialization.Parsable, error) { @@ -55,15 +67,15 @@ func (c Client) GetAllCalendarNamesForUser( return c.stable.Client().UsersById(user).Calendars().Get(ctx, options) } -// EnumerateCalendars iterates through all of the users current -// contacts folders, converting each to a graph.CacheFolder, and +// EnumerateContainers iterates through all of the users current +// calendars, converting each to a graph.CacheFolder, and // calling fn(cf) on each one. If fn(cf) errors, the error is // aggregated into a multierror that gets returned to the caller. // Folder hierarchy is represented in its current state, and does // not contain historical data. -func (c Client) EnumerateCalendars( +func (c Events) EnumerateContainers( ctx context.Context, - userID string, + userID, baseDirID string, fn func(graph.CacheFolder) error, ) error { service, err := c.service() @@ -112,8 +124,7 @@ func (c Client) EnumerateCalendars( return errs.ErrorOrNil() } -// FetchEventIDsFromCalendar returns a list of all M365IDs of events of the targeted Calendar. -func (c Client) FetchEventIDsFromCalendar( +func (c Events) GetAddedAndRemovedItemIDs( ctx context.Context, user, calendarID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { diff --git a/src/internal/connector/exchange/api/mail.go b/src/internal/connector/exchange/api/mail.go index 08a2ddb2e..bc10bf53b 100644 --- a/src/internal/connector/exchange/api/mail.go +++ b/src/internal/connector/exchange/api/mail.go @@ -13,9 +13,21 @@ import ( "github.com/alcionai/corso/src/internal/connector/support" ) +// --------------------------------------------------------------------------- +// controller +// --------------------------------------------------------------------------- + +type Mail struct { + Client +} + +// --------------------------------------------------------------------------- +// methods +// --------------------------------------------------------------------------- + // CreateMailFolder makes a mail folder iff a folder of the same name does not exist // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http -func (c Client) CreateMailFolder( +func (c Mail) CreateMailFolder( ctx context.Context, user, folder string, ) (models.MailFolderable, error) { @@ -27,7 +39,7 @@ func (c Client) CreateMailFolder( return c.stable.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil) } -func (c Client) CreateMailFolderWithParent( +func (c Mail) CreateMailFolderWithParent( ctx context.Context, user, folder, parentID string, ) (models.MailFolderable, error) { @@ -51,30 +63,47 @@ func (c Client) CreateMailFolderWithParent( // DeleteMailFolder removes a mail folder with the corresponding M365 ID from the user's M365 Exchange account // Reference: https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http -func (c Client) DeleteMailFolder( +func (c Mail) DeleteMailFolder( ctx context.Context, user, folderID string, ) error { return c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) } +func (c Mail) GetContainerByID( + ctx context.Context, + userID, dirID string, +) (graph.Container, error) { + service, err := c.service() + if err != nil { + return nil, err + } + + ofmf, err := optionsForMailFoldersItem([]string{"displayName", "parentFolderId"}) + if err != nil { + return nil, errors.Wrap(err, "options for mail folder") + } + + return service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf) +} + // RetrieveMessageDataForUser is a GraphRetrievalFunc that returns message data. -func (c Client) RetrieveMessageDataForUser( +func (c Mail) RetrieveMessageDataForUser( ctx context.Context, user, m365ID string, ) (serialization.Parsable, error) { return c.stable.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil) } -// EnumeratetMailFolders iterates through all of the users current +// EnumerateContainers iterates through all of the users current // mail folders, converting each to a graph.CacheFolder, and calling // fn(cf) on each one. If fn(cf) errors, the error is aggregated // into a multierror that gets returned to the caller. // Folder hierarchy is represented in its current state, and does // not contain historical data. -func (c Client) EnumerateMailFolders( +func (c Mail) EnumerateContainers( ctx context.Context, - userID string, + userID, baseDirID string, fn func(graph.CacheFolder) error, ) error { service, err := c.service() @@ -116,22 +145,7 @@ func (c Client) EnumerateMailFolders( return errs.ErrorOrNil() } -func (c Client) GetMailFolderByID( - ctx context.Context, - userID, dirID string, - optionalFields ...string, -) (models.MailFolderable, error) { - ofmf, err := optionsForMailFoldersItem(optionalFields) - if err != nil { - return nil, errors.Wrapf(err, "options for mail folder: %v", optionalFields) - } - - return c.stable.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf) -} - -// FetchMessageIDsFromDirectory function that returns a list of all the m365IDs of the exchange.Mail -// of the targeted directory -func (c Client) FetchMessageIDsFromDirectory( +func (c Mail) GetAddedAndRemovedItemIDs( ctx context.Context, user, directoryID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { diff --git a/src/internal/connector/exchange/contact_folder_cache.go b/src/internal/connector/exchange/contact_folder_cache.go index 5cb7bf25d..9ac83e665 100644 --- a/src/internal/connector/exchange/contact_folder_cache.go +++ b/src/internal/connector/exchange/contact_folder_cache.go @@ -5,7 +5,6 @@ import ( "github.com/pkg/errors" - "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/pkg/path" @@ -15,7 +14,8 @@ var _ graph.ContainerResolver = &contactFolderCache{} type contactFolderCache struct { *containerResolver - ac api.Client + enumer containersEnumerator + getter containerGetter userID string } @@ -24,7 +24,7 @@ func (cfc *contactFolderCache) populateContactRoot( directoryID string, baseContainerPath []string, ) error { - f, err := cfc.ac.GetContactFolderByID(ctx, cfc.userID, directoryID) + f, err := cfc.getter.GetContainerByID(ctx, cfc.userID, directoryID) if err != nil { return errors.Wrapf( err, @@ -53,7 +53,7 @@ func (cfc *contactFolderCache) Populate( return err } - err := cfc.ac.EnumerateContactsFolders(ctx, cfc.userID, baseID, cfc.addFolder) + err := cfc.enumer.EnumerateContainers(ctx, cfc.userID, baseID, cfc.addFolder) if err != nil { return err } diff --git a/src/internal/connector/exchange/container_resolver.go b/src/internal/connector/exchange/container_resolver.go index 772fe1f13..3541a3914 100644 --- a/src/internal/connector/exchange/container_resolver.go +++ b/src/internal/connector/exchange/container_resolver.go @@ -10,6 +10,29 @@ import ( "github.com/alcionai/corso/src/pkg/path" ) +// --------------------------------------------------------------------------- +// common interfaces +// --------------------------------------------------------------------------- + +type containerGetter interface { + GetContainerByID( + ctx context.Context, + userID, dirID string, + ) (graph.Container, error) +} + +type containersEnumerator interface { + EnumerateContainers( + ctx context.Context, + userID, baseDirID string, + fn func(graph.CacheFolder) error, + ) error +} + +// --------------------------------------------------------------------------- +// controller +// --------------------------------------------------------------------------- + // Exchange has a limit of 300 for folder depth. OneDrive has a limit on path // length of 400 characters (including separators) which would be roughly 200 // folders if each folder is only a single character. diff --git a/src/internal/connector/exchange/data_collections.go b/src/internal/connector/exchange/data_collections.go index 85e6c43da..62bc12f3f 100644 --- a/src/internal/connector/exchange/data_collections.go +++ b/src/internal/connector/exchange/data_collections.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" + "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/data" @@ -205,12 +206,25 @@ func DataCollections( return collections, errs } +func getterByType(ac api.Client, category path.CategoryType) (addedAndRemovedItemIDsGetter, error) { + switch category { + case path.EmailCategory: + return ac.Mail(), nil + case path.EventsCategory: + return ac.Events(), nil + case path.ContactsCategory: + return ac.Contacts(), nil + default: + return nil, fmt.Errorf("category %s not supported by getFetchIDFunc", category) + } +} + // createCollections - utility function that retrieves M365 // IDs through Microsoft Graph API. The selectors.ExchangeScope // determines the type of collections that are retrieved. func createCollections( ctx context.Context, - acct account.M365Config, + creds account.M365Config, user string, scope selectors.ExchangeScope, dps DeltaPaths, @@ -220,15 +234,22 @@ func createCollections( var ( errs *multierror.Error allCollections = make([]data.Collection, 0) + ac = api.Client{Credentials: creds} + category = scope.Category().PathType() ) + getter, err := getterByType(ac, category) + if err != nil { + return nil, err + } + // Create collection of ExchangeDataCollection collections := make(map[string]data.Collection) qp := graph.QueryParams{ - Category: scope.Category().PathType(), + Category: category, ResourceOwner: user, - Credentials: acct, + Credentials: creds, } foldersComplete, closer := observe.MessageWithCompletion(fmt.Sprintf("∙ %s - %s:", qp.Category, user)) @@ -243,6 +264,7 @@ func createCollections( err = filterContainersAndFillCollections( ctx, qp, + getter, collections, su, resolver, diff --git a/src/internal/connector/exchange/event_calendar_cache.go b/src/internal/connector/exchange/event_calendar_cache.go index fae9c35ad..c383a74e6 100644 --- a/src/internal/connector/exchange/event_calendar_cache.go +++ b/src/internal/connector/exchange/event_calendar_cache.go @@ -5,7 +5,6 @@ import ( "github.com/pkg/errors" - "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/pkg/path" ) @@ -14,7 +13,7 @@ var _ graph.ContainerResolver = &eventCalendarCache{} type eventCalendarCache struct { *containerResolver - ac api.Client + enumer containersEnumerator userID string } @@ -30,7 +29,7 @@ func (ecc *eventCalendarCache) Populate( ecc.containerResolver = newContainerResolver() } - err := ecc.ac.EnumerateCalendars(ctx, ecc.userID, ecc.addFolder) + err := ecc.enumer.EnumerateContainers(ctx, ecc.userID, "", ecc.addFolder) if err != nil { return err } diff --git a/src/internal/connector/exchange/exchange_data_collection.go b/src/internal/connector/exchange/exchange_data_collection.go index 5b1b05b81..8668e86b0 100644 --- a/src/internal/connector/exchange/exchange_data_collection.go +++ b/src/internal/connector/exchange/exchange_data_collection.go @@ -142,11 +142,11 @@ func (col *Collection) Items() <-chan data.Stream { func GetQueryAndSerializeFunc(ac api.Client, category path.CategoryType) (api.GraphRetrievalFunc, GraphSerializeFunc) { switch category { case path.ContactsCategory: - return ac.RetrieveContactDataForUser, serializeAndStreamContact + return ac.Contacts().RetrieveContactDataForUser, serializeAndStreamContact case path.EventsCategory: - return ac.RetrieveEventDataForUser, serializeAndStreamEvent + return ac.Events().RetrieveEventDataForUser, serializeAndStreamEvent case path.EmailCategory: - return ac.RetrieveMessageDataForUser, serializeAndStreamMessage + return ac.Mail().RetrieveMessageDataForUser, serializeAndStreamMessage // Unsupported options returns nil, nil default: return nil, nil diff --git a/src/internal/connector/exchange/folder_resolver_test.go b/src/internal/connector/exchange/folder_resolver_test.go index 68c17e408..36bf0ffc5 100644 --- a/src/internal/connector/exchange/folder_resolver_test.go +++ b/src/internal/connector/exchange/folder_resolver_test.go @@ -50,14 +50,15 @@ func (suite *CacheResolverSuite) TestPopulate() { eventFunc := func(t *testing.T) graph.ContainerResolver { return &eventCalendarCache{ userID: tester.M365UserID(t), - ac: ac, + enumer: ac.Events(), } } contactFunc := func(t *testing.T) graph.ContainerResolver { return &contactFolderCache{ userID: tester.M365UserID(t), - ac: ac, + enumer: ac.Contacts(), + getter: ac.Contacts(), } } diff --git a/src/internal/connector/exchange/mail_folder_cache.go b/src/internal/connector/exchange/mail_folder_cache.go index 4d9d184d3..a2b92593c 100644 --- a/src/internal/connector/exchange/mail_folder_cache.go +++ b/src/internal/connector/exchange/mail_folder_cache.go @@ -5,7 +5,6 @@ import ( "github.com/pkg/errors" - "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/pkg/path" @@ -18,7 +17,8 @@ var _ graph.ContainerResolver = &mailFolderCache{} // nameLookup map: Key: DisplayName Value: ID type mailFolderCache struct { *containerResolver - ac api.Client + enumer containersEnumerator + getter containerGetter userID string } @@ -33,7 +33,7 @@ func (mc *mailFolderCache) populateMailRoot( for _, fldr := range []string{rootFolderAlias, DefaultMailFolder} { var directory string - f, err := mc.ac.GetMailFolderByID(ctx, mc.userID, fldr, "displayName", "parentFolderId") + f, err := mc.getter.GetContainerByID(ctx, mc.userID, fldr) if err != nil { return errors.Wrap(err, "fetching root folder"+support.ConnectorStackErrorTrace(err)) } @@ -65,7 +65,7 @@ func (mc *mailFolderCache) Populate( return err } - err := mc.ac.EnumerateMailFolders(ctx, mc.userID, mc.addFolder) + err := mc.enumer.EnumerateContainers(ctx, mc.userID, "", mc.addFolder) if err != nil { return err } diff --git a/src/internal/connector/exchange/mail_folder_cache_test.go b/src/internal/connector/exchange/mail_folder_cache_test.go index 3cd58be91..6a55df0bc 100644 --- a/src/internal/connector/exchange/mail_folder_cache_test.go +++ b/src/internal/connector/exchange/mail_folder_cache_test.go @@ -84,9 +84,12 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() { ac, err := api.NewClient(suite.credentials) require.NoError(t, err) + acm := ac.Mail() + mfc := mailFolderCache{ userID: userID, - ac: ac, + enumer: acm, + getter: acm, } require.NoError(t, mfc.Populate(ctx, test.root, test.path...)) diff --git a/src/internal/connector/exchange/restore_test.go b/src/internal/connector/exchange/restore_test.go index 3b739c6bb..f439ea3ad 100644 --- a/src/internal/connector/exchange/restore_test.go +++ b/src/internal/connector/exchange/restore_test.go @@ -69,14 +69,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreContact() { folderName = "TestRestoreContact: " + common.FormatSimpleDateTime(now) ) - aFolder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) + aFolder, err := suite.ac.Contacts().CreateContactFolder(ctx, userID, folderName) require.NoError(t, err) folderID := *aFolder.GetId() defer func() { // Remove the folder containing contact prior to exiting test - err = suite.ac.DeleteContactFolder(ctx, userID, folderID) + err = suite.ac.Contacts().DeleteContactFolder(ctx, userID, folderID) assert.NoError(t, err) }() @@ -103,14 +103,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() { name = "TestRestoreEvent: " + common.FormatSimpleDateTime(time.Now()) ) - calendar, err := suite.ac.CreateCalendar(ctx, userID, name) + calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, name) require.NoError(t, err) calendarID := *calendar.GetId() defer func() { // Removes calendar containing events created during the test - err = suite.ac.DeleteCalendar(ctx, userID, calendarID) + err = suite.ac.Events().DeleteCalendar(ctx, userID, calendarID) assert.NoError(t, err) }() @@ -146,10 +146,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail", bytes: mockconnector.GetMockMessageBytes("Restore Exchange Object"), category: path.EmailCategory, - cleanupFunc: suite.ac.DeleteMailFolder, + cleanupFunc: suite.ac.Mail().DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailObject: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -159,10 +159,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: One Direct Attachment", bytes: mockconnector.GetMockMessageWithDirectAttachment("Restore 1 Attachment"), category: path.EmailCategory, - cleanupFunc: suite.ac.DeleteMailFolder, + cleanupFunc: suite.ac.Mail().DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithAttachment: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -172,10 +172,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: One Large Attachment", bytes: mockconnector.GetMockMessageWithLargeAttachment("Restore Large Attachment"), category: path.EmailCategory, - cleanupFunc: suite.ac.DeleteMailFolder, + cleanupFunc: suite.ac.Mail().DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithLargeAttachment: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -185,10 +185,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: Two Attachments", bytes: mockconnector.GetMockMessageWithTwoAttachments("Restore 2 Attachments"), category: path.EmailCategory, - cleanupFunc: suite.ac.DeleteMailFolder, + cleanupFunc: suite.ac.Mail().DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithAttachments: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -198,10 +198,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: Reference(OneDrive) Attachment", bytes: mockconnector.GetMessageWithOneDriveAttachment("Restore Reference(OneDrive) Attachment"), category: path.EmailCategory, - cleanupFunc: suite.ac.DeleteMailFolder, + cleanupFunc: suite.ac.Mail().DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithReferenceAttachment: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -212,10 +212,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Contact", bytes: mockconnector.GetMockContactBytes("Test_Omega"), category: path.ContactsCategory, - cleanupFunc: suite.ac.DeleteContactFolder, + cleanupFunc: suite.ac.Contacts().DeleteContactFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreContactObject: " + common.FormatSimpleDateTime(now) - folder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) + folder, err := suite.ac.Contacts().CreateContactFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -225,10 +225,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Events", bytes: mockconnector.GetDefaultMockEventBytes("Restored Event Object"), category: path.EventsCategory, - cleanupFunc: suite.ac.DeleteCalendar, + cleanupFunc: suite.ac.Events().DeleteCalendar, destination: func(t *testing.T, ctx context.Context) string { calendarName := "TestRestoreEventObject: " + common.FormatSimpleDateTime(now) - calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) + calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, calendarName) require.NoError(t, err) return *calendar.GetId() @@ -238,10 +238,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Event with Attachment", bytes: mockconnector.GetMockEventWithAttachment("Restored Event Attachment"), category: path.EventsCategory, - cleanupFunc: suite.ac.DeleteCalendar, + cleanupFunc: suite.ac.Events().DeleteCalendar, destination: func(t *testing.T, ctx context.Context) string { calendarName := "TestRestoreEventObject_" + common.FormatSimpleDateTime(now) - calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) + calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, calendarName) require.NoError(t, err) return *calendar.GetId() diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index 579a7915c..d646fb132 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -48,23 +48,27 @@ func PopulateExchangeContainerResolver( switch qp.Category { case path.EmailCategory: + acm := ac.Mail() res = &mailFolderCache{ userID: qp.ResourceOwner, - ac: ac, + getter: acm, + enumer: acm, } cacheRoot = rootFolderAlias case path.ContactsCategory: + acc := ac.Contacts() res = &contactFolderCache{ userID: qp.ResourceOwner, - ac: ac, + getter: acc, + enumer: acc, } cacheRoot = DefaultContactFolder case path.EventsCategory: res = &eventCalendarCache{ userID: qp.ResourceOwner, - ac: ac, + enumer: ac.Events(), } cacheRoot = DefaultCalendar diff --git a/src/internal/connector/exchange/service_iterators.go b/src/internal/connector/exchange/service_iterators.go index 34e8c2664..86e598605 100644 --- a/src/internal/connector/exchange/service_iterators.go +++ b/src/internal/connector/exchange/service_iterators.go @@ -2,7 +2,6 @@ package exchange import ( "context" - "fmt" "github.com/pkg/errors" @@ -16,6 +15,13 @@ import ( "github.com/alcionai/corso/src/pkg/selectors" ) +type addedAndRemovedItemIDsGetter interface { + GetAddedAndRemovedItemIDs( + ctx context.Context, + user, containerID, oldDeltaToken string, + ) ([]string, []string, api.DeltaUpdate, error) +} + // filterContainersAndFillCollections is a utility function // that places the M365 object ids belonging to specific directories // into a Collection. Messages outside of those directories are omitted. @@ -24,6 +30,7 @@ import ( func filterContainersAndFillCollections( ctx context.Context, qp graph.QueryParams, + getter addedAndRemovedItemIDsGetter, collections map[string]data.Collection, statusUpdater support.StatusUpdater, resolver graph.ContainerResolver, @@ -41,17 +48,14 @@ func filterContainersAndFillCollections( tombstones = makeTombstones(dps) ) - // TODO(rkeepers): pass in the api client instead of generating it here. + // TODO(rkeepers): this should be passed in from the caller, probably + // as an interface that satisfies the NewCollection requirements. + // But this will work for the short term. ac, err := api.NewClient(qp.Credentials) if err != nil { return err } - getJobs, err := getFetchIDFunc(ac, qp.Category) - if err != nil { - return support.WrapAndAppend(qp.ResourceOwner, err, errs) - } - for _, c := range resolver.Items() { if ctrlOpts.FailFast && errs != nil { return errs @@ -89,7 +93,7 @@ func filterContainersAndFillCollections( } } - added, removed, newDelta, err := getJobs(ctx, qp.ResourceOwner, cID, prevDelta) + added, removed, newDelta, err := getter.GetAddedAndRemovedItemIDs(ctx, qp.ResourceOwner, cID, prevDelta) if err != nil { if graph.IsErrDeletedInFlight(err) == nil { errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) @@ -217,24 +221,3 @@ func pathFromPrevString(ps string) (path.Path, error) { return p, nil } - -// FetchIDFunc collection of helper functions which return a list of all item -// IDs in the given container and a delta token for future requests if the -// container supports fetching delta records. -type FetchIDFunc func( - ctx context.Context, - user, containerID, oldDeltaToken string, -) ([]string, []string, api.DeltaUpdate, error) - -func getFetchIDFunc(ac api.Client, category path.CategoryType) (FetchIDFunc, error) { - switch category { - case path.EmailCategory: - return ac.FetchMessageIDsFromDirectory, nil - case path.EventsCategory: - return ac.FetchEventIDsFromCalendar, nil - case path.ContactsCategory: - return ac.FetchContactIDsFromDirectory, nil - default: - return nil, fmt.Errorf("category %s not supported by getFetchIDFunc", category) - } -} diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index 3cff4476b..cdf4541a9 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -455,9 +455,11 @@ func CreateContainerDestinaion( switch category { case path.EmailCategory: if directoryCache == nil { + acm := ac.Mail() mfc := &mailFolderCache{ userID: user, - ac: ac, + enumer: acm, + getter: acm, } caches[category] = mfc @@ -475,9 +477,11 @@ func CreateContainerDestinaion( case path.ContactsCategory: if directoryCache == nil { + acc := ac.Contacts() cfc := &contactFolderCache{ userID: user, - ac: ac, + enumer: acc, + getter: acc, } caches[category] = cfc newCache = true @@ -496,7 +500,7 @@ func CreateContainerDestinaion( if directoryCache == nil { ecc := &eventCalendarCache{ userID: user, - ac: ac, + enumer: ac.Events(), } caches[category] = ecc newCache = true @@ -543,7 +547,7 @@ func establishMailRestoreLocation( continue } - temp, err := ac.CreateMailFolderWithParent(ctx, user, folder, folderID) + temp, err := ac.Mail().CreateMailFolderWithParent(ctx, user, folder, folderID) if err != nil { // Should only error if cache malfunctions or incorrect parameters return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) @@ -590,7 +594,7 @@ func establishContactsRestoreLocation( return cached, nil } - temp, err := ac.CreateContactFolder(ctx, user, folders[0]) + temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } @@ -623,7 +627,7 @@ func establishEventsRestoreLocation( return cached, nil } - temp, err := ac.CreateCalendar(ctx, user, folders[0]) + temp, err := ac.Events().CreateCalendar(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } diff --git a/src/internal/operations/backup_integration_test.go b/src/internal/operations/backup_integration_test.go index 977f494bf..acf8e6dea 100644 --- a/src/internal/operations/backup_integration_test.go +++ b/src/internal/operations/backup_integration_test.go @@ -942,7 +942,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() { switch category { case path.EmailCategory: - ids, _, _, err := ac.FetchMessageIDsFromDirectory(ctx, suite.user, containerID, "") + ids, _, _, err := ac.Mail().GetAddedAndRemovedItemIDs(ctx, suite.user, containerID, "") require.NoError(t, err, "getting message ids") require.NotEmpty(t, ids, "message ids in folder") @@ -950,7 +950,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() { require.NoError(t, err, "deleting email item: %s", support.ConnectorStackErrorTrace(err)) case path.ContactsCategory: - ids, _, _, err := ac.FetchContactIDsFromDirectory(ctx, suite.user, containerID, "") + ids, _, _, err := ac.Contacts().GetAddedAndRemovedItemIDs(ctx, suite.user, containerID, "") require.NoError(t, err, "getting contact ids") require.NotEmpty(t, ids, "contact ids in folder")