diff --git a/src/internal/m365/collection/drive/item_collector.go b/src/internal/m365/collection/drive/item_collector.go index a0099a584..f48cd735b 100644 --- a/src/internal/m365/collection/drive/item_collector.go +++ b/src/internal/m365/collection/drive/item_collector.go @@ -85,7 +85,7 @@ func collectItems( invalidPrevDelta = true newPaths = map[string]string{} - pager.Reset(ctx) + pager.Reset() continue } @@ -94,16 +94,11 @@ func collectItems( return DeltaUpdate{}, nil, nil, graph.Wrap(ctx, err, "getting page") } - vals, err := pager.ValuesIn(page) - if err != nil { - return DeltaUpdate{}, nil, nil, graph.Wrap(ctx, err, "extracting items from response") - } - err = collector( ctx, driveID, driveName, - vals, + page.GetValue(), oldPaths, newPaths, excluded, diff --git a/src/internal/m365/collection/drive/url_cache.go b/src/internal/m365/collection/drive/url_cache.go index 1a8cc7899..b672198e9 100644 --- a/src/internal/m365/collection/drive/url_cache.go +++ b/src/internal/m365/collection/drive/url_cache.go @@ -182,7 +182,7 @@ func (uc *urlCache) deltaQuery( ) error { logger.Ctx(ctx).Debug("starting delta query") // Reset item pager to remove any previous state - uc.itemPager.Reset(ctx) + uc.itemPager.Reset() _, _, _, err := collectItems( ctx, diff --git a/src/internal/m365/collection/groups/backup.go b/src/internal/m365/collection/groups/backup.go index 9b31126a1..32b33c66a 100644 --- a/src/internal/m365/collection/groups/backup.go +++ b/src/internal/m365/collection/groups/backup.go @@ -243,7 +243,7 @@ func populateCollections( func collectItems( ctx context.Context, - pager api.ChannelMessageDeltaEnumerator, + pager api.DeltaPager[models.ChatMessageable], ) ([]models.ChatMessageable, error) { items := []models.ChatMessageable{} @@ -265,11 +265,7 @@ func collectItems( // continue // } - vals, err := pager.ValuesIn(page) - if err != nil { - return nil, graph.Wrap(ctx, err, "getting items in page") - } - + vals := page.GetValue() items = append(items, vals...) nextLink, _ := api.NextAndDeltaLink(page) diff --git a/src/internal/m365/collection/groups/handlers.go b/src/internal/m365/collection/groups/handlers.go index bf3cb8f0f..976863944 100644 --- a/src/internal/m365/collection/groups/handlers.go +++ b/src/internal/m365/collection/groups/handlers.go @@ -16,7 +16,7 @@ type BackupHandler interface { ) (models.Channelable, error) NewChannelsPager( teamID string, - ) api.ChannelDeltaEnumerator + ) api.DeltaPager[models.Channelable] GetMessageByID( ctx context.Context, @@ -24,7 +24,7 @@ type BackupHandler interface { ) (models.ChatMessageable, error) NewMessagePager( teamID, channelID string, - ) api.ChannelMessageDeltaEnumerator + ) api.DeltaPager[models.ChatMessageable] GetMessageReplies( ctx context.Context, @@ -34,7 +34,7 @@ type BackupHandler interface { type BackupMessagesHandler interface { GetMessage(ctx context.Context, teamID, channelID, itemID string) (models.ChatMessageable, error) - NewMessagePager(teamID, channelID string) api.ChannelMessageDeltaEnumerator + NewMessagePager(teamID, channelID string) api.DeltaPager[models.ChatMessageable] GetChannel(ctx context.Context, teamID, channelID string) (models.Channelable, error) GetReply(ctx context.Context, teamID, channelID, messageID string) (serialization.Parsable, error) } diff --git a/src/pkg/services/m365/api/contacts_pager.go b/src/pkg/services/m365/api/contacts_pager.go index b253fd35c..63f697657 100644 --- a/src/pkg/services/m365/api/contacts_pager.go +++ b/src/pkg/services/m365/api/contacts_pager.go @@ -90,7 +90,7 @@ func (c Contacts) EnumerateContainers( // item pager // --------------------------------------------------------------------------- -var _ itemPager[models.Contactable] = &contactsPageCtrl{} +var _ Pager[models.Contactable] = &contactsPageCtrl{} type contactsPageCtrl struct { gs graph.Servicer @@ -100,10 +100,11 @@ type contactsPageCtrl struct { func (c Contacts) NewContactsPager( userID, containerID string, + immutableIDs bool, selectProps ...string, -) itemPager[models.Contactable] { +) Pager[models.Contactable] { options := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{ - Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize)), + Headers: newPreferHeaders(preferPageSize(c.options.DeltaPageSize), preferImmutableIDs(immutableIDs)), QueryParameters: &users.ItemContactFoldersItemContactsRequestBuilderGetQueryParameters{}, // do NOT set Top. It limits the total items received. } @@ -129,22 +130,98 @@ func (c Contacts) NewContactsPager( return &contactsPageCtrl{c.Stable, builder, options} } -//lint:ignore U1000 False Positive -func (p *contactsPageCtrl) getPage(ctx context.Context) (PageLinkValuer[models.Contactable], error) { +func (p *contactsPageCtrl) GetPage(ctx context.Context) (LinkValuer[models.Contactable], error) { resp, err := p.builder.Get(ctx, p.options) if err != nil { return nil, graph.Stack(ctx, err) } - return EmptyDeltaLinker[models.Contactable]{PageLinkValuer: resp}, nil + return EmptyDeltaLinker[models.Contactable]{LinkValuer: resp}, nil } -//lint:ignore U1000 False Positive -func (p *contactsPageCtrl) setNext(nextLink string) { +func (p *contactsPageCtrl) SetNext(nextLink string) { p.builder = users.NewItemContactFoldersItemContactsRequestBuilder(nextLink, p.gs.Adapter()) } -//lint:ignore U1000 False Positive +// --------------------------------------------------------------------------- +// delta pager +// --------------------------------------------------------------------------- + +var _ DeltaPager[models.Contactable] = &contactDeltaPager{} + +type contactDeltaPager struct { + gs graph.Servicer + userID string + containerID string + builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder + options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration +} + +func getContactDeltaBuilder( + ctx context.Context, + gs graph.Servicer, + userID, containerID string, + options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration, +) *users.ItemContactFoldersItemContactsDeltaRequestBuilder { + builder := gs.Client(). + Users(). + ByUserId(userID). + ContactFolders(). + ByContactFolderId(containerID). + Contacts(). + Delta() + + return builder +} + +func (c Contacts) NewContactsDeltaPager( + ctx context.Context, + userID, containerID, oldDelta string, + immutableIDs bool, + selectProps ...string, +) DeltaPager[getIDAndAddtler] { + options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{ + Headers: newPreferHeaders(preferPageSize(c.options.DeltaPageSize), preferImmutableIDs(immutableIDs)), + QueryParameters: &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetQueryParameters{ + // do NOT set Top. It limits the total items received. + }, + } + + if len(selectProps) > 0 { + options.QueryParameters.Select = selectProps + } + + var builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder + if oldDelta != "" { + builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, c.Stable.Adapter()) + } else { + builder = getContactDeltaBuilder(ctx, c.Stable, userID, containerID, options) + } + + return &contactDeltaPager{c.Stable, userID, containerID, builder, options} +} + +func (p *contactDeltaPager) GetPage(ctx context.Context) (DeltaLinkValuer[models.Contactable], error) { + resp, err := p.builder.Get(ctx, p.options) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + return resp, nil +} + +func (p *contactDeltaPager) SetNext(nextLink string) { + p.builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(nextLink, p.gs.Adapter()) +} + +func (p *contactDeltaPager) Reset(ctx context.Context) { + p.builder = getContactDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options) +} + +// --------------------------------------------------------------------------- +// runners +// --------------------------------------------------------------------------- + func (c Contacts) GetItemsInContainerByCollisionKey( ctx context.Context, userID, containerID string, @@ -171,7 +248,7 @@ func (c Contacts) GetItemIDsInContainer( userID, containerID string, ) (map[string]struct{}, error) { ctx = clues.Add(ctx, "container_id", containerID) - pager := c.NewContactsPager(userID, containerID, "id") + pager := c.NewContactsPager(userID, containerID, false, "id") items, err := enumerateItems(ctx, pager) if err != nil { @@ -187,130 +264,6 @@ func (c Contacts) GetItemIDsInContainer( return m, nil } -// --------------------------------------------------------------------------- -// item ID pager -// --------------------------------------------------------------------------- - -var _ DeltaPager[getIDAndAddtler] = &contactIDPager{} - -type contactIDPager struct { - gs graph.Servicer - builder *users.ItemContactFoldersItemContactsRequestBuilder - options *users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration -} - -func (c Contacts) NewContactIDsPager( - ctx context.Context, - userID, containerID string, - immutableIDs bool, -) DeltaPager[getIDAndAddtler] { - config := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{ - QueryParameters: &users.ItemContactFoldersItemContactsRequestBuilderGetQueryParameters{ - Select: idAnd(parentFolderID), - // do NOT set Top. It limits the total items received. - }, - Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)), - } - - builder := c.Stable. - Client(). - Users(). - ByUserId(userID). - ContactFolders(). - ByContactFolderId(containerID). - Contacts() - - return &contactIDPager{c.Stable, builder, config} -} - -func (p *contactIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { - resp, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return EmptyDeltaLinker[models.Contactable]{PageLinkValuer: resp}, nil -} - -func (p *contactIDPager) SetNext(nextLink string) { - p.builder = users.NewItemContactFoldersItemContactsRequestBuilder(nextLink, p.gs.Adapter()) -} - -// non delta pagers don't need reset -func (p *contactIDPager) Reset(context.Context) {} - -func (p *contactIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Contactable](pl) -} - -// --------------------------------------------------------------------------- -// delta item ID pager -// --------------------------------------------------------------------------- - -var _ DeltaPager[getIDAndAddtler] = &contactDeltaIDPager{} - -type contactDeltaIDPager struct { - gs graph.Servicer - userID string - containerID string - builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder - options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration -} - -func getContactDeltaBuilder( - ctx context.Context, - gs graph.Servicer, - userID, containerID string, - options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration, -) *users.ItemContactFoldersItemContactsDeltaRequestBuilder { - builder := gs.Client().Users().ByUserId(userID).ContactFolders().ByContactFolderId(containerID).Contacts().Delta() - return builder -} - -func (c Contacts) NewContactDeltaIDsPager( - ctx context.Context, - userID, containerID, oldDelta string, - immutableIDs bool, -) DeltaPager[getIDAndAddtler] { - options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{ - QueryParameters: &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetQueryParameters{ - Select: idAnd(parentFolderID), - // do NOT set Top. It limits the total items received. - }, - Headers: newPreferHeaders(preferPageSize(c.options.DeltaPageSize), preferImmutableIDs(immutableIDs)), - } - - var builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder - if oldDelta != "" { - builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, c.Stable.Adapter()) - } else { - builder = getContactDeltaBuilder(ctx, c.Stable, userID, containerID, options) - } - - return &contactDeltaIDPager{c.Stable, userID, containerID, builder, options} -} - -func (p *contactDeltaIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { - resp, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return resp, nil -} - -func (p *contactDeltaIDPager) SetNext(nextLink string) { - p.builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(nextLink, p.gs.Adapter()) -} - -func (p *contactDeltaIDPager) Reset(ctx context.Context) { - p.builder = getContactDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options) -} - -func (p *contactDeltaIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Contactable](pl) -} - func (c Contacts) GetAddedAndRemovedItemIDs( ctx context.Context, userID, containerID, oldDelta string, @@ -322,8 +275,14 @@ func (c Contacts) GetAddedAndRemovedItemIDs( "category", selectors.ExchangeContact, "container_id", containerID) - pager := c.NewContactIDsPager(ctx, userID, containerID, immutableIDs) - deltaPager := c.NewContactDeltaIDsPager(ctx, userID, containerID, oldDelta, immutableIDs) + pager := c.NewContactsPager(userID, containerID, immutableIDs, idAnd(parentFolderID)...) + deltaPager := c.NewContactsDeltaPager(ctx, userID, containerID, oldDelta, immutableIDs, idAnd(parentFolderID)...) - return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries) + return getAddedAndRemovedItemIDs[models.Contactable]( + ctx, + c.Stable, + pager, + deltaPager, + oldDelta, + canMakeDeltaQueries) } diff --git a/src/pkg/services/m365/api/contacts_pager_test.go b/src/pkg/services/m365/api/contacts_pager_test.go index 5f3561e6d..5d859cd12 100644 --- a/src/pkg/services/m365/api/contacts_pager_test.go +++ b/src/pkg/services/m365/api/contacts_pager_test.go @@ -39,13 +39,13 @@ func (suite *ContactsPagerIntgSuite) TestContacts_GetItemsInContainerByCollision ctx, flush := tester.NewContext(t) defer flush() - container, err := ac.GetContainerByID(ctx, suite.its.userID, "contacts") + container, err := ac.GetContainerByID(ctx, suite.its.user.id, "contacts") require.NoError(t, err, clues.ToCore(err)) conts, err := ac.Stable. Client(). Users(). - ByUserId(suite.its.userID). + ByUserId(suite.its.user.id). ContactFolders(). ByContactFolderId(ptr.Val(container.GetId())). Contacts(). @@ -61,7 +61,7 @@ func (suite *ContactsPagerIntgSuite) TestContacts_GetItemsInContainerByCollision expect := maps.Keys(expectM) - results, err := suite.its.ac.Contacts().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "contacts") + results, err := suite.its.ac.Contacts().GetItemsInContainerByCollisionKey(ctx, suite.its.user.id, "contacts") require.NoError(t, err, clues.ToCore(err)) require.Less(t, 0, len(results), "requires at least one result") @@ -91,13 +91,13 @@ func (suite *ContactsPagerIntgSuite) TestContacts_GetItemsIDsInContainer() { ctx, flush := tester.NewContext(t) defer flush() - container, err := ac.GetContainerByID(ctx, suite.its.userID, api.DefaultContacts) + container, err := ac.GetContainerByID(ctx, suite.its.user.id, api.DefaultContacts) require.NoError(t, err, clues.ToCore(err)) msgs, err := ac.Stable. Client(). Users(). - ByUserId(suite.its.userID). + ByUserId(suite.its.user.id). ContactFolders(). ByContactFolderId(ptr.Val(container.GetId())). Contacts(). @@ -112,7 +112,7 @@ func (suite *ContactsPagerIntgSuite) TestContacts_GetItemsIDsInContainer() { } results, err := suite.its.ac.Contacts(). - GetItemIDsInContainer(ctx, suite.its.userID, api.DefaultContacts) + GetItemIDsInContainer(ctx, suite.its.user.id, api.DefaultContacts) require.NoError(t, err, clues.ToCore(err)) require.Less(t, 0, len(results), "requires at least one result") require.Equal(t, len(expect), len(results), "must have same count of items") diff --git a/src/pkg/services/m365/api/contacts_test.go b/src/pkg/services/m365/api/contacts_test.go index afc344cc5..55f059d04 100644 --- a/src/pkg/services/m365/api/contacts_test.go +++ b/src/pkg/services/m365/api/contacts_test.go @@ -141,7 +141,7 @@ func (suite *ContactsAPIIntgSuite) TestContacts_GetContainerByName() { cc, err := suite.its.ac.Contacts().CreateContainer( ctx, - suite.its.userID, + suite.its.user.id, "", rc.Location) require.NoError(t, err, clues.ToCore(err)) @@ -168,7 +168,7 @@ func (suite *ContactsAPIIntgSuite) TestContacts_GetContainerByName() { _, err := suite.its.ac. Contacts(). - GetContainerByName(ctx, suite.its.userID, "", test.name) + GetContainerByName(ctx, suite.its.user.id, "", test.name) test.expectErr(t, err, clues.ToCore(err)) }) } diff --git a/src/pkg/services/m365/api/drive_pager.go b/src/pkg/services/m365/api/drive_pager.go index 1aa485200..b9a48f27b 100644 --- a/src/pkg/services/m365/api/drive_pager.go +++ b/src/pkg/services/m365/api/drive_pager.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "time" "github.com/alcionai/clues" @@ -21,7 +20,7 @@ import ( // non-delta item pager // --------------------------------------------------------------------------- -var _ itemPager[models.DriveItemable] = &driveItemPageCtrl{} +var _ Pager[models.DriveItemable] = &driveItemPageCtrl{} type driveItemPageCtrl struct { gs graph.Servicer @@ -32,7 +31,7 @@ type driveItemPageCtrl struct { func (c Drives) NewDriveItemPager( driveID, containerID string, selectProps ...string, -) itemPager[models.DriveItemable] { +) Pager[models.DriveItemable] { options := &drives.ItemItemsItemChildrenRequestBuilderGetRequestConfiguration{ QueryParameters: &drives.ItemItemsItemChildrenRequestBuilderGetQueryParameters{}, } @@ -53,17 +52,16 @@ func (c Drives) NewDriveItemPager( } //lint:ignore U1000 False Positive -func (p *driveItemPageCtrl) getPage(ctx context.Context) (PageLinkValuer[models.DriveItemable], error) { +func (p *driveItemPageCtrl) GetPage(ctx context.Context) (LinkValuer[models.DriveItemable], error) { page, err := p.builder.Get(ctx, p.options) if err != nil { return nil, graph.Stack(ctx, err) } - return EmptyDeltaLinker[models.DriveItemable]{PageLinkValuer: page}, nil + return EmptyDeltaLinker[models.DriveItemable]{LinkValuer: page}, nil } -//lint:ignore U1000 False Positive -func (p *driveItemPageCtrl) setNext(nextLink string) { +func (p *driveItemPageCtrl) SetNext(nextLink string) { p.builder = drives.NewItemItemsItemChildrenRequestBuilder(nextLink, p.gs.Adapter()) } @@ -105,7 +103,7 @@ func (c Drives) GetItemIDsInContainer( items, err := enumerateItems(ctx, pager) if err != nil { - return nil, graph.Wrap(ctx, err, "enumerating contacts") + return nil, graph.Wrap(ctx, err, "enumerating drive items") } m := map[string]DriveItemIDType{} @@ -171,25 +169,16 @@ func (c Drives) NewDriveItemDeltaPager( return res } -func (p *DriveItemDeltaPageCtrl) GetPage(ctx context.Context) (DeltaPageLinker, error) { - var ( - resp DeltaPageLinker - err error - ) - - resp, err = p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return resp, nil +func (p *DriveItemDeltaPageCtrl) GetPage(ctx context.Context) (DeltaLinkValuer[models.DriveItemable], error) { + resp, err := p.builder.Get(ctx, p.options) + return resp, graph.Stack(ctx, err).OrNil() } func (p *DriveItemDeltaPageCtrl) SetNext(link string) { p.builder = drives.NewItemItemsItemDeltaRequestBuilder(link, p.gs.Adapter()) } -func (p *DriveItemDeltaPageCtrl) Reset(context.Context) { +func (p *DriveItemDeltaPageCtrl) Reset() { p.builder = p.gs.Client(). Drives(). ByDriveId(p.driveID). @@ -198,10 +187,6 @@ func (p *DriveItemDeltaPageCtrl) Reset(context.Context) { Delta() } -func (p *DriveItemDeltaPageCtrl) ValuesIn(l PageLinker) ([]models.DriveItemable, error) { - return getValues[models.DriveItemable](l) -} - // --------------------------------------------------------------------------- // user's drives pager // --------------------------------------------------------------------------- @@ -245,12 +230,11 @@ type nopUserDrivePageLinker struct { func (nl nopUserDrivePageLinker) GetOdataNextLink() *string { return nil } -func (p *userDrivePager) GetPage(ctx context.Context) (PageLinker, error) { - var ( - resp PageLinker - err error - ) +func (nl nopUserDrivePageLinker) GetValue() []models.Driveable { + return []models.Driveable{nl.drive} +} +func (p *userDrivePager) GetPage(ctx context.Context) (LinkValuer[models.Driveable], error) { d, err := p.gs. Client(). Users(). @@ -261,7 +245,7 @@ func (p *userDrivePager) GetPage(ctx context.Context) (PageLinker, error) { return nil, graph.Stack(ctx, err) } - resp = &nopUserDrivePageLinker{drive: d} + var resp LinkValuer[models.Driveable] = &nopUserDrivePageLinker{drive: d} // TODO(keepers): turn back on when we can separate drive enumeration // from default drive lookup. @@ -278,20 +262,6 @@ func (p *userDrivePager) SetNext(link string) { p.builder = users.NewItemDrivesRequestBuilder(link, p.gs.Adapter()) } -func (p *userDrivePager) ValuesIn(l PageLinker) ([]models.Driveable, error) { - nl, ok := l.(*nopUserDrivePageLinker) - if !ok || nl == nil { - return nil, clues.New(fmt.Sprintf("improper page linker struct for user drives: %T", l)) - } - - // TODO(keepers): turn back on when we can separate drive enumeration - // from default drive lookup. - - // return getValues[models.Driveable](l) - - return []models.Driveable{nl.drive}, nil -} - // --------------------------------------------------------------------------- // site's libraries pager // --------------------------------------------------------------------------- @@ -332,28 +302,15 @@ func (c Drives) NewSiteDrivePager( return res } -func (p *siteDrivePager) GetPage(ctx context.Context) (PageLinker, error) { - var ( - resp PageLinker - err error - ) - - resp, err = p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return resp, nil +func (p *siteDrivePager) GetPage(ctx context.Context) (LinkValuer[models.Driveable], error) { + resp, err := p.builder.Get(ctx, p.options) + return resp, graph.Stack(ctx, err).OrNil() } func (p *siteDrivePager) SetNext(link string) { p.builder = sites.NewItemDrivesRequestBuilder(link, p.gs.Adapter()) } -func (p *siteDrivePager) ValuesIn(l PageLinker) ([]models.Driveable, error) { - return getValues[models.Driveable](l) -} - // --------------------------------------------------------------------------- // drive pager // --------------------------------------------------------------------------- @@ -374,8 +331,8 @@ func GetAllDrives( // Loop through all pages returned by Graph API. for { var ( + page LinkValuer[models.Driveable] err error - page PageLinker ) // Retry Loop for Drive retrieval. Request can timeout @@ -400,12 +357,8 @@ func GetAllDrives( break } - tmp, err := pager.ValuesIn(page) - if err != nil { - return nil, graph.Wrap(ctx, err, "extracting drives from response") - } - - ds = append(ds, tmp...) + items := page.GetValue() + ds = append(ds, items...) nextLink := ptr.Val(page.GetOdataNextLink()) if len(nextLink) == 0 { @@ -419,17 +372,3 @@ func GetAllDrives( return ds, nil } - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -func getValues[T any](l PageLinker) ([]T, error) { - page, ok := l.(interface{ GetValue() []T }) - if !ok { - return nil, clues.New("page does not comply with GetValue() interface"). - With("page_item_type", fmt.Sprintf("%T", l)) - } - - return page.GetValue(), nil -} diff --git a/src/pkg/services/m365/api/drive_pager_test.go b/src/pkg/services/m365/api/drive_pager_test.go index 71177e2c8..f28277eee 100644 --- a/src/pkg/services/m365/api/drive_pager_test.go +++ b/src/pkg/services/m365/api/drive_pager_test.go @@ -39,13 +39,13 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemsInContainerByCollisionKey() }{ { name: "user drive", - driveID: suite.its.userDriveID, - rootFolderID: suite.its.userDriveRootFolderID, + driveID: suite.its.user.driveID, + rootFolderID: suite.its.user.driveRootFolderID, }, { name: "site drive", - driveID: suite.its.siteDriveID, - rootFolderID: suite.its.siteDriveRootFolderID, + driveID: suite.its.site.driveID, + rootFolderID: suite.its.site.driveRootFolderID, }, } for _, test := range table { @@ -75,7 +75,7 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemsInContainerByCollisionKey() t, ims, "need at least one item to compare in user %s drive %s folder %s", - suite.its.userID, test.driveID, test.rootFolderID) + suite.its.user.id, test.driveID, test.rootFolderID) results, err := suite.its.ac. Drives(). @@ -113,13 +113,13 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemIDsInContainer() { }{ { name: "user drive", - driveID: suite.its.userDriveID, - rootFolderID: suite.its.userDriveRootFolderID, + driveID: suite.its.user.driveID, + rootFolderID: suite.its.user.driveRootFolderID, }, { name: "site drive", - driveID: suite.its.siteDriveID, - rootFolderID: suite.its.siteDriveRootFolderID, + driveID: suite.its.site.driveID, + rootFolderID: suite.its.site.driveRootFolderID, }, } for _, test := range table { @@ -149,7 +149,7 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemIDsInContainer() { t, igv, "need at least one item to compare in user %s drive %s folder %s", - suite.its.userID, test.driveID, test.rootFolderID) + suite.its.user.id, test.driveID, test.rootFolderID) for _, itm := range igv { expect[ptr.Val(itm.GetId())] = api.DriveItemIDType{ diff --git a/src/pkg/services/m365/api/drive_test.go b/src/pkg/services/m365/api/drive_test.go index 82a889452..28173c27a 100644 --- a/src/pkg/services/m365/api/drive_test.go +++ b/src/pkg/services/m365/api/drive_test.go @@ -76,8 +76,8 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() { // generate a parent for the test data parent, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, - suite.its.userDriveRootFolderID, + suite.its.user.driveID, + suite.its.user.driveRootFolderID, newItem(rc.Location, true), control.Replace) require.NoError(t, err, clues.ToCore(err)) @@ -86,7 +86,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() { folder := newItem("collision", true) origFolder, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, + suite.its.user.driveID, ptr.Val(parent.GetId()), folder, control.Copy) @@ -96,7 +96,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() { file := newItem("collision.txt", false) origFile, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, + suite.its.user.driveID, ptr.Val(parent.GetId()), file, control.Copy) @@ -211,7 +211,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() { t := suite.T() i, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, + suite.its.user.driveID, ptr.Val(parent.GetId()), test.postItem, test.onCollision) @@ -239,8 +239,8 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer_replaceFolderRegr // generate a folder for the test data folder, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, - suite.its.userDriveRootFolderID, + suite.its.user.driveID, + suite.its.user.driveRootFolderID, newItem(rc.Location, true), // skip instead of replace here to get // an ErrItemAlreadyExistsConflict, just in case. @@ -252,7 +252,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer_replaceFolderRegr file := newItem(fmt.Sprintf("collision_%d.txt", i), false) f, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, + suite.its.user.driveID, ptr.Val(folder.GetId()), file, control.Copy) @@ -263,7 +263,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer_replaceFolderRegr resultFolder, err := acd.PostItemInContainer( ctx, - suite.its.userDriveID, + suite.its.user.driveID, ptr.Val(folder.GetParentReference().GetId()), newItem(rc.Location, true), control.Replace) @@ -274,7 +274,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer_replaceFolderRegr resultFileColl, err := acd.Stable. Client(). Drives(). - ByDriveId(suite.its.userDriveID). + ByDriveId(suite.its.user.driveID). Items(). ByDriveItemId(ptr.Val(resultFolder.GetId())). Children(). diff --git a/src/pkg/services/m365/api/events_pager.go b/src/pkg/services/m365/api/events_pager.go index 55e227d58..5d4a96d1b 100644 --- a/src/pkg/services/m365/api/events_pager.go +++ b/src/pkg/services/m365/api/events_pager.go @@ -98,7 +98,7 @@ func (c Events) EnumerateContainers( // item pager // --------------------------------------------------------------------------- -var _ itemPager[models.Eventable] = &eventsPageCtrl{} +var _ Pager[models.Eventable] = &eventsPageCtrl{} type eventsPageCtrl struct { gs graph.Servicer @@ -109,7 +109,7 @@ type eventsPageCtrl struct { func (c Events) NewEventsPager( userID, containerID string, selectProps ...string, -) itemPager[models.Eventable] { +) Pager[models.Eventable] { options := &users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration{ Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize)), QueryParameters: &users.ItemCalendarsItemEventsRequestBuilderGetQueryParameters{ @@ -133,13 +133,11 @@ func (c Events) NewEventsPager( } //lint:ignore U1000 False Positive -func (p *eventsPageCtrl) getPage(ctx context.Context) (PageLinkValuer[models.Eventable], error) { +func (p *eventsPageCtrl) getPage( + ctx context.Context, +) (LinkValuer[models.Eventable], error) { resp, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return resp, nil + return resp, graph.Stack(ctx, err).OrNil() } //lint:ignore U1000 False Positive @@ -204,13 +202,11 @@ func (c Events) NewEventIDsPager( return &eventIDPager{c.Stable, builder, options}, nil } -func (p *eventIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { +func (p *eventIDPager) GetPage( + ctx context.Context, +) (LinkValuer[models.Eventable], error) { resp, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return EmptyDeltaLinker[models.Eventable]{PageLinkValuer: resp}, nil + return EmptyDeltaLinker[models.Eventable]{LinkValuer: resp}, graph.Stack(ctx, err).OrNil() } func (p *eventIDPager) SetNext(nextLink string) { @@ -218,11 +214,7 @@ func (p *eventIDPager) SetNext(nextLink string) { } // non delta pagers don't need reset -func (p *eventIDPager) Reset(context.Context) {} - -func (p *eventIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Eventable](pl) -} +func (p *eventIDPager) Reset() {} // --------------------------------------------------------------------------- // delta item ID pager @@ -253,7 +245,7 @@ func (c Events) NewEventDeltaIDsPager( var builder *users.ItemCalendarsItemEventsDeltaRequestBuilder if oldDelta == "" { - builder = getEventDeltaBuilder(ctx, c.Stable, userID, containerID, options) + builder = getEventDeltaBuilder(c.Stable, userID, containerID, options) } else { builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, c.Stable.Adapter()) } @@ -262,7 +254,6 @@ func (c Events) NewEventDeltaIDsPager( } func getEventDeltaBuilder( - ctx context.Context, gs graph.Servicer, userID, containerID string, options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration, @@ -281,25 +272,19 @@ func getEventDeltaBuilder( return builder } -func (p *eventDeltaIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { +func (p *eventDeltaIDPager) GetPage( + ctx context.Context, +) (DeltaLinkValuer[models.Eventable], error) { resp, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return resp, nil + return resp, graph.Stack(ctx, err).OrNil() } func (p *eventDeltaIDPager) SetNext(nextLink string) { p.builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(nextLink, p.gs.Adapter()) } -func (p *eventDeltaIDPager) Reset(ctx context.Context) { - p.builder = getEventDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options) -} - -func (p *eventDeltaIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Eventable](pl) +func (p *eventDeltaIDPager) Reset() { + p.builder = getEventDeltaBuilder(p.gs, p.userID, p.containerID, p.options) } func (c Events) GetAddedAndRemovedItemIDs( diff --git a/src/pkg/services/m365/api/events_pager_test.go b/src/pkg/services/m365/api/events_pager_test.go index 04ba45da9..610b449af 100644 --- a/src/pkg/services/m365/api/events_pager_test.go +++ b/src/pkg/services/m365/api/events_pager_test.go @@ -39,13 +39,13 @@ func (suite *EventsPagerIntgSuite) TestEvents_GetItemsInContainerByCollisionKey( ctx, flush := tester.NewContext(t) defer flush() - container, err := ac.GetContainerByID(ctx, suite.its.userID, "calendar") + container, err := ac.GetContainerByID(ctx, suite.its.user.id, "calendar") require.NoError(t, err, clues.ToCore(err)) evts, err := ac.Stable. Client(). Users(). - ByUserId(suite.its.userID). + ByUserId(suite.its.user.id). Calendars(). ByCalendarId(ptr.Val(container.GetId())). Events(). @@ -63,7 +63,7 @@ func (suite *EventsPagerIntgSuite) TestEvents_GetItemsInContainerByCollisionKey( results, err := suite.its.ac. Events(). - GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "calendar") + GetItemsInContainerByCollisionKey(ctx, suite.its.user.id, "calendar") require.NoError(t, err, clues.ToCore(err)) require.Less(t, 0, len(results), "requires at least one result") diff --git a/src/pkg/services/m365/api/events_test.go b/src/pkg/services/m365/api/events_test.go index cf7d9873f..5b5a2d0db 100644 --- a/src/pkg/services/m365/api/events_test.go +++ b/src/pkg/services/m365/api/events_test.go @@ -289,7 +289,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_canFindNonStandardFolder() { ac := suite.its.ac.Events() rc := testdata.DefaultRestoreConfig("api_calendar_discovery") - cal, err := ac.CreateContainer(ctx, suite.its.userID, "", rc.Location) + cal, err := ac.CreateContainer(ctx, suite.its.user.id, "", rc.Location) require.NoError(t, err, clues.ToCore(err)) var ( @@ -306,7 +306,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_canFindNonStandardFolder() { err = ac.EnumerateContainers( ctx, - suite.its.userID, + suite.its.user.id, "Calendar", findContainer, fault.New(true)) @@ -342,7 +342,7 @@ func (suite *EventsAPIIntgSuite) TestEvents_GetContainerByName() { _, err := suite.its.ac. Events(). - GetContainerByName(ctx, suite.its.userID, "", test.name) + GetContainerByName(ctx, suite.its.user.id, "", test.name) test.expectErr(t, err, clues.ToCore(err)) }) } diff --git a/src/pkg/services/m365/api/groups_test.go b/src/pkg/services/m365/api/groups_test.go index 6a0434196..c57640070 100644 --- a/src/pkg/services/m365/api/groups_test.go +++ b/src/pkg/services/m365/api/groups_test.go @@ -112,7 +112,7 @@ func (suite *GroupsIntgSuite) TestGetAll() { func (suite *GroupsIntgSuite) TestGroups_GetByID() { var ( - groupID = suite.its.groupID + groupID = suite.its.group.id groupsAPI = suite.its.ac.Groups() ) diff --git a/src/pkg/services/m365/api/helper_test.go b/src/pkg/services/m365/api/helper_test.go index 8e8c760c0..76adb3891 100644 --- a/src/pkg/services/m365/api/helper_test.go +++ b/src/pkg/services/m365/api/helper_test.go @@ -74,16 +74,19 @@ func parseableToMap(t *testing.T, thing serialization.Parsable) map[string]any { // Suite Setup // --------------------------------------------------------------------------- +type ids struct { + id string + driveID string + driveRootFolderID string + testContainerID string +} + type intgTesterSetup struct { - ac api.Client - gockAC api.Client - userID string - userDriveID string - userDriveRootFolderID string - siteID string - siteDriveID string - siteDriveRootFolderID string - groupID string + ac api.Client + gockAC api.Client + user ids + site ids + group ids } func newIntegrationTesterSetup(t *testing.T) intgTesterSetup { @@ -106,42 +109,47 @@ func newIntegrationTesterSetup(t *testing.T) intgTesterSetup { // user drive - its.userID = tconfig.M365UserID(t) + its.user.id = tconfig.M365UserID(t) - userDrive, err := its.ac.Users().GetDefaultDrive(ctx, its.userID) + userDrive, err := its.ac.Users().GetDefaultDrive(ctx, its.user.id) require.NoError(t, err, clues.ToCore(err)) - its.userDriveID = ptr.Val(userDrive.GetId()) + its.user.driveID = ptr.Val(userDrive.GetId()) - userDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, its.userDriveID) + userDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, its.user.driveID) require.NoError(t, err, clues.ToCore(err)) - its.userDriveRootFolderID = ptr.Val(userDriveRootFolder.GetId()) - - its.siteID = tconfig.M365SiteID(t) + its.user.driveRootFolderID = ptr.Val(userDriveRootFolder.GetId()) // site - siteDrive, err := its.ac.Sites().GetDefaultDrive(ctx, its.siteID) + its.site.id = tconfig.M365SiteID(t) + + siteDrive, err := its.ac.Sites().GetDefaultDrive(ctx, its.site.id) require.NoError(t, err, clues.ToCore(err)) - its.siteDriveID = ptr.Val(siteDrive.GetId()) + its.site.driveID = ptr.Val(siteDrive.GetId()) - siteDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, its.siteDriveID) + siteDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, its.site.driveID) require.NoError(t, err, clues.ToCore(err)) - its.siteDriveRootFolderID = ptr.Val(siteDriveRootFolder.GetId()) + its.site.driveRootFolderID = ptr.Val(siteDriveRootFolder.GetId()) - // group + // groups/teams // use of the TeamID is intentional here, so that we are assured // the group has full usage of the teams api. - its.groupID = tconfig.M365TeamID(t) + its.group.id = tconfig.M365TeamID(t) - team, err := its.ac.Groups().GetByID(ctx, its.groupID) + channel, err := its.ac.Channels(). + GetChannelByName( + ctx, + its.group.id, + "Test") require.NoError(t, err, clues.ToCore(err)) + require.Equal(t, "Test", ptr.Val(channel.GetDisplayName())) - its.groupID = ptr.Val(team.GetId()) + its.group.testContainerID = ptr.Val(channel.GetId()) return its } diff --git a/src/pkg/services/m365/api/item_pager.go b/src/pkg/services/m365/api/item_pager.go index 285fb0e67..52bb9457b 100644 --- a/src/pkg/services/m365/api/item_pager.go +++ b/src/pkg/services/m365/api/item_pager.go @@ -17,46 +17,45 @@ import ( // --------------------------------------------------------------------------- type DeltaPager[T any] interface { - DeltaGetPager + DeltaGetPager[T] Resetter SetNextLinker - ValuesInPageLinker[T] } type Pager[T any] interface { - GetPager + GetPager[T] SetNextLinker - ValuesInPageLinker[T] } -type DeltaGetPager interface { - GetPage(context.Context) (DeltaPageLinker, error) +type DeltaGetPager[T any] interface { + GetPage(context.Context) (DeltaLinkValuer[T], error) } -type GetPager interface { - GetPage(context.Context) (PageLinker, error) +type GetPager[T any] interface { + GetPage(context.Context) (LinkValuer[T], error) } type Valuer[T any] interface { GetValue() []T } -type ValuesInPageLinker[T any] interface { - ValuesIn(PageLinker) ([]T, error) -} - -type PageLinker interface { +type GetNextLinker interface { GetOdataNextLink() *string } -type DeltaPageLinker interface { - PageLinker +type GetDeltaLinker interface { + GetNextLinker GetOdataDeltaLink() *string } -type PageLinkValuer[T any] interface { - PageLinker +type LinkValuer[T any] interface { Valuer[T] + GetNextLinker +} + +type DeltaLinkValuer[T any] interface { + LinkValuer[T] + GetDeltaLinker } type SetNextLinker interface { @@ -64,7 +63,7 @@ type SetNextLinker interface { } type Resetter interface { - Reset(context.Context) + Reset() } // --------------------------------------------------------------------------- @@ -76,17 +75,17 @@ func IsNextLinkValid(next string) bool { return !strings.Contains(next, `users//`) } -func NextLink(pl PageLinker) string { - return ptr.Val(pl.GetOdataNextLink()) +func NextLink(gnl GetNextLinker) string { + return ptr.Val(gnl.GetOdataNextLink()) } -func NextAndDeltaLink(pl DeltaPageLinker) (string, string) { - return NextLink(pl), ptr.Val(pl.GetOdataDeltaLink()) +func NextAndDeltaLink(gdl GetDeltaLinker) (string, string) { + return NextLink(gdl), ptr.Val(gdl.GetOdataDeltaLink()) } // EmptyDeltaLinker is used to convert PageLinker to DeltaPageLinker type EmptyDeltaLinker[T any] struct { - PageLinkValuer[T] + LinkValuer[T] } func (EmptyDeltaLinker[T]) GetOdataDeltaLink() *string { @@ -94,23 +93,16 @@ func (EmptyDeltaLinker[T]) GetOdataDeltaLink() *string { } func (e EmptyDeltaLinker[T]) GetValue() []T { - return e.PageLinkValuer.GetValue() + return e.GetValue() } // --------------------------------------------------------------------------- // generic handler for non-delta item paging in a container // --------------------------------------------------------------------------- -type itemPager[T any] interface { - // getPage get a page with the specified options from graph - getPage(context.Context) (PageLinkValuer[T], error) - // setNext is used to pass in the next url got from graph - setNext(string) -} - func enumerateItems[T any]( ctx context.Context, - pager itemPager[T], + pager Pager[T], ) ([]T, error) { var ( result = make([]T, 0) @@ -120,7 +112,7 @@ func enumerateItems[T any]( for len(nextLink) > 0 { // get the next page of data, check for standard errors - resp, err := pager.getPage(ctx) + resp, err := pager.GetPage(ctx) if err != nil { return nil, graph.Stack(ctx, err) } @@ -128,7 +120,7 @@ func enumerateItems[T any]( result = append(result, resp.GetValue()...) nextLink = NextLink(resp) - pager.setNext(nextLink) + pager.SetNext(nextLink) } logger.Ctx(ctx).Infow("completed enumeration", "count", len(result)) @@ -137,9 +129,37 @@ func enumerateItems[T any]( } // --------------------------------------------------------------------------- -// generic handler for delta-based ittem paging in a container +// generic handler for delta-based item paging in a container // --------------------------------------------------------------------------- +func enumerateDeltaItems[T any]( + ctx context.Context, + pager DeltaPager[T], +) ([]T, error) { + var ( + result = make([]T, 0) + // stubbed initial value to ensure we enter the loop. + nextLink = "do-while" + ) + + for len(nextLink) > 0 { + // get the next page of data, check for standard errors + resp, err := pager.GetPage(ctx) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + result = append(result, resp.GetValue()...) + nextLink = NextLink(resp) + + pager.SetNext(nextLink) + } + + logger.Ctx(ctx).Infow("completed enumeration", "count", len(result)) + + return result, nil +} + // uses a models interface compliant with { GetValues() []T } // to transform its results into a slice of getIDer interfaces. // Generics used here to handle the variation of msoft interfaces @@ -173,16 +193,16 @@ type getIDAndAddtler interface { GetAdditionalData() map[string]any } -func getAddedAndRemovedItemIDs( +func getAddedAndRemovedItemIDs[T any]( ctx context.Context, service graph.Servicer, - pager DeltaPager[getIDAndAddtler], - deltaPager DeltaPager[getIDAndAddtler], + pager DeltaPager[T], + deltaPager DeltaPager[T], oldDelta string, canMakeDeltaQueries bool, ) ([]string, []string, DeltaUpdate, error) { var ( - pgr DeltaPager[getIDAndAddtler] + pgr DeltaPager[T] resetDelta bool ) @@ -211,7 +231,7 @@ func getAddedAndRemovedItemIDs( } // reset deltaPager - pgr.Reset(ctx) + pgr.Reset() added, removed, deltaURL, err = getItemsAddedAndRemovedFromContainer(ctx, pgr) if err != nil { @@ -222,9 +242,9 @@ func getAddedAndRemovedItemIDs( } // generic controller for retrieving all item ids in a container. -func getItemsAddedAndRemovedFromContainer( +func getItemsAddedAndRemovedFromContainer[T any]( ctx context.Context, - pager DeltaPager[getIDAndAddtler], + pager DeltaPager[T], ) ([]string, []string, string, error) { var ( addedIDs = []string{} @@ -243,10 +263,7 @@ func getItemsAddedAndRemovedFromContainer( // each category type responds with a different interface, but all // of them comply with GetValue, which is where we'll get our item data. - items, err := pager.ValuesIn(resp) - if err != nil { - return nil, nil, "", graph.Stack(ctx, err) - } + items := resp.GetValue() itemCount += len(items) page++ diff --git a/src/pkg/services/m365/api/item_pager_test.go b/src/pkg/services/m365/api/item_pager_test.go index 4b86b1600..d4a22c434 100644 --- a/src/pkg/services/m365/api/item_pager_test.go +++ b/src/pkg/services/m365/api/item_pager_test.go @@ -57,41 +57,39 @@ func (v testPagerValue) GetAdditionalData() map[string]any { // mock page -type testPage struct { - values []any +type testPage[T any] struct { + values []T } -func (p testPage) GetOdataNextLink() *string { +func (p testPage[T]) GetOdataNextLink() *string { // no next, just one page return ptr.To("") } -func (p testPage) GetOdataDeltaLink() *string { +func (p testPage[T]) GetOdataDeltaLink() *string { // delta is not tested here return ptr.To("") } -func (p testPage) GetValue() []any { +func (p testPage[T]) GetValue() []T { return p.values } // mock item pager -var _ itemPager[any] = &testPager{} +var _ Pager[any] = &testPager{} type testPager struct { t *testing.T - pager testPage + pager testPage[any] pageErr error } -//lint:ignore U1000 False Positive -func (p *testPager) getPage(ctx context.Context) (PageLinkValuer[any], error) { +func (p *testPager) GetPage(ctx context.Context) (LinkValuer[any], error) { return p.pager, p.pageErr } -//lint:ignore U1000 False Positive -func (p *testPager) setNext(nextLink string) {} +func (p *testPager) SetNext(nextLink string) {} // mock id pager @@ -105,7 +103,7 @@ type testIDsPager struct { needsReset bool } -func (p *testIDsPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { +func (p *testIDsPager) GetPage(ctx context.Context) (DeltaLinkValuer[getIDAndAddtler], error) { if p.errorCode != "" { ierr := odataerrors.NewMainError() ierr.SetCode(&p.errorCode) @@ -116,10 +114,10 @@ func (p *testIDsPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { return nil, err } - return testPage{}, nil + return testPage[getIDAndAddtler]{}, nil } func (p *testIDsPager) SetNext(string) {} -func (p *testIDsPager) Reset(context.Context) { +func (p *testIDsPager) Reset() { if !p.needsReset { require.Fail(p.t, "reset should not be called") } @@ -128,7 +126,7 @@ func (p *testIDsPager) Reset(context.Context) { p.errorCode = "" } -func (p *testIDsPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { +func (p *testIDsPager) ValuesIn(pl LinkValuer[getIDAndAddtler]) ([]getIDAndAddtler, error) { items := []getIDAndAddtler{} for _, id := range p.added { @@ -157,7 +155,7 @@ func TestItemPagerUnitSuite(t *testing.T) { func (suite *ItemPagerUnitSuite) TestEnumerateItems() { tests := []struct { name string - getPager func(*testing.T, context.Context) itemPager[any] + getPager func(*testing.T, context.Context) Pager[any] expect []any expectErr require.ErrorAssertionFunc }{ @@ -166,10 +164,10 @@ func (suite *ItemPagerUnitSuite) TestEnumerateItems() { getPager: func( t *testing.T, ctx context.Context, - ) itemPager[any] { + ) Pager[any] { return &testPager{ t: t, - pager: testPage{[]any{"foo", "bar"}}, + pager: testPage[any]{[]any{"foo", "bar"}}, } }, expect: []any{"foo", "bar"}, @@ -180,7 +178,7 @@ func (suite *ItemPagerUnitSuite) TestEnumerateItems() { getPager: func( t *testing.T, ctx context.Context, - ) itemPager[any] { + ) Pager[any] { return &testPager{ t: t, pageErr: assert.AnError, diff --git a/src/pkg/services/m365/api/lists_test.go b/src/pkg/services/m365/api/lists_test.go index 5864427f2..7250eef67 100644 --- a/src/pkg/services/m365/api/lists_test.go +++ b/src/pkg/services/m365/api/lists_test.go @@ -41,7 +41,7 @@ func (suite *ListsAPIIntgSuite) TestLists_PostDrive() { var ( acl = suite.its.ac.Lists() driveName = testdata.DefaultRestoreConfig("list_api_post_drive").Location - siteID = suite.its.siteID + siteID = suite.its.site.id ) // first post, should have no errors diff --git a/src/pkg/services/m365/api/mail_pager.go b/src/pkg/services/m365/api/mail_pager.go index 634a89095..277b2ea5d 100644 --- a/src/pkg/services/m365/api/mail_pager.go +++ b/src/pkg/services/m365/api/mail_pager.go @@ -31,7 +31,7 @@ func (c Mail) NewMailFolderPager(userID string) mailFolderPager { return mailFolderPager{c.Stable, builder} } -func (p *mailFolderPager) getPage(ctx context.Context) (PageLinker, error) { +func (p *mailFolderPager) getPage(ctx context.Context) (LinkValuer[models.MailFolderable], error) { page, err := p.builder.Get(ctx, nil) if err != nil { return nil, graph.Stack(ctx, err) @@ -44,7 +44,7 @@ func (p *mailFolderPager) setNext(nextLink string) { p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.service.Adapter()) } -func (p *mailFolderPager) valuesIn(pl PageLinker) ([]models.MailFolderable, error) { +func (p *mailFolderPager) valuesIn(pl LinkValuer[models.MailFolderable]) ([]models.MailFolderable, error) { // Ideally this should be `users.ItemMailFoldersResponseable`, but // that is not a thing as stable returns different result page, ok := pl.(models.MailFolderCollectionResponseable) @@ -79,10 +79,7 @@ func (c Mail) EnumerateContainers( return graph.Stack(ctx, err) } - resp, err := pgr.valuesIn(page) - if err != nil { - return graph.Stack(ctx, err) - } + resp := page.GetValue() for _, fold := range resp { if el.Failure() != nil { @@ -121,7 +118,7 @@ func (c Mail) EnumerateContainers( // item pager // --------------------------------------------------------------------------- -var _ itemPager[models.Messageable] = &mailPageCtrl{} +var _ Pager[models.Messageable] = &mailPageCtrl{} type mailPageCtrl struct { gs graph.Servicer @@ -132,7 +129,7 @@ type mailPageCtrl struct { func (c Mail) NewMailPager( userID, containerID string, selectProps ...string, -) itemPager[models.Messageable] { +) Pager[models.Messageable] { options := &users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration{ Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize)), QueryParameters: &users.ItemMailFoldersItemMessagesRequestBuilderGetQueryParameters{ @@ -155,14 +152,11 @@ func (c Mail) NewMailPager( return &mailPageCtrl{c.Stable, builder, options} } -//lint:ignore U1000 False Positive -func (p *mailPageCtrl) getPage(ctx context.Context) (PageLinkValuer[models.Messageable], error) { +func (p *mailPageCtrl) GetPage( + ctx context.Context, +) (LinkValuer[models.Messageable], error) { page, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return EmptyDeltaLinker[models.Messageable]{PageLinkValuer: page}, nil + return EmptyDeltaLinker[models.Messageable]{LinkValuer: page}, graph.Stack(ctx, err).OrNil() } //lint:ignore U1000 False Positive @@ -206,13 +200,9 @@ func (c Mail) NewMailIDsPager( return &mailIDPager{c.Stable, builder, config} } -func (p *mailIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { +func (p *mailIDPager) GetPage(ctx context.Context) (DeltaLinkValuer[models.Messageable], error) { page, err := p.builder.Get(ctx, p.options) - if err != nil { - return nil, graph.Stack(ctx, err) - } - - return EmptyDeltaLinker[models.Messageable]{PageLinkValuer: page}, nil + return EmptyDeltaLinker[models.Messageable]{LinkValuer: page}, graph.Stack(ctx, err).OrNil() } func (p *mailIDPager) SetNext(nextLink string) { @@ -220,11 +210,7 @@ func (p *mailIDPager) SetNext(nextLink string) { } // non delta pagers don't have reset -func (p *mailIDPager) Reset(context.Context) {} - -func (p *mailIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Messageable](pl) -} +func (p *mailIDPager) Reset() {} func (c Mail) GetItemsInContainerByCollisionKey( ctx context.Context, @@ -256,7 +242,7 @@ func (c Mail) GetItemIDsInContainer( items, err := enumerateItems(ctx, pager) if err != nil { - return nil, graph.Wrap(ctx, err, "enumerating contacts") + return nil, graph.Wrap(ctx, err, "enumerating mail messages") } m := map[string]struct{}{} @@ -324,7 +310,7 @@ func (c Mail) NewMailDeltaIDsPager( return &mailDeltaIDPager{c.Stable, userID, containerID, builder, config} } -func (p *mailDeltaIDPager) GetPage(ctx context.Context) (DeltaPageLinker, error) { +func (p *mailDeltaIDPager) GetPage(ctx context.Context) (LinkValuer[models.Messageable], error) { page, err := p.builder.Get(ctx, p.options) if err != nil { return nil, graph.Stack(ctx, err) @@ -337,7 +323,7 @@ func (p *mailDeltaIDPager) SetNext(nextLink string) { p.builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(nextLink, p.gs.Adapter()) } -func (p *mailDeltaIDPager) Reset(ctx context.Context) { +func (p *mailDeltaIDPager) Reset() { p.builder = p.gs. Client(). Users(). @@ -348,10 +334,6 @@ func (p *mailDeltaIDPager) Reset(ctx context.Context) { Delta() } -func (p *mailDeltaIDPager) ValuesIn(pl PageLinker) ([]getIDAndAddtler, error) { - return toValues[models.Messageable](pl) -} - func (c Mail) GetAddedAndRemovedItemIDs( ctx context.Context, userID, containerID, oldDelta string, diff --git a/src/pkg/services/m365/api/mail_pager_test.go b/src/pkg/services/m365/api/mail_pager_test.go index d99c428a2..7f367de1d 100644 --- a/src/pkg/services/m365/api/mail_pager_test.go +++ b/src/pkg/services/m365/api/mail_pager_test.go @@ -40,13 +40,13 @@ func (suite *MailPagerIntgSuite) TestMail_GetItemsInContainerByCollisionKey() { ctx, flush := tester.NewContext(t) defer flush() - container, err := ac.GetContainerByID(ctx, suite.its.userID, api.MailInbox) + container, err := ac.GetContainerByID(ctx, suite.its.user.id, api.MailInbox) require.NoError(t, err, clues.ToCore(err)) msgs, err := ac.Stable. Client(). Users(). - ByUserId(suite.its.userID). + ByUserId(suite.its.user.id). MailFolders(). ByMailFolderId(ptr.Val(container.GetId())). Messages(). @@ -62,7 +62,7 @@ func (suite *MailPagerIntgSuite) TestMail_GetItemsInContainerByCollisionKey() { expect := maps.Keys(expectM) - results, err := suite.its.ac.Mail().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, api.MailInbox) + results, err := suite.its.ac.Mail().GetItemsInContainerByCollisionKey(ctx, suite.its.user.id, api.MailInbox) require.NoError(t, err, clues.ToCore(err)) require.Less(t, 0, len(results), "requires at least one result") @@ -101,7 +101,7 @@ func (suite *MailPagerIntgSuite) TestMail_GetItemsIDsInContainer() { msgs, err := ac.Stable. Client(). Users(). - ByUserId(suite.its.userID). + ByUserId(suite.its.user.id). MailFolders(). ByMailFolderId(api.MailInbox). Messages(). @@ -116,7 +116,7 @@ func (suite *MailPagerIntgSuite) TestMail_GetItemsIDsInContainer() { } results, err := suite.its.ac.Mail(). - GetItemIDsInContainer(ctx, suite.its.userID, api.MailInbox) + GetItemIDsInContainer(ctx, suite.its.user.id, api.MailInbox) require.NoError(t, err, clues.ToCore(err)) require.Less(t, 0, len(results), "requires at least one result") require.Equal(t, len(expect), len(results), "must have same count of items") diff --git a/src/pkg/services/m365/api/mail_test.go b/src/pkg/services/m365/api/mail_test.go index 74a4d57b7..e72d5445a 100644 --- a/src/pkg/services/m365/api/mail_test.go +++ b/src/pkg/services/m365/api/mail_test.go @@ -414,7 +414,7 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName() { ctx, flush := tester.NewContext(t) defer flush() - parent, err := acm.CreateContainer(ctx, suite.its.userID, "msgfolderroot", rc.Location) + parent, err := acm.CreateContainer(ctx, suite.its.user.id, "msgfolderroot", rc.Location) require.NoError(t, err, clues.ToCore(err)) table := []struct { @@ -448,7 +448,7 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName() { ctx, flush := tester.NewContext(t) defer flush() - _, err := acm.GetContainerByName(ctx, suite.its.userID, test.parentContainerID, test.name) + _, err := acm.GetContainerByName(ctx, suite.its.user.id, test.parentContainerID, test.name) test.expectErr(t, err, clues.ToCore(err)) }) } @@ -460,10 +460,10 @@ func (suite *MailAPIIntgSuite) TestMail_GetContainerByName() { ctx, flush := tester.NewContext(t) defer flush() - child, err := acm.CreateContainer(ctx, suite.its.userID, pid, rc.Location) + child, err := acm.CreateContainer(ctx, suite.its.user.id, pid, rc.Location) require.NoError(t, err, clues.ToCore(err)) - result, err := acm.GetContainerByName(ctx, suite.its.userID, pid, rc.Location) + result, err := acm.GetContainerByName(ctx, suite.its.user.id, pid, rc.Location) assert.NoError(t, err, clues.ToCore(err)) assert.Equal(t, ptr.Val(child.GetId()), ptr.Val(result.GetId())) }) diff --git a/src/pkg/services/m365/api/mock/pager.go b/src/pkg/services/m365/api/mock/pager.go index 9fd8749dd..22fedf649 100644 --- a/src/pkg/services/m365/api/mock/pager.go +++ b/src/pkg/services/m365/api/mock/pager.go @@ -8,26 +8,25 @@ import ( "github.com/alcionai/corso/src/pkg/services/m365/api" ) -type DeltaNextLinks struct { - Next *string - Delta *string -} - -func (dnl *DeltaNextLinks) GetOdataNextLink() *string { - return dnl.Next -} - -func (dnl *DeltaNextLinks) GetOdataDeltaLink() *string { - return dnl.Delta -} - type PagerResult[T any] struct { - Values []T - NextLink *string DeltaLink *string + NextLink *string + Values []T Err error } +func (pr PagerResult[T]) GetValue() []T { + return pr.Values +} + +func (pr PagerResult[T]) GetOdataNextLink() *string { + return pr.NextLink +} + +func (pr PagerResult[T]) GetOdataDeltaLink() *string { + return pr.DeltaLink +} + // --------------------------------------------------------------------------- // non-delta pager // --------------------------------------------------------------------------- @@ -37,7 +36,7 @@ type Pager[T any] struct { getIdx int } -func (p *Pager[T]) GetPage(context.Context) (api.PageLinker, error) { +func (p *Pager[T]) GetPage(context.Context) (api.LinkValuer[T], error) { if len(p.ToReturn) <= p.getIdx { return nil, clues.New("index out of bounds"). With("index", p.getIdx, "values", p.ToReturn) @@ -46,28 +45,11 @@ func (p *Pager[T]) GetPage(context.Context) (api.PageLinker, error) { idx := p.getIdx p.getIdx++ - link := DeltaNextLinks{Next: p.ToReturn[idx].NextLink} - - return &link, p.ToReturn[idx].Err + return &p.ToReturn[idx], p.ToReturn[idx].Err } func (p *Pager[T]) SetNext(string) {} -func (p *Pager[T]) ValuesIn(api.PageLinker) ([]T, error) { - idx := p.getIdx - if idx > 0 { - // Return values lag by one since we increment in GetPage(). - idx-- - } - - if len(p.ToReturn) <= idx { - return nil, clues.New("index out of bounds"). - With("index", idx, "values", p.ToReturn) - } - - return p.ToReturn[idx].Values, nil -} - // --------------------------------------------------------------------------- // delta pager // --------------------------------------------------------------------------- @@ -77,7 +59,7 @@ type DeltaPager[T any] struct { getIdx int } -func (p *DeltaPager[T]) GetPage(context.Context) (api.DeltaPageLinker, error) { +func (p *DeltaPager[T]) GetPage(context.Context) (api.DeltaLinkValuer[T], error) { if len(p.ToReturn) <= p.getIdx { return nil, clues.New("index out of bounds"). With("index", p.getIdx, "values", p.ToReturn) @@ -86,28 +68,8 @@ func (p *DeltaPager[T]) GetPage(context.Context) (api.DeltaPageLinker, error) { idx := p.getIdx p.getIdx++ - link := DeltaNextLinks{ - Next: p.ToReturn[idx].NextLink, - Delta: p.ToReturn[idx].DeltaLink, - } - - return &link, p.ToReturn[idx].Err + return &p.ToReturn[idx], p.ToReturn[idx].Err } -func (p *DeltaPager[T]) SetNext(string) {} -func (p *DeltaPager[T]) Reset(context.Context) {} - -func (p *DeltaPager[T]) ValuesIn(api.PageLinker) ([]T, error) { - idx := p.getIdx - if idx > 0 { - // Return values lag by one since we increment in GetPage(). - idx-- - } - - if len(p.ToReturn) <= idx { - return nil, clues.New("index out of bounds"). - With("index", idx, "values", p.ToReturn) - } - - return p.ToReturn[idx].Values, nil -} +func (p *DeltaPager[T]) SetNext(string) {} +func (p *DeltaPager[T]) Reset() {}