move container enum to pager pattern (#4298)

moves exchange container enumeration funcs in
the api to the pager pattern established with item enumeration.

---

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

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-09-22 11:34:06 -06:00 committed by GitHub
parent dcffc61bf5
commit 0545365b12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 246 additions and 212 deletions

View File

@ -810,7 +810,8 @@ func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
err := suite.ac.Events().EnumerateContainers(
ctx,
suite.user,
api.DefaultCalendar,
"",
false,
fn,
fault.New(true))
require.NoError(t, err, clues.ToCore(err))

View File

@ -77,7 +77,13 @@ func (cfc *contactContainerCache) Populate(
return clues.Wrap(err, "initializing")
}
err := cfc.enumer.EnumerateContainers(ctx, cfc.userID, baseID, cfc.addFolder, errs)
err := cfc.enumer.EnumerateContainers(
ctx,
cfc.userID,
baseID,
false,
cfc.addFolder,
errs)
if err != nil {
return clues.Wrap(err, "enumerating containers")
}

View File

@ -27,6 +27,7 @@ type containersEnumerator interface {
EnumerateContainers(
ctx context.Context,
userID, baseDirID string,
immutableIDs bool,
fn func(graph.CachedContainer) error,
errs *fault.Bus,
) error
@ -332,15 +333,17 @@ func (cr *containerResolver) LocationInCache(pathString string) (string, bool) {
// addFolder adds a folder to the cache with the given ID. If the item is
// already in the cache does nothing. The path for the item is not modified.
func (cr *containerResolver) addFolder(cf graph.CachedContainer) error {
var err error
// Only require a non-nil non-empty parent if the path isn't already populated.
if cf.Path() != nil {
if err := checkIDAndName(cf); err != nil {
return clues.Wrap(err, "adding item to cache")
}
err = checkIDAndName(cf)
} else {
if err := checkRequiredValues(cf); err != nil {
return clues.Wrap(err, "adding item to cache")
}
err = checkRequiredValues(cf)
}
if err != nil {
return clues.Wrap(err, "validating container for cache")
}
if _, ok := cr.cache[ptr.Val(cf.GetId())]; ok {

View File

@ -74,6 +74,7 @@ func (ecc *eventContainerCache) Populate(
ctx,
ecc.userID,
"",
false,
ecc.addFolder,
errs)
if err != nil {

View File

@ -100,7 +100,13 @@ func (mc *mailContainerCache) Populate(
return clues.Wrap(err, "initializing")
}
err := mc.enumer.EnumerateContainers(ctx, mc.userID, "", mc.addFolder, errs)
err := mc.enumer.EnumerateContainers(
ctx,
mc.userID,
"",
false,
mc.addFolder,
errs)
if err != nil {
return clues.Wrap(err, "enumerating containers")
}

View File

@ -933,25 +933,25 @@ func (suite *ControllerIntegrationSuite) TestRestoreAndBackup_core() {
// {
// name: "MultipleEventsSingleCalendar",
// service: path.ExchangeService,
// collections: []colInfo{
// collections: []stub.ColInfo{
// {
// pathElements: []string{"Work"},
// category: path.EventsCategory,
// items: []itemInfo{
// PathElements: []string{"Work"},
// Category: path.EventsCategory,
// Items: []stub.ItemInfo{
// {
// name: "someencodeditemID",
// data: exchMock.EventWithSubjectBytes("Ghimley"),
// lookupKey: "Ghimley",
// Name: "someencodeditemID",
// Data: exchMock.EventWithSubjectBytes("Ghimley"),
// LookupKey: "Ghimley",
// },
// {
// name: "someencodeditemID2",
// data: exchMock.EventWithSubjectBytes("Irgot"),
// lookupKey: "Irgot",
// Name: "someencodeditemID2",
// Data: exchMock.EventWithSubjectBytes("Irgot"),
// LookupKey: "Irgot",
// },
// {
// name: "someencodeditemID3",
// data: exchMock.EventWithSubjectBytes("Jannes"),
// lookupKey: "Jannes",
// Name: "someencodeditemID3",
// Data: exchMock.EventWithSubjectBytes("Jannes"),
// LookupKey: "Jannes",
// },
// },
// },
@ -960,41 +960,41 @@ func (suite *ControllerIntegrationSuite) TestRestoreAndBackup_core() {
// {
// name: "MultipleEventsMultipleCalendars",
// service: path.ExchangeService,
// collections: []colInfo{
// collections: []stub.ColInfo{
// {
// pathElements: []string{"Work"},
// category: path.EventsCategory,
// items: []itemInfo{
// PathElements: []string{"Work"},
// Category: path.EventsCategory,
// Items: []stub.ItemInfo{
// {
// name: "someencodeditemID",
// data: exchMock.EventWithSubjectBytes("Ghimley"),
// lookupKey: "Ghimley",
// Name: "someencodeditemID",
// Data: exchMock.EventWithSubjectBytes("Ghimley"),
// LookupKey: "Ghimley",
// },
// {
// name: "someencodeditemID2",
// data: exchMock.EventWithSubjectBytes("Irgot"),
// lookupKey: "Irgot",
// Name: "someencodeditemID2",
// Data: exchMock.EventWithSubjectBytes("Irgot"),
// LookupKey: "Irgot",
// },
// {
// name: "someencodeditemID3",
// data: exchMock.EventWithSubjectBytes("Jannes"),
// lookupKey: "Jannes",
// Name: "someencodeditemID3",
// Data: exchMock.EventWithSubjectBytes("Jannes"),
// LookupKey: "Jannes",
// },
// },
// },
// {
// pathElements: []string{"Personal"},
// category: path.EventsCategory,
// items: []itemInfo{
// PathElements: []string{"Personal"},
// Category: path.EventsCategory,
// Items: []stub.ItemInfo{
// {
// name: "someencodeditemID4",
// data: exchMock.EventWithSubjectBytes("Argon"),
// lookupKey: "Argon",
// Name: "someencodeditemID4",
// Data: exchMock.EventWithSubjectBytes("Argon"),
// LookupKey: "Argon",
// },
// {
// name: "someencodeditemID5",
// data: exchMock.EventWithSubjectBytes("Bernard"),
// lookupKey: "Bernard",
// Name: "someencodeditemID5",
// Data: exchMock.EventWithSubjectBytes("Bernard"),
// LookupKey: "Bernard",
// },
// },
// },

View File

@ -95,7 +95,6 @@ type CacheFolder struct {
p *path.Builder
}
// NewCacheFolder public constructor for struct
func NewCacheFolder(c Container, pb, lpb *path.Builder) CacheFolder {
cf := CacheFolder{
Container: c,

View File

@ -18,25 +18,29 @@ import (
// container pager
// ---------------------------------------------------------------------------
// EnumerateContainers iterates through all of the users current
// contacts folders, converting each to a graph.CacheFolder, and calling
// fn(cf) on each one.
// Folder hierarchy is represented in its current state, and does
// not contain historical data.
// TODO: use enumerateItems for containers
func (c Contacts) EnumerateContainers(
ctx context.Context,
var _ Pager[models.ContactFolderable] = &contactsFoldersPageCtrl{}
type contactsFoldersPageCtrl struct {
gs graph.Servicer
builder *users.ItemContactFoldersItemChildFoldersRequestBuilder
options *users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration
}
func (c Contacts) NewContactFoldersPager(
userID, baseContainerID string,
fn func(graph.CachedContainer) error,
errs *fault.Bus,
) error {
config := &users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemContactFoldersItemChildFoldersRequestBuilderGetQueryParameters{
Select: idAnd(displayName, parentFolderID),
},
immutableIDs bool,
selectProps ...string,
) Pager[models.ContactFolderable] {
options := &users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
QueryParameters: &users.ItemContactFoldersItemChildFoldersRequestBuilderGetQueryParameters{},
// do NOT set Top. It limits the total items received.
}
if len(selectProps) > 0 {
options.QueryParameters.Select = selectProps
}
el := errs.Local()
builder := c.Stable.
Client().
Users().
@ -45,44 +49,57 @@ func (c Contacts) EnumerateContainers(
ByContactFolderId(baseContainerID).
ChildFolders()
for {
return &contactsFoldersPageCtrl{c.Stable, builder, options}
}
func (p *contactsFoldersPageCtrl) GetPage(
ctx context.Context,
) (NextLinkValuer[models.ContactFolderable], error) {
resp, err := p.builder.Get(ctx, p.options)
return resp, graph.Stack(ctx, err).OrNil()
}
func (p *contactsFoldersPageCtrl) SetNextLink(nextLink string) {
p.builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(nextLink, p.gs.Adapter())
}
func (p *contactsFoldersPageCtrl) ValidModTimes() bool {
return true
}
// EnumerateContainers iterates through all of the users current
// contacts folders, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Contact folders are represented in their current state, and do
// not contain historical data.
func (c Contacts) EnumerateContainers(
ctx context.Context,
userID, baseContainerID string,
immutableIDs bool,
fn func(graph.CachedContainer) error,
errs *fault.Bus,
) error {
var (
el = errs.Local()
pgr = c.NewContactFoldersPager(userID, baseContainerID, immutableIDs)
)
containers, err := enumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
resp, err := builder.Get(ctx, config)
if err != nil {
return graph.Stack(ctx, err)
gncf := graph.NewCacheFolder(c, nil, nil)
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
for _, fold := range resp.GetValue() {
if el.Failure() != nil {
return el.Failure()
}
if err := graph.CheckIDNameAndParentFolderID(fold); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
fctx := clues.Add(
ctx,
"container_id", ptr.Val(fold.GetId()),
"container_display_name", ptr.Val(fold.GetDisplayName()))
temp := graph.NewCacheFolder(fold, nil, nil)
if err := fn(&temp); err != nil {
errs.AddRecoverable(ctx, graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break
}
builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(link, c.Stable.Adapter())
}
return el.Failure()

View File

@ -21,73 +21,89 @@ const eventBetaDeltaURLTemplate = "https://graph.microsoft.com/beta/users/%s/cal
// container pager
// ---------------------------------------------------------------------------
var _ Pager[models.Calendarable] = &eventsCalendarsPageCtrl{}
type eventsCalendarsPageCtrl struct {
gs graph.Servicer
builder *users.ItemCalendarsRequestBuilder
options *users.ItemCalendarsRequestBuilderGetRequestConfiguration
}
func (c Events) NewEventCalendarsPager(
userID string,
immutableIDs bool,
selectProps ...string,
) Pager[models.Calendarable] {
options := &users.ItemCalendarsRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
QueryParameters: &users.ItemCalendarsRequestBuilderGetQueryParameters{},
// do NOT set Top. It limits the total items received.
}
if len(selectProps) > 0 {
options.QueryParameters.Select = selectProps
}
builder := c.Stable.
Client().
Users().
ByUserId(userID).
Calendars()
return &eventsCalendarsPageCtrl{c.Stable, builder, options}
}
func (p *eventsCalendarsPageCtrl) GetPage(
ctx context.Context,
) (NextLinkValuer[models.Calendarable], error) {
resp, err := p.builder.Get(ctx, p.options)
return resp, graph.Stack(ctx, err).OrNil()
}
func (p *eventsCalendarsPageCtrl) SetNextLink(nextLink string) {
p.builder = users.NewItemCalendarsRequestBuilder(nextLink, p.gs.Adapter())
}
func (p *eventsCalendarsPageCtrl) ValidModTimes() bool {
return true
}
// EnumerateContainers iterates through all of the users current
// calendars, converting each to a graph.CacheFolder, and
// calling fn(cf) on each one.
// Folder hierarchy is represented in its current state, and does
// events calendars, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Calendars are represented in their current state, and do
// not contain historical data.
func (c Events) EnumerateContainers(
ctx context.Context,
userID, baseContainerID string,
userID, _ string, // baseContainerID not needed
immutableIDs bool,
fn func(graph.CachedContainer) error,
errs *fault.Bus,
) error {
var (
el = errs.Local()
config = &users.ItemCalendarsRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemCalendarsRequestBuilderGetQueryParameters{
Select: idAnd("name"),
},
}
builder = c.Stable.
Client().
Users().
ByUserId(userID).
Calendars()
el = errs.Local()
pgr = c.NewEventCalendarsPager(userID, immutableIDs)
)
for {
containers, err := enumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
resp, err := builder.Get(ctx, config)
if err != nil {
return graph.Stack(ctx, err)
gncf := graph.NewCacheFolder(
CalendarDisplayable{Calendarable: c},
path.Builder{}.Append(ptr.Val(c.GetId())),
path.Builder{}.Append(ptr.Val(c.GetName())))
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
for _, cal := range resp.GetValue() {
if el.Failure() != nil {
break
}
cd := CalendarDisplayable{Calendarable: cal}
if err := graph.CheckIDAndName(cd); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
fctx := clues.Add(
ctx,
"container_id", ptr.Val(cal.GetId()),
"container_name", ptr.Val(cal.GetName()))
temp := graph.NewCacheFolder(
cd,
path.Builder{}.Append(ptr.Val(cd.GetId())), // storage path
path.Builder{}.Append(ptr.Val(cd.GetDisplayName()))) // display location
if err := fn(&temp); err != nil {
errs.AddRecoverable(ctx, graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break
}
builder = users.NewItemCalendarsRequestBuilder(link, c.Stable.Adapter())
}
return el.Failure()

View File

@ -306,7 +306,8 @@ func (suite *EventsAPIIntgSuite) TestEvents_canFindNonStandardFolder() {
err = ac.EnumerateContainers(
ctx,
suite.its.user.id,
"Calendar",
api.DefaultCalendar,
false,
findContainer,
fault.New(true))
require.NoError(t, err, clues.ToCore(err))

View File

@ -19,100 +19,84 @@ import (
// container pager
// ---------------------------------------------------------------------------
type mailFolderPager struct {
service graph.Servicer
var _ Pager[models.MailFolderable] = &mailFoldersPageCtrl{}
type mailFoldersPageCtrl struct {
gs graph.Servicer
builder *users.ItemMailFoldersRequestBuilder
options *users.ItemMailFoldersRequestBuilderGetRequestConfiguration
}
func (c Mail) NewMailFolderPager(userID string) mailFolderPager {
func (c Mail) NewMailFoldersPager(
userID string,
immutableIDs bool,
selectProps ...string,
) Pager[models.MailFolderable] {
options := &users.ItemMailFoldersRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
QueryParameters: &users.ItemMailFoldersRequestBuilderGetQueryParameters{},
// do NOT set Top. It limits the total items received.
}
if len(selectProps) > 0 {
options.QueryParameters.Select = selectProps
}
// v1.0 non delta /mailFolders endpoint does not return any of the nested folders
rawURL := fmt.Sprintf(mailFoldersBetaURLTemplate, userID)
builder := users.NewItemMailFoldersRequestBuilder(rawURL, c.Stable.Adapter())
return mailFolderPager{c.Stable, builder}
return &mailFoldersPageCtrl{c.Stable, builder, options}
}
func (p *mailFolderPager) getPage(ctx context.Context) (NextLinker, error) {
page, err := p.builder.Get(ctx, nil)
if err != nil {
return nil, graph.Stack(ctx, err)
}
return page, nil
func (p *mailFoldersPageCtrl) GetPage(
ctx context.Context,
) (NextLinkValuer[models.MailFolderable], error) {
resp, err := p.builder.Get(ctx, p.options)
return resp, graph.Stack(ctx, err).OrNil()
}
func (p *mailFolderPager) SetNextLink(nextLink string) {
p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.service.Adapter())
func (p *mailFoldersPageCtrl) SetNextLink(nextLink string) {
p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.gs.Adapter())
}
func (p *mailFolderPager) valuesIn(pl NextLinker) ([]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)
if !ok {
return nil, clues.New("converting to ItemMailFoldersResponseable")
}
return page.GetValue(), nil
func (p *mailFoldersPageCtrl) ValidModTimes() bool {
return true
}
// EnumerateContainers iterates through all of the users current
// mail folders, converting each to a graph.CacheFolder, and calling
// fn(cf) on each one.
// mail folders, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Folder hierarchy is represented in its current state, and does
// not contain historical data.
func (c Mail) EnumerateContainers(
ctx context.Context,
userID, baseContainerID string,
userID, _ string, // baseContainerID not needed here
immutableIDs bool,
fn func(graph.CachedContainer) error,
errs *fault.Bus,
) error {
el := errs.Local()
pgr := c.NewMailFolderPager(userID)
var (
el = errs.Local()
pgr = c.NewMailFoldersPager(userID, immutableIDs)
)
for {
containers, err := enumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
page, err := pgr.getPage(ctx)
if err != nil {
return graph.Stack(ctx, err)
gncf := graph.NewCacheFolder(c, nil, nil)
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
resp, err := pgr.valuesIn(page)
if err != nil {
return graph.Stack(ctx, err)
}
for _, fold := range resp {
if el.Failure() != nil {
break
}
if err := graph.CheckIDNameAndParentFolderID(fold); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
fctx := clues.Add(
ctx,
"container_id", ptr.Val(fold.GetId()),
"container_name", ptr.Val(fold.GetDisplayName()))
temp := graph.NewCacheFolder(fold, nil, nil)
if err := fn(&temp); err != nil {
errs.AddRecoverable(ctx, graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
link, ok := ptr.ValOK(page.GetOdataNextLink())
if !ok {
break
}
pgr.SetNextLink(link)
}
return el.Failure()