diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index d048bab97..415c4106e 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -580,7 +580,7 @@ func (ec exchangeCategory) isLeaf() bool { // Example: // [tenantID, service, userPN, category, mailFolder, mailID] // => {exchMailFolder: mailFolder, exchMail: mailID} -func (ec exchangeCategory) pathValues(repo, location path.Path) (map[categorizer]string, map[categorizer]string) { +func (ec exchangeCategory) pathValues(repo path.Path, ent details.DetailsEntry) map[categorizer][]string { var folderCat, itemCat categorizer switch ec { @@ -594,24 +594,19 @@ func (ec exchangeCategory) pathValues(repo, location path.Path) (map[categorizer folderCat, itemCat = ExchangeMailFolder, ExchangeMail default: - return map[categorizer]string{}, map[categorizer]string{} + return map[categorizer][]string{} } - rv := map[categorizer]string{ - folderCat: repo.Folder(false), - itemCat: repo.Item(), + result := map[categorizer][]string{ + folderCat: {repo.Folder(false)}, + itemCat: {repo.Item(), ent.ShortRef}, } - lv := map[categorizer]string{} - - if location != nil { - lv = map[categorizer]string{ - folderCat: location.Folder(false), - itemCat: location.Item(), - } + if len(ent.LocationRef) > 0 { + result[folderCat] = append(result[folderCat], ent.LocationRef) } - return rv, lv + return result } // pathKeys returns the path keys recognized by the receiver's leaf type. diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index b18563fe5..817b3ee25 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -1,6 +1,7 @@ package selectors import ( + "strings" "testing" "time" @@ -716,9 +717,14 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() { var ( repo = stubPath(suite.T(), usr, []string{fID1, fID2, mail}, path.EmailCategory) - loc = stubPath(suite.T(), usr, []string{fld1, fld2, mail}, path.EmailCategory) + loc = strings.Join([]string{fld1, fld2, mail}, "/") short = "thisisahashofsomekind" es = NewExchangeRestore(Any()) + ent = details.DetailsEntry{ + RepoRef: repo.String(), + ShortRef: short, + LocationRef: loc, + } ) table := []struct { @@ -758,12 +764,12 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() { scopes := setScopesToDefault(test.scope) var aMatch bool for _, scope := range scopes { - repoVals, locVals := ExchangeMail.pathValues(repo, loc) - if matchesPathValues(scope, ExchangeMail, repoVals, short) { + pvs := ExchangeMail.pathValues(repo, ent) + if matchesPathValues(scope, ExchangeMail, pvs) { aMatch = true break } - if matchesPathValues(scope, ExchangeMail, locVals, short) { + if matchesPathValues(scope, ExchangeMail, pvs) { aMatch = true break } @@ -1313,7 +1319,10 @@ func (suite *ExchangeSelectorSuite) TestPasses() { mail = setScopesToDefault(es.Mails(Any(), []string{mid})) noMail = setScopesToDefault(es.Mails(Any(), None())) allMail = setScopesToDefault(es.Mails(Any(), Any())) - pth = stubPath(suite.T(), "user", []string{"folder", mid}, path.EmailCategory) + repo = stubPath(suite.T(), "user", []string{"folder", mid}, path.EmailCategory) + ent = details.DetailsEntry{ + RepoRef: repo.String(), + } ) table := []struct { @@ -1336,12 +1345,11 @@ func (suite *ExchangeSelectorSuite) TestPasses() { suite.Run(test.name, func() { t := suite.T() - repoVals, locVals := cat.pathValues(pth, pth) + pvs := cat.pathValues(repo, ent) result := passes( cat, - repoVals, - locVals, + pvs, entry, test.excludes, test.filters, @@ -1447,25 +1455,25 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() { t := suite.T() contactPath := stubPath(t, "user", []string{"cfolder", "contactitem"}, path.ContactsCategory) - contactMap := map[categorizer]string{ - ExchangeContactFolder: contactPath.Folder(false), - ExchangeContact: contactPath.Item(), + contactMap := map[categorizer][]string{ + ExchangeContactFolder: {contactPath.Folder(false)}, + ExchangeContact: {contactPath.Item(), "short"}, } eventPath := stubPath(t, "user", []string{"ecalendar", "eventitem"}, path.EventsCategory) - eventMap := map[categorizer]string{ - ExchangeEventCalendar: eventPath.Folder(false), - ExchangeEvent: eventPath.Item(), + eventMap := map[categorizer][]string{ + ExchangeEventCalendar: {eventPath.Folder(false)}, + ExchangeEvent: {eventPath.Item(), "short"}, } mailPath := stubPath(t, "user", []string{"mfolder", "mailitem"}, path.EmailCategory) - mailMap := map[categorizer]string{ - ExchangeMailFolder: mailPath.Folder(false), - ExchangeMail: mailPath.Item(), + mailMap := map[categorizer][]string{ + ExchangeMailFolder: {mailPath.Folder(false)}, + ExchangeMail: {mailPath.Item(), "short"}, } table := []struct { cat exchangeCategory path path.Path - expect map[categorizer]string + expect map[categorizer][]string }{ {ExchangeContact, contactPath, contactMap}, {ExchangeEvent, eventPath, eventMap}, @@ -1473,9 +1481,13 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() { } for _, test := range table { suite.T().Run(string(test.cat), func(t *testing.T) { - r, l := test.cat.pathValues(test.path, test.path) - assert.Equal(t, test.expect, r) - assert.Equal(t, test.expect, l) + ent := details.DetailsEntry{ + RepoRef: test.path.String(), + ShortRef: "short", + } + + pvs := test.cat.pathValues(test.path, ent) + assert.Equal(t, test.expect, pvs) }) } } diff --git a/src/pkg/selectors/helpers_test.go b/src/pkg/selectors/helpers_test.go index 5ab0360e5..88e19fe71 100644 --- a/src/pkg/selectors/helpers_test.go +++ b/src/pkg/selectors/helpers_test.go @@ -55,13 +55,11 @@ func (mc mockCategorizer) isLeaf() bool { return mc == leafCatStub } -func (mc mockCategorizer) pathValues(repo, location path.Path) (map[categorizer]string, map[categorizer]string) { - pv := map[categorizer]string{ - rootCatStub: "root", - leafCatStub: "leaf", +func (mc mockCategorizer) pathValues(repo path.Path, ent details.DetailsEntry) map[categorizer][]string { + return map[categorizer][]string{ + rootCatStub: {"root"}, + leafCatStub: {"leaf"}, } - - return pv, pv } func (mc mockCategorizer) pathKeys() []categorizer { @@ -77,10 +75,10 @@ func (mc mockCategorizer) PathType() path.CategoryType { } } -func stubPathValues() map[categorizer]string { - return map[categorizer]string{ - rootCatStub: rootCatStub.String(), - leafCatStub: leafCatStub.String(), +func stubPathValues() map[categorizer][]string { + return map[categorizer][]string{ + rootCatStub: {rootCatStub.String()}, + leafCatStub: {leafCatStub.String()}, } } diff --git a/src/pkg/selectors/onedrive.go b/src/pkg/selectors/onedrive.go index f9cc037ea..dde3b9784 100644 --- a/src/pkg/selectors/onedrive.go +++ b/src/pkg/selectors/onedrive.go @@ -65,7 +65,7 @@ func (s Selector) ToOneDriveBackup() (*OneDriveBackup, error) { } func (s OneDriveBackup) SplitByResourceOwner(users []string) []OneDriveBackup { - sels := splitByResourceOwner[ExchangeScope](s.Selector, users, OneDriveUser) + sels := splitByResourceOwner[OneDriveScope](s.Selector, users, OneDriveUser) ss := make([]OneDriveBackup, 0, len(sels)) for _, sel := range sels { @@ -99,7 +99,7 @@ func (s Selector) ToOneDriveRestore() (*OneDriveRestore, error) { } func (s OneDriveRestore) SplitByResourceOwner(users []string) []OneDriveRestore { - sels := splitByResourceOwner[ExchangeScope](s.Selector, users, ExchangeUser) + sels := splitByResourceOwner[OneDriveScope](s.Selector, users, OneDriveUser) ss := make([]OneDriveRestore, 0, len(sels)) for _, sel := range sels { @@ -376,25 +376,20 @@ func (c oneDriveCategory) isLeaf() bool { // Example: // [tenantID, service, userPN, category, folder, fileID] // => {odFolder: folder, odFileID: fileID} -func (c oneDriveCategory) pathValues(repo, location path.Path) (map[categorizer]string, map[categorizer]string) { +func (c oneDriveCategory) pathValues(repo path.Path, ent details.DetailsEntry) map[categorizer][]string { // Ignore `drives//root:` for folder comparison rFld := path.Builder{}.Append(repo.Folders()...).PopFront().PopFront().PopFront().String() - rv := map[categorizer]string{ - OneDriveFolder: rFld, - OneDriveItem: repo.Item(), + + result := map[categorizer][]string{ + OneDriveFolder: {rFld}, + OneDriveItem: {repo.Item(), ent.ShortRef}, } - lv := map[categorizer]string{} - - if location != nil { - lFld := path.Builder{}.Append(location.Folders()...).PopFront().PopFront().PopFront().String() - lv = map[categorizer]string{ - OneDriveFolder: lFld, - OneDriveItem: location.Item(), - } + if len(ent.LocationRef) > 0 { + result[OneDriveFolder] = append(result[OneDriveFolder], ent.LocationRef) } - return rv, lv + return result } // pathKeys returns the path keys recognized by the receiver's leaf type. diff --git a/src/pkg/selectors/onedrive_test.go b/src/pkg/selectors/onedrive_test.go index 6423ca2a7..61a00533a 100644 --- a/src/pkg/selectors/onedrive_test.go +++ b/src/pkg/selectors/onedrive_test.go @@ -261,14 +261,18 @@ func (suite *OneDriveSelectorSuite) TestOneDriveCategory_PathValues() { filePath, err := pathBuilder.ToDataLayerOneDrivePath("tenant", "user", true) require.NoError(t, err) - expected := map[categorizer]string{ - OneDriveFolder: "dir1/dir2", - OneDriveItem: "file", + expected := map[categorizer][]string{ + OneDriveFolder: {"dir1/dir2"}, + OneDriveItem: {"file", "short"}, } - r, l := OneDriveItem.pathValues(filePath, filePath) + ent := details.DetailsEntry{ + RepoRef: filePath.String(), + ShortRef: "short", + } + + r := OneDriveItem.pathValues(filePath, ent) assert.Equal(t, expected, r) - assert.Equal(t, expected, l) } func (suite *OneDriveSelectorSuite) TestOneDriveScope_MatchesInfo() { diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index 6b7d45c89..32076f049 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -88,7 +88,7 @@ type ( // folderCat: folder, // itemCat: itemID, // } - pathValues(path.Path, path.Path) (map[categorizer]string, map[categorizer]string) + pathValues(path.Path, details.DetailsEntry) map[categorizer][]string // pathKeys produces a list of categorizers that can be used as keys in the pathValues // map. The combination of the two funcs generically interprets the context of the @@ -212,6 +212,20 @@ func matches[T scopeT, C categoryT](s T, cat C, inpt string) bool { return s[cat.String()].Compare(inpt) } +// matchesAny returns true if the category is included in the scope's +// data type, and any one of the input strings passes the scope's filter. +func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool { + if !typeAndCategoryMatches(cat, s.categorizer()) { + return false + } + + if len(inpts) == 0 { + return false + } + + return s[cat.String()].CompareAny(inpts...) +} + // getCategory returns the scope's category value. // if s is a filter-type scope, returns the filter category. func getCategory[T scopeT](s T) string { @@ -297,6 +311,8 @@ func reduce[T scopeT, C categoryT]( return nil } + el := errs.Local() + // if a DiscreteOwner is specified, only match details for that owner. matchesResourceOwner := s.ResourceOwners if len(s.DiscreteOwner) > 0 { @@ -314,35 +330,10 @@ func reduce[T scopeT, C categoryT]( for _, ent := range deets.Items() { repoPath, err := path.FromDataLayerPath(ent.RepoRef, true) if err != nil { - errs.AddRecoverable(clues.Wrap(err, "transforming repoRef to path").WithClues(ctx)) + el.AddRecoverable(clues.Wrap(err, "transforming repoRef to path").WithClues(ctx)) continue } - var locationPath path.Path - - // if the details entry has a locationRef specified, use those folders in place - // of the repoRef folders, so that scopes can match against the display names - // instead of container IDs. - if len(ent.LocationRef) > 0 { - pb, err := path.Builder{}.SplitUnescapeAppend(ent.LocationRef) - if err != nil { - errs.AddRecoverable(clues.Wrap(err, "transforming locationRef to path").WithClues(ctx)) - continue - } - - locationPath, err = pb.Append(repoPath.Item()). - ToDataLayerPath( - repoPath.Tenant(), - repoPath.ResourceOwner(), - repoPath.Service(), - repoPath.Category(), - true) - if err != nil { - errs.AddRecoverable(clues.Wrap(err, "transforming locationRef to path").WithClues(ctx)) - continue - } - } - // first check, every entry needs to match the selector's resource owners. if !matchesResourceOwner.Compare(repoPath.ResourceOwner()) { continue @@ -360,9 +351,9 @@ func reduce[T scopeT, C categoryT]( continue } - rv, lv := dc.pathValues(repoPath, locationPath) + pv := dc.pathValues(repoPath, *ent) - passed := passes(dc, rv, lv, *ent, e, f, i) + passed := passes(dc, pv, *ent, e, f, i) if passed { ents = append(ents, *ent) } @@ -407,7 +398,7 @@ func scopesByCategory[T scopeT, C categoryT]( // if the path is included, passes filters, and not excluded. func passes[T scopeT, C categoryT]( cat C, - repoValues, locationValues map[categorizer]string, + pathValues map[categorizer][]string, entry details.DetailsEntry, excs, filts, incs []T, ) bool { @@ -423,7 +414,7 @@ func passes[T scopeT, C categoryT]( var included bool for _, inc := range incs { - if matchesEntry(inc, cat, repoValues, locationValues, entry) { + if matchesEntry(inc, cat, pathValues, entry) { included = true break } @@ -436,14 +427,14 @@ func passes[T scopeT, C categoryT]( // all filters must pass for _, filt := range filts { - if !matchesEntry(filt, cat, repoValues, locationValues, entry) { + if !matchesEntry(filt, cat, pathValues, entry) { return false } } // any matching exclusion means failure for _, exc := range excs { - if matchesEntry(exc, cat, repoValues, locationValues, entry) { + if matchesEntry(exc, cat, pathValues, entry) { return false } } @@ -456,7 +447,7 @@ func passes[T scopeT, C categoryT]( func matchesEntry[T scopeT, C categoryT]( sc T, cat C, - repoValues, locationValues map[categorizer]string, + pathValues map[categorizer][]string, entry details.DetailsEntry, ) bool { // filterCategory requires matching against service-specific info values @@ -464,11 +455,7 @@ func matchesEntry[T scopeT, C categoryT]( return sc.matchesInfo(entry.ItemInfo) } - if len(locationValues) > 0 && matchesPathValues(sc, cat, locationValues, entry.ShortRef) { - return true - } - - return matchesPathValues(sc, cat, repoValues, entry.ShortRef) + return matchesPathValues(sc, cat, pathValues) } // matchesPathValues will check whether the pathValues have matching entries @@ -479,8 +466,7 @@ func matchesEntry[T scopeT, C categoryT]( func matchesPathValues[T scopeT, C categoryT]( sc T, cat C, - pathValues map[categorizer]string, - shortRef string, + pathValues map[categorizer][]string, ) bool { for _, c := range cat.pathKeys() { // resourceOwners are now checked at the beginning of the reduction. @@ -488,12 +474,6 @@ func matchesPathValues[T scopeT, C categoryT]( continue } - // the pathValues must have an entry for the given categorizer - pathVal, ok := pathValues[c] - if !ok { - return false - } - cc := c.(C) if isNoneTarget(sc, cc) { @@ -505,23 +485,13 @@ func matchesPathValues[T scopeT, C categoryT]( continue } - var ( - match bool - isLeaf = c.isLeaf() - ) - - switch { - // Leaf category - the scope can match either the path value (the item ID itself), - // or the shortRef hash representing the item. - case isLeaf && len(shortRef) > 0: - match = matches(sc, cc, pathVal) || matches(sc, cc, shortRef) - - // all other categories (root, folder, etc) just need to pass the filter - default: - match = matches(sc, cc, pathVal) + // the pathValues must have an entry for the given categorizer + pathVals, ok := pathValues[c] + if !ok || len(pathVals) == 0 { + return false } - if !match { + if !matchesAny(sc, cc, pathVals) { return false } } @@ -530,7 +500,7 @@ func matchesPathValues[T scopeT, C categoryT]( } // --------------------------------------------------------------------------- -// categorizer funcs +// helper funcs // --------------------------------------------------------------------------- // categoryMatches returns true if: diff --git a/src/pkg/selectors/scopes_test.go b/src/pkg/selectors/scopes_test.go index 8d5e0517e..80b94b06f 100644 --- a/src/pkg/selectors/scopes_test.go +++ b/src/pkg/selectors/scopes_test.go @@ -354,10 +354,14 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() { } func (suite *SelectorScopesSuite) TestPasses() { - cat := rootCatStub - pth := stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory) - repoVals, locVals := cat.pathValues(pth, pth) - entry := details.DetailsEntry{} + var ( + cat = rootCatStub + pth = stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory) + entry = details.DetailsEntry{ + RepoRef: pth.String(), + } + pvs = cat.pathValues(pth, entry) + ) for _, test := range reduceTestTable { suite.Run(test.name, func() { @@ -369,8 +373,7 @@ func (suite *SelectorScopesSuite) TestPasses() { incl := toMockScope(sel.Includes) result := passes( cat, - repoVals, - locVals, + pvs, entry, excl, filt, incl) test.expectPasses(t, result) @@ -394,7 +397,6 @@ func toMockScope(sc []scope) []mockScope { func (suite *SelectorScopesSuite) TestMatchesPathValues() { cat := rootCatStub - pvs := stubPathValues() short := "brunheelda" table := []struct { @@ -440,12 +442,14 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() { for _, test := range table { suite.Run(test.name, func() { t := suite.T() + pvs := stubPathValues() + pvs[leafCatStub] = append(pvs[leafCatStub], test.shortRef) sc := stubScope("") sc[rootCatStub.String()] = filterize(scopeConfig{}, test.rootVal) sc[leafCatStub.String()] = filterize(scopeConfig{}, test.leafVal) - test.expect(t, matchesPathValues(sc, cat, pvs, test.shortRef)) + test.expect(t, matchesPathValues(sc, cat, pvs)) }) } } diff --git a/src/pkg/selectors/sharepoint.go b/src/pkg/selectors/sharepoint.go index c0407f75a..f7cfc47e2 100644 --- a/src/pkg/selectors/sharepoint.go +++ b/src/pkg/selectors/sharepoint.go @@ -65,7 +65,7 @@ func (s Selector) ToSharePointBackup() (*SharePointBackup, error) { } func (s SharePointBackup) SplitByResourceOwner(sites []string) []SharePointBackup { - sels := splitByResourceOwner[ExchangeScope](s.Selector, sites, SharePointSite) + sels := splitByResourceOwner[SharePointScope](s.Selector, sites, SharePointSite) ss := make([]SharePointBackup, 0, len(sels)) for _, sel := range sels { @@ -98,8 +98,8 @@ func (s Selector) ToSharePointRestore() (*SharePointRestore, error) { return &src, nil } -func (s SharePointRestore) SplitByResourceOwner(users []string) []SharePointRestore { - sels := splitByResourceOwner[ExchangeScope](s.Selector, users, ExchangeUser) +func (s SharePointRestore) SplitByResourceOwner(sites []string) []SharePointRestore { + sels := splitByResourceOwner[SharePointScope](s.Selector, sites, SharePointSite) ss := make([]SharePointRestore, 0, len(sels)) for _, sel := range sels { @@ -476,7 +476,7 @@ func (c sharePointCategory) isLeaf() bool { // Example: // [tenantID, service, siteID, category, folder, itemID] // => {spFolder: folder, spItemID: itemID} -func (c sharePointCategory) pathValues(repo, location path.Path) (map[categorizer]string, map[categorizer]string) { +func (c sharePointCategory) pathValues(repo path.Path, ent details.DetailsEntry) map[categorizer][]string { var folderCat, itemCat categorizer switch c { @@ -487,24 +487,19 @@ func (c sharePointCategory) pathValues(repo, location path.Path) (map[categorize case SharePointPage, SharePointPageFolder: folderCat, itemCat = SharePointPageFolder, SharePointPage default: - return map[categorizer]string{}, map[categorizer]string{} + return map[categorizer][]string{} } - rv := map[categorizer]string{ - folderCat: repo.Folder(false), - itemCat: repo.Item(), + result := map[categorizer][]string{ + folderCat: {repo.Folder(false)}, + itemCat: {repo.Item(), ent.ShortRef}, } - lv := map[categorizer]string{} - - if location != nil { - lv = map[categorizer]string{ - folderCat: location.Folder(false), - itemCat: location.Item(), - } + if len(ent.LocationRef) > 0 { + result[folderCat] = append(result[folderCat], ent.LocationRef) } - return rv, lv + return result } // pathKeys returns the path keys recognized by the receiver's leaf type. diff --git a/src/pkg/selectors/sharepoint_test.go b/src/pkg/selectors/sharepoint_test.go index a7b3aa701..ffc7a9ec8 100644 --- a/src/pkg/selectors/sharepoint_test.go +++ b/src/pkg/selectors/sharepoint_test.go @@ -326,22 +326,22 @@ func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() { table := []struct { name string sc sharePointCategory - expected map[categorizer]string + expected map[categorizer][]string }{ { name: "SharePoint Libraries", sc: SharePointLibraryItem, - expected: map[categorizer]string{ - SharePointLibrary: "dir1/dir2", - SharePointLibraryItem: "item", + expected: map[categorizer][]string{ + SharePointLibrary: {"dir1/dir2"}, + SharePointLibraryItem: {"item", "short"}, }, }, { name: "SharePoint Lists", sc: SharePointListItem, - expected: map[categorizer]string{ - SharePointList: "dir1/dir2", - SharePointListItem: "item", + expected: map[categorizer][]string{ + SharePointList: {"dir1/dir2"}, + SharePointListItem: {"item", "short"}, }, }, } @@ -356,9 +356,14 @@ func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() { test.sc.PathType(), true) require.NoError(t, err) - r, l := test.sc.pathValues(itemPath, itemPath) - assert.Equal(t, test.expected, r) - assert.Equal(t, test.expected, l) + + ent := details.DetailsEntry{ + RepoRef: itemPath.String(), + ShortRef: "short", + } + + pv := test.sc.pathValues(itemPath, ent) + assert.Equal(t, test.expected, pv) }) } }