diff --git a/src/pkg/backup/details/details.go b/src/pkg/backup/details/details.go index a506b20bf..91353b7e8 100644 --- a/src/pkg/backup/details/details.go +++ b/src/pkg/backup/details/details.go @@ -39,7 +39,7 @@ func (dm DetailsModel) PrintEntries(ctx context.Context) { } func printTable(ctx context.Context, dm DetailsModel) { - perType := map[itemType][]print.Printable{} + perType := map[ItemType][]print.Printable{} for _, de := range dm.Entries { it := de.infoType() @@ -226,21 +226,21 @@ func (de DetailsEntry) Values() []string { return vs } -type itemType int +type ItemType int const ( - UnknownType itemType = iota + UnknownType ItemType = iota // separate each service by a factor of 100 for padding ExchangeContact ExchangeEvent ExchangeMail - SharepointItem itemType = iota + 100 + SharepointItem ItemType = iota + 100 - OneDriveItem itemType = iota + 200 + OneDriveItem ItemType = iota + 200 - FolderItem itemType = iota + 300 + FolderItem ItemType = iota + 300 ) // ItemInfo is a oneOf that contains service specific @@ -258,7 +258,7 @@ type ItemInfo struct { // infoType provides internal categorization for collecting like-typed ItemInfos. // It should return the most granular value type (ex: "event" for an exchange // calendar event). -func (i ItemInfo) infoType() itemType { +func (i ItemInfo) infoType() ItemType { switch { case i.Folder != nil: return i.Folder.ItemType @@ -277,8 +277,8 @@ func (i ItemInfo) infoType() itemType { } type FolderInfo struct { - ItemType itemType - DisplayName string `json:"displayName"` + ItemType ItemType `json:"itemType,omitempty"` + DisplayName string `json:"displayName"` } func (i FolderInfo) Headers() []string { @@ -291,7 +291,7 @@ func (i FolderInfo) Values() []string { // ExchangeInfo describes an exchange item type ExchangeInfo struct { - ItemType itemType + ItemType ItemType `json:"itemType,omitempty"` Sender string `json:"sender,omitempty"` Subject string `json:"subject,omitempty"` Received time.Time `json:"received,omitempty"` @@ -344,7 +344,7 @@ func (i ExchangeInfo) Values() []string { // TODO: Implement this. This is currently here // just to illustrate usage type SharepointInfo struct { - ItemType itemType + ItemType ItemType `json:"itemType,omitempty"` } // Headers returns the human-readable names of properties in a SharepointInfo @@ -361,9 +361,9 @@ func (i SharepointInfo) Values() []string { // OneDriveInfo describes a oneDrive item type OneDriveInfo struct { - ItemType itemType - ParentPath string `json:"parentPath"` - ItemName string `json:"itemName"` + ItemType ItemType `json:"itemType,omitempty"` + ParentPath string `json:"parentPath"` + ItemName string `json:"itemName"` } // Headers returns the human-readable names of properties in a OneDriveInfo diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 75accde1e..5313b8849 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -333,8 +333,8 @@ func (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope { func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope { return []ExchangeScope{ makeFilterScope[ExchangeScope]( - ExchangeMail, - ExchangeFilterMailReceivedAfter, + ExchangeEvent, + ExchangeFilterEventStartsAfter, []string{timeStrings}, wrapFilter(filters.Less)), } @@ -347,8 +347,8 @@ func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope func (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope { return []ExchangeScope{ makeFilterScope[ExchangeScope]( - ExchangeMail, - ExchangeFilterMailReceivedBefore, + ExchangeEvent, + ExchangeFilterEventStartsBefore, []string{timeStrings}, wrapFilter(filters.Greater)), } @@ -690,25 +690,21 @@ func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.D ) } -// matchesEntry returns true if either the path or the info in the exchangeEntry matches the scope details. -func (s ExchangeScope) matchesEntry( - cat categorizer, - pathValues map[categorizer]string, - entry details.DetailsEntry, -) bool { - // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat.(exchangeCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.Exchange) -} - // matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter // returns true if the scope and info match for the provided category. -func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool { - // we need values to match against +func (s ExchangeScope) matchesInfo(dii details.ItemInfo) bool { + info := dii.Exchange if info == nil { return false } filterCat := s.FilterCategory() + + cfpc := categoryFromItemType(info.ItemType) + if !typeAndCategoryMatches(filterCat, cfpc) { + return false + } + i := "" switch filterCat { @@ -732,3 +728,19 @@ func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool { return s.Matches(filterCat, i) } + +// categoryFromItemType interprets the category represented by the ExchangeInfo +// struct. Since every ExchangeInfo can hold all exchange data info, the exact +// type that the struct represents must be compared using its ItemType prop. +func categoryFromItemType(pct details.ItemType) exchangeCategory { + switch pct { + case details.ExchangeContact: + return ExchangeContact + case details.ExchangeMail: + return ExchangeMail + case details.ExchangeEvent: + return ExchangeEvent + } + + return ExchangeCategoryUnknown +} diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index 42bc852f2..42f19fa2f 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -710,62 +710,99 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesInfo() { epoch = time.Time{} now = time.Now() future = now.Add(1 * time.Minute) - info = &details.ExchangeInfo{ - ContactName: name, - EventRecurs: true, - EventStart: now, - Organizer: organizer, - Sender: sender, - Subject: subject, - Received: now, - } ) + infoWith := func(itype details.ItemType) details.ItemInfo { + return details.ItemInfo{ + Exchange: &details.ExchangeInfo{ + ItemType: itype, + ContactName: name, + EventRecurs: true, + EventStart: now, + Organizer: organizer, + Sender: sender, + Subject: subject, + Received: now, + }, + } + } + table := []struct { name string + itype details.ItemType scope []ExchangeScope expect assert.BoolAssertionFunc }{ - {"any mail with a sender", es.MailSender(AnyTgt), assert.True}, - {"no mail, regardless of sender", es.MailSender(NoneTgt), assert.False}, - {"mail from a different sender", es.MailSender("magoo@ma.goo"), assert.False}, - {"mail from the matching sender", es.MailSender(sender), assert.True}, - {"mail with any subject", es.MailSubject(AnyTgt), assert.True}, - {"mail with none subject", es.MailSubject(NoneTgt), assert.False}, - {"mail with a different subject", es.MailSubject("fancy"), assert.False}, - {"mail with the matching subject", es.MailSubject(subject), assert.True}, - {"mail with a substring subject match", es.MailSubject(subject[5:9]), assert.True}, - {"mail received after the epoch", es.MailReceivedAfter(common.FormatTime(epoch)), assert.True}, - {"mail received after now", es.MailReceivedAfter(common.FormatTime(now)), assert.False}, - {"mail received after sometime later", es.MailReceivedAfter(common.FormatTime(future)), assert.False}, - {"mail received before the epoch", es.MailReceivedBefore(common.FormatTime(epoch)), assert.False}, - {"mail received before now", es.MailReceivedBefore(common.FormatTime(now)), assert.False}, - {"mail received before sometime later", es.MailReceivedBefore(common.FormatTime(future)), assert.True}, - {"event with any organizer", es.EventOrganizer(AnyTgt), assert.True}, - {"event with none organizer", es.EventOrganizer(NoneTgt), assert.False}, - {"event with a different organizer", es.EventOrganizer("fancy"), assert.False}, - {"event with the matching organizer", es.EventOrganizer(organizer), assert.True}, - {"event that recurs", es.EventRecurs("true"), assert.True}, - {"event that does not recur", es.EventRecurs("false"), assert.False}, - {"event starting after the epoch", es.EventStartsAfter(common.FormatTime(epoch)), assert.True}, - {"event starting after now", es.EventStartsAfter(common.FormatTime(now)), assert.False}, - {"event starting after sometime later", es.EventStartsAfter(common.FormatTime(future)), assert.False}, - {"event starting before the epoch", es.EventStartsBefore(common.FormatTime(epoch)), assert.False}, - {"event starting before now", es.EventStartsBefore(common.FormatTime(now)), assert.False}, - {"event starting before sometime later", es.EventStartsBefore(common.FormatTime(future)), assert.True}, - {"event with any subject", es.EventSubject(AnyTgt), assert.True}, - {"event with none subject", es.EventSubject(NoneTgt), assert.False}, - {"event with a different subject", es.EventSubject("fancy"), assert.False}, - {"event with the matching subject", es.EventSubject(subject), assert.True}, - {"contact with a different name", es.ContactName("blarps"), assert.False}, - {"contact with the same name", es.ContactName(name), assert.True}, - {"contact with a subname search", es.ContactName(name[2:5]), assert.True}, + {"any mail with a sender", details.ExchangeMail, es.MailSender(AnyTgt), assert.True}, + {"no mail, regardless of sender", details.ExchangeMail, es.MailSender(NoneTgt), assert.False}, + {"mail from a different sender", details.ExchangeMail, es.MailSender("magoo@ma.goo"), assert.False}, + {"mail from the matching sender", details.ExchangeMail, es.MailSender(sender), assert.True}, + {"mail with any subject", details.ExchangeMail, es.MailSubject(AnyTgt), assert.True}, + {"mail with none subject", details.ExchangeMail, es.MailSubject(NoneTgt), assert.False}, + {"mail with a different subject", details.ExchangeMail, es.MailSubject("fancy"), assert.False}, + {"mail with the matching subject", details.ExchangeMail, es.MailSubject(subject), assert.True}, + {"mail with a substring subject match", details.ExchangeMail, es.MailSubject(subject[5:9]), assert.True}, + {"mail received after the epoch", details.ExchangeMail, es.MailReceivedAfter(common.FormatTime(epoch)), assert.True}, + {"mail received after now", details.ExchangeMail, es.MailReceivedAfter(common.FormatTime(now)), assert.False}, + { + "mail received after sometime later", + details.ExchangeMail, + es.MailReceivedAfter(common.FormatTime(future)), + assert.False, + }, + { + "mail received before the epoch", + details.ExchangeMail, + es.MailReceivedBefore(common.FormatTime(epoch)), + assert.False, + }, + {"mail received before now", details.ExchangeMail, es.MailReceivedBefore(common.FormatTime(now)), assert.False}, + { + "mail received before sometime later", + details.ExchangeMail, + es.MailReceivedBefore(common.FormatTime(future)), + assert.True, + }, + {"event with any organizer", details.ExchangeEvent, es.EventOrganizer(AnyTgt), assert.True}, + {"event with none organizer", details.ExchangeEvent, es.EventOrganizer(NoneTgt), assert.False}, + {"event with a different organizer", details.ExchangeEvent, es.EventOrganizer("fancy"), assert.False}, + {"event with the matching organizer", details.ExchangeEvent, es.EventOrganizer(organizer), assert.True}, + {"event that recurs", details.ExchangeEvent, es.EventRecurs("true"), assert.True}, + {"event that does not recur", details.ExchangeEvent, es.EventRecurs("false"), assert.False}, + {"event starting after the epoch", details.ExchangeEvent, es.EventStartsAfter(common.FormatTime(epoch)), assert.True}, + {"event starting after now", details.ExchangeEvent, es.EventStartsAfter(common.FormatTime(now)), assert.False}, + { + "event starting after sometime later", + details.ExchangeEvent, + es.EventStartsAfter(common.FormatTime(future)), + assert.False, + }, + { + "event starting before the epoch", + details.ExchangeEvent, + es.EventStartsBefore(common.FormatTime(epoch)), + assert.False, + }, + {"event starting before now", details.ExchangeEvent, es.EventStartsBefore(common.FormatTime(now)), assert.False}, + { + "event starting before sometime later", + details.ExchangeEvent, + es.EventStartsBefore(common.FormatTime(future)), + assert.True, + }, + {"event with any subject", details.ExchangeEvent, es.EventSubject(AnyTgt), assert.True}, + {"event with none subject", details.ExchangeEvent, es.EventSubject(NoneTgt), assert.False}, + {"event with a different subject", details.ExchangeEvent, es.EventSubject("fancy"), assert.False}, + {"event with the matching subject", details.ExchangeEvent, es.EventSubject(subject), assert.True}, + {"contact with a different name", details.ExchangeContact, es.ContactName("blarps"), assert.False}, + {"contact with the same name", details.ExchangeContact, es.ContactName(name), assert.True}, + {"contact with a subname search", details.ExchangeContact, es.ContactName(name[2:5]), assert.True}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { scopes := setScopesToDefault(test.scope) for _, scope := range scopes { - test.expect(t, scope.matchesInfo(info)) + test.expect(t, scope.matchesInfo(infoWith(test.itype))) } }) } @@ -864,6 +901,12 @@ func (suite *ExchangeSelectorSuite) TestIdPath() { } func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() { + var ( + contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid") + event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid") + mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid") + ) + makeDeets := func(refs ...string) *details.Details { deets := &details.Details{ DetailsModel: details.DetailsModel{ @@ -872,20 +915,30 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() { } for _, r := range refs { + itype := details.UnknownType + + switch r { + case contact: + itype = details.ExchangeContact + case event: + itype = details.ExchangeEvent + case mail: + itype = details.ExchangeMail + } + deets.Entries = append(deets.Entries, details.DetailsEntry{ RepoRef: r, + ItemInfo: details.ItemInfo{ + Exchange: &details.ExchangeInfo{ + ItemType: itype, + }, + }, }) } return deets } - var ( - contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid") - event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid") - mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid") - ) - arr := func(s ...string) []string { return s } @@ -1009,6 +1062,44 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() { }, arr(contact, event), }, + { + "filter on mail subject", + func() *details.Details { + ds := makeDeets(mail) + for i := range ds.Entries { + ds.Entries[i].Exchange.Subject = "has a subject" + } + return ds + }(), + func() *ExchangeRestore { + er := NewExchangeRestore() + er.Include(er.Users(Any())) + er.Filter(er.MailSubject("subj")) + return er + }, + arr(mail), + }, + { + "filter on mail subject multiple input categories", + func() *details.Details { + mds := makeDeets(mail) + for i := range mds.Entries { + mds.Entries[i].Exchange.Subject = "has a subject" + } + + ds := makeDeets(contact, event) + ds.Entries = append(ds.Entries, mds.Entries...) + + return ds + }(), + func() *ExchangeRestore { + er := NewExchangeRestore() + er.Include(er.Users(Any())) + er.Filter(er.MailSubject("subj")) + return er + }, + arr(mail), + }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { @@ -1067,7 +1158,7 @@ func (suite *ExchangeSelectorSuite) TestScopesByCategory() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - result := scopesByCategory[ExchangeScope](test.scopes, cats) + result := scopesByCategory[ExchangeScope](test.scopes, cats, false) assert.Len(t, result[ExchangeContact], test.expect.contact) assert.Len(t, result[ExchangeEvent], test.expect.event) assert.Len(t, result[ExchangeMail], test.expect.mail) @@ -1288,3 +1379,38 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() { }) } } + +func (suite *ExchangeSelectorSuite) TestCategoryFromItemType() { + table := []struct { + name string + input details.ItemType + expect exchangeCategory + }{ + { + name: "contact", + input: details.ExchangeContact, + expect: ExchangeContact, + }, + { + name: "event", + input: details.ExchangeEvent, + expect: ExchangeEvent, + }, + { + name: "mail", + input: details.ExchangeMail, + expect: ExchangeMail, + }, + { + name: "unknown", + input: details.UnknownType, + expect: ExchangeCategoryUnknown, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + result := categoryFromItemType(test.input) + assert.Equal(t, test.expect, result) + }) + } +} diff --git a/src/pkg/selectors/helpers_test.go b/src/pkg/selectors/helpers_test.go index 1fa216c66..f16bff6fb 100644 --- a/src/pkg/selectors/helpers_test.go +++ b/src/pkg/selectors/helpers_test.go @@ -52,7 +52,10 @@ func (mc mockCategorizer) isLeaf() bool { } func (mc mockCategorizer) pathValues(pth path.Path) map[categorizer]string { - return map[categorizer]string{rootCatStub: "stub"} + return map[categorizer]string{ + rootCatStub: "root", + leafCatStub: "leaf", + } } func (mc mockCategorizer) pathKeys() []categorizer { @@ -86,11 +89,7 @@ func (ms mockScope) categorizer() categorizer { return unknownCatStub } -func (ms mockScope) matchesEntry( - cat categorizer, - pathValues map[categorizer]string, - entry details.DetailsEntry, -) bool { +func (ms mockScope) matchesInfo(dii details.ItemInfo) bool { return ms[shouldMatch].Target == "true" } @@ -107,14 +106,27 @@ func stubScope(match string) mockScope { sm = match } + filt := passAny + if match == "none" { + filt = failAny + } + return mockScope{ - rootCatStub.String(): passAny, + rootCatStub.String(): filt, + leafCatStub.String(): filt, scopeKeyCategory: filters.Identity(rootCatStub.String()), scopeKeyDataType: filters.Identity(rootCatStub.String()), shouldMatch: filters.Identity(sm), } } +func stubInfoScope(match string) mockScope { + sc := stubScope(match) + sc[scopeKeyInfoFilter] = filters.Identity("true") + + return sc +} + // --------------------------------------------------------------------------- // selectors // --------------------------------------------------------------------------- diff --git a/src/pkg/selectors/onedrive.go b/src/pkg/selectors/onedrive.go index 8f2583d67..f4275daa5 100644 --- a/src/pkg/selectors/onedrive.go +++ b/src/pkg/selectors/onedrive.go @@ -299,23 +299,14 @@ func (s OneDriveScope) setDefaults() { // no-op while no child scope types below user are identified } -// matchesEntry returns true if either the path or the info in the oneDriveEntry matches the scope details. -func (s OneDriveScope) matchesEntry( - cat categorizer, - pathValues map[categorizer]string, - entry details.DetailsEntry, -) bool { - // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat.(oneDriveCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.OneDrive) -} - // matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo // returns true if the scope and info match for the provided category. -func (s OneDriveScope) matchesInfo(info *details.OneDriveInfo) bool { - // we need values to match against +func (s OneDriveScope) matchesInfo(dii details.ItemInfo) bool { + info := dii.OneDrive if info == nil { return false } + // the scope must define targets to match on filterCat := s.FilterCategory() targets := s.Get(filterCat) diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index 5e1ef1f62..45db46568 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -93,19 +93,14 @@ type ( // internal to scopes.go can utilize the scope's category without the service context. categorizer() categorizer - // matchesEntry is used to determine if the scope values match with either the pathValues, - // or the DetailsEntry for the given category. - // The path comparison (using cat and pathValues) can be handled generically within - // scopes.go. However, the entry comparison requires service-specific context in order - // for the scope to extract the correct serviceInfo in the entry. + // matchesInfo is used to determine if the scope values match a specific DetailsEntry + // ItemInfo filter. Unlike path filtering, the entry comparison requires service-specific + // context in order for the scope to extract the correct serviceInfo in the entry. // // Params: - // cat - the category type expressed in the Path. Not the category of the Scope. If the - // scope does not align with this parameter, the result is automatically false. - // pathValues - the result of categorizer.pathValues() for the Path being checked. - // entry - the details entry containing extended service info for the item that a filter may - // compare. Identification of the correct entry Info service is left up to the scope. - matchesEntry(cat categorizer, pathValues map[categorizer]string, entry details.DetailsEntry) bool + // info - the details entry itemInfo containing extended service info that a filter may + // compare. Identification of the correct entry Info service is left up to the fulfiller. + matchesInfo(info details.ItemInfo) bool // setDefaults populates default values for certain scope categories. // Primarily to ensure that root- or mid-tier scopes (such as folders) @@ -220,9 +215,9 @@ func reduce[T scopeT, C categoryT]( } // aggregate each scope type by category for easier isolation in future processing. - excls := scopesByCategory[T](s.Excludes, dataCategories) - filts := scopesByCategory[T](s.Filters, dataCategories) - incls := scopesByCategory[T](s.Includes, dataCategories) + excls := scopesByCategory[T](s.Excludes, dataCategories, false) + filts := scopesByCategory[T](s.Filters, dataCategories, true) + incls := scopesByCategory[T](s.Includes, dataCategories, false) ents := []details.DetailsEntry{} @@ -262,9 +257,12 @@ func reduce[T scopeT, C categoryT]( // ex: a slice containing the scopes [mail1, mail2, event1] // would produce a map like { mail: [1, 2], event: [1] } // so long as "mail" and "event" are contained in cats. +// For ALL-mach requirements, scopes used as filters should force inclusion using +// includeAll=true, independent of the category. func scopesByCategory[T scopeT, C categoryT]( scopes []scope, cats map[path.CategoryType]C, + includeAll bool, ) map[C][]T { m := map[C][]T{} for _, cat := range cats { @@ -274,7 +272,8 @@ func scopesByCategory[T scopeT, C categoryT]( for _, sc := range scopes { for _, cat := range cats { t := T(sc) - if typeAndCategoryMatches(cat, t.categorizer()) { + // include a scope if the data category matches, or the caller forces inclusion. + if includeAll || typeAndCategoryMatches(cat, t.categorizer()) { m[cat] = append(m[cat], t) } } @@ -285,8 +284,8 @@ func scopesByCategory[T scopeT, C categoryT]( // passes compares each path to the included and excluded exchange scopes. Returns true // if the path is included, passes filters, and not excluded. -func passes[T scopeT]( - cat categorizer, +func passes[T scopeT, C categoryT]( + cat C, pathValues map[categorizer]string, entry details.DetailsEntry, excs, filts, incs []T, @@ -303,7 +302,7 @@ func passes[T scopeT]( var included bool for _, inc := range incs { - if inc.matchesEntry(cat, pathValues, entry) { + if matchesEntry(inc, cat, pathValues, entry) { included = true break } @@ -316,14 +315,14 @@ func passes[T scopeT]( // all filters must pass for _, filt := range filts { - if !filt.matchesEntry(cat, pathValues, entry) { + if !matchesEntry(filt, cat, pathValues, entry) { return false } } // any matching exclusion means failure for _, exc := range excs { - if exc.matchesEntry(cat, pathValues, entry) { + if matchesEntry(exc, cat, pathValues, entry) { return false } } @@ -331,6 +330,22 @@ func passes[T scopeT]( return true } +// matchesEntry determines whether the category and scope require a path +// comparison or an entry info comparison. +func matchesEntry[T scopeT, C categoryT]( + sc T, + cat C, + pathValues map[categorizer]string, + entry details.DetailsEntry, +) bool { + // filterCategory requires matching against service-specific info values + if len(getFilterCategory(sc)) > 0 { + return sc.matchesInfo(entry.ItemInfo) + } + + return matchesPathValues(sc, cat, pathValues, entry.ShortRef) +} + // matchesPathValues will check whether the pathValues have matching entries // in the scope. The keys of the values to match against are identified by // the categorizer. @@ -342,27 +357,25 @@ func matchesPathValues[T scopeT, C categoryT]( pathValues map[categorizer]string, shortRef string, ) bool { - // if scope specifies a filter category, - // path checking is automatically skipped. - if len(getFilterCategory(sc)) > 0 { - return false - } - for _, c := range cat.pathKeys() { scopeVals := getCatValue(sc, c) + // the scope must define the targets to match on if len(scopeVals) == 0 { return false } + // None() fails all matches if scopeVals[0] == NoneTgt { return false } - // the path must contain a value to match against + + // the pathValues must have an entry for the given categorizer pathVal, ok := pathValues[c] if !ok { return false } + // all parts of the scope must match cc := c.(C) if !isAnyTarget(sc, cc) { diff --git a/src/pkg/selectors/scopes_test.go b/src/pkg/selectors/scopes_test.go index 944515751..56f73ea1e 100644 --- a/src/pkg/selectors/scopes_test.go +++ b/src/pkg/selectors/scopes_test.go @@ -103,19 +103,29 @@ func (suite *SelectorScopesSuite) TestContains() { func (suite *SelectorScopesSuite) TestGetCatValue() { t := suite.T() + stub := stubScope("") stub[rootCatStub.String()] = filterize(rootCatStub.String()) + assert.Equal(t, []string{rootCatStub.String()}, getCatValue(stub, rootCatStub)) - assert.Equal(t, None(), getCatValue(stub, leafCatStub)) + assert.Equal(t, + None(), + getCatValue(stub, mockCategorizer("foo"))) } func (suite *SelectorScopesSuite) TestIsAnyTarget() { t := suite.T() stub := stubScope("") assert.True(t, isAnyTarget(stub, rootCatStub)) + assert.True(t, isAnyTarget(stub, leafCatStub)) + assert.False(t, isAnyTarget(stub, mockCategorizer("smarf"))) + + stub = stubScope("none") + assert.False(t, isAnyTarget(stub, rootCatStub)) assert.False(t, isAnyTarget(stub, leafCatStub)) + assert.False(t, isAnyTarget(stub, mockCategorizer("smarf"))) } var reduceTestTable = []struct { @@ -161,7 +171,7 @@ var reduceTestTable = []struct { name: "include all filter none", sel: func() mockSel { sel := stubSelector() - sel.Filters[0] = scope(stubScope("none")) + sel.Filters[0] = scope(stubInfoScope("none")) sel.Excludes = nil return sel }, @@ -257,7 +267,8 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() { []scope{scope(s1), scope(s2)}, map[path.CategoryType]mockCategorizer{ path.UnknownCategory: rootCatStub, - }) + }, + false) assert.Len(t, result, 1) assert.Len(t, result[rootCatStub], 1) assert.Empty(t, result[leafCatStub]) @@ -265,7 +276,8 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() { func (suite *SelectorScopesSuite) TestPasses() { cat := rootCatStub - pathVals := map[categorizer]string{} + pth := stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory) + pathVals := cat.pathValues(pth) entry := details.DetailsEntry{} for _, test := range reduceTestTable {