diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 32436f980..5fb6ccf51 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -316,7 +316,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { emailFolder, event, user) - includeExchangeBackupDetailInfoSelectors( + filterExchangeBackupDetailInfoSelectors( sel, emailReceivedAfter, emailReceivedBefore, @@ -328,7 +328,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { sel.Include(sel.Users(selectors.Any())) } - ds := sel.FilterDetails(d) + ds := sel.Reduce(d) print.Entries(ds.Entries) return nil @@ -389,43 +389,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin sel.Include(sel.Events(users, events)) } -// builds the info-selector inclusions for `backup details exchange` -func includeExchangeBackupDetailInfoSelectors( +// builds the info-selector filters for `backup details exchange` +func filterExchangeBackupDetailInfoSelectors( sel *selectors.ExchangeRestore, emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string, ) { - includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter) - includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore) - includeExchangeInfoMailSender(sel, emailSender) - includeExchangeInfoMailSubject(sel, emailSubject) + filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter) + filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore) + filterExchangeInfoMailSender(sel, emailSender) + filterExchangeInfoMailSubject(sel, emailSubject) } -func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) { +func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) { if len(receivedAfter) == 0 { return } - sel.Include(sel.MailReceivedAfter(receivedAfter)) + sel.Filter(sel.MailReceivedAfter(receivedAfter)) } -func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) { +func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) { if len(receivedBefore) == 0 { return } - sel.Include(sel.MailReceivedBefore(receivedBefore)) + sel.Filter(sel.MailReceivedBefore(receivedBefore)) } -func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) { +func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) { if len(sender) == 0 { return } - sel.Include(sel.MailSender(sender)) + sel.Filter(sel.MailSender(sender)) } -func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) { +func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) { if len(subject) == 0 { return } - sel.Include(sel.MailSubject(subject)) + sel.Filter(sel.MailSubject(subject)) } // checks all flags for correctness and interdependencies diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index dd0088699..7f63715ad 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -480,98 +480,98 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() { } } -func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() { +func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() { stub := []string{"id-stub"} twoStubs := []string{"smarfs", "fnords"} any := []string{utils.Wildcard} table := []struct { name string after, before, sender, subject []string - expectIncludeLen int + expectFilterLen int }{ { - name: "no selectors", - expectIncludeLen: 0, + name: "no selectors", + expectFilterLen: 0, }, { - name: "any receivedAfter", - after: any, - expectIncludeLen: 1, + name: "any receivedAfter", + after: any, + expectFilterLen: 1, }, { - name: "single receivedAfter", - after: stub, - expectIncludeLen: 1, + name: "single receivedAfter", + after: stub, + expectFilterLen: 1, }, { - name: "multiple receivedAfter", - after: twoStubs, - expectIncludeLen: 1, + name: "multiple receivedAfter", + after: twoStubs, + expectFilterLen: 1, }, { - name: "any receivedBefore", - before: any, - expectIncludeLen: 1, + name: "any receivedBefore", + before: any, + expectFilterLen: 1, }, { - name: "single receivedBefore", - before: stub, - expectIncludeLen: 1, + name: "single receivedBefore", + before: stub, + expectFilterLen: 1, }, { - name: "multiple receivedBefore", - before: twoStubs, - expectIncludeLen: 1, + name: "multiple receivedBefore", + before: twoStubs, + expectFilterLen: 1, }, { - name: "any sender", - sender: any, - expectIncludeLen: 1, + name: "any sender", + sender: any, + expectFilterLen: 1, }, { - name: "single sender", - sender: stub, - expectIncludeLen: 1, + name: "single sender", + sender: stub, + expectFilterLen: 1, }, { - name: "multiple senders", - sender: twoStubs, - expectIncludeLen: 1, + name: "multiple senders", + sender: twoStubs, + expectFilterLen: 1, }, { - name: "any subject", - subject: any, - expectIncludeLen: 1, + name: "any subject", + subject: any, + expectFilterLen: 1, }, { - name: "single subject", - subject: stub, - expectIncludeLen: 1, + name: "single subject", + subject: stub, + expectFilterLen: 1, }, { - name: "multiple subjects", - subject: twoStubs, - expectIncludeLen: 1, + name: "multiple subjects", + subject: twoStubs, + expectFilterLen: 1, }, { - name: "one of each", - after: stub, - before: stub, - sender: stub, - subject: stub, - expectIncludeLen: 4, + name: "one of each", + after: stub, + before: stub, + sender: stub, + subject: stub, + expectFilterLen: 4, }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { sel := selectors.NewExchangeRestore() - includeExchangeBackupDetailInfoSelectors( + filterExchangeBackupDetailInfoSelectors( sel, test.after, test.before, test.sender, test.subject) - assert.Equal(t, test.expectIncludeLen, len(sel.Includes)) + assert.Equal(t, test.expectFilterLen, len(sel.Filters)) }) } } diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index 01d959ff7..4bad24049 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -138,7 +138,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error { emailFolder, event, user) - includeExchangeRestoreInfoSelectors( + filterExchangeRestoreInfoSelectors( sel, emailReceivedAfter, emailReceivedBefore, @@ -218,43 +218,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin sel.Include(sel.Events(users, events)) } -// builds the info-selector inclusions for `restore exchange` -func includeExchangeRestoreInfoSelectors( +// builds the info-selector filters for `restore exchange` +func filterExchangeRestoreInfoSelectors( sel *selectors.ExchangeRestore, emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string, ) { - includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter) - includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore) - includeExchangeInfoMailSender(sel, emailSender) - includeExchangeInfoMailSubject(sel, emailSubject) + filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter) + filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore) + filterExchangeInfoMailSender(sel, emailSender) + filterExchangeInfoMailSubject(sel, emailSubject) } -func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) { +func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) { if len(receivedAfter) == 0 { return } - sel.Include(sel.MailReceivedAfter(receivedAfter)) + sel.Filter(sel.MailReceivedAfter(receivedAfter)) } -func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) { +func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) { if len(receivedBefore) == 0 { return } - sel.Include(sel.MailReceivedBefore(receivedBefore)) + sel.Filter(sel.MailReceivedBefore(receivedBefore)) } -func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) { +func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) { if len(sender) == 0 { return } - sel.Include(sel.MailSender(sender)) + sel.Filter(sel.MailSender(sender)) } -func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) { +func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) { if len(subject) == 0 { return } - sel.Include(sel.MailSubject(subject)) + sel.Filter(sel.MailSubject(subject)) } // checks all flags for correctness and interdependencies diff --git a/src/cli/restore/exchange_test.go b/src/cli/restore/exchange_test.go index 75a45d733..75c85c232 100644 --- a/src/cli/restore/exchange_test.go +++ b/src/cli/restore/exchange_test.go @@ -316,98 +316,98 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() { } } -func (suite *ExchangeSuite) TestIncludeExchangeRestoreInfoSelectors() { +func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() { stub := []string{"id-stub"} twoStubs := []string{"a-stub", "b-stub"} any := []string{utils.Wildcard} table := []struct { name string after, before, sender, subject []string - expectIncludeLen int + expectFilterLen int }{ { - name: "no selectors", - expectIncludeLen: 0, + name: "no selectors", + expectFilterLen: 0, }, { - name: "any receivedAfter", - after: any, - expectIncludeLen: 1, + name: "any receivedAfter", + after: any, + expectFilterLen: 1, }, { - name: "single receivedAfter", - after: stub, - expectIncludeLen: 1, + name: "single receivedAfter", + after: stub, + expectFilterLen: 1, }, { - name: "multiple receivedAfter", - after: twoStubs, - expectIncludeLen: 1, + name: "multiple receivedAfter", + after: twoStubs, + expectFilterLen: 1, }, { - name: "any receivedBefore", - before: any, - expectIncludeLen: 1, + name: "any receivedBefore", + before: any, + expectFilterLen: 1, }, { - name: "single receivedBefore", - before: stub, - expectIncludeLen: 1, + name: "single receivedBefore", + before: stub, + expectFilterLen: 1, }, { - name: "multiple receivedBefore", - before: twoStubs, - expectIncludeLen: 1, + name: "multiple receivedBefore", + before: twoStubs, + expectFilterLen: 1, }, { - name: "any senders", - sender: any, - expectIncludeLen: 1, + name: "any senders", + sender: any, + expectFilterLen: 1, }, { - name: "single sender", - sender: stub, - expectIncludeLen: 1, + name: "single sender", + sender: stub, + expectFilterLen: 1, }, { - name: "multiple senders", - sender: twoStubs, - expectIncludeLen: 1, + name: "multiple senders", + sender: twoStubs, + expectFilterLen: 1, }, { - name: "any subjects", - subject: any, - expectIncludeLen: 1, + name: "any subjects", + subject: any, + expectFilterLen: 1, }, { - name: "single subject", - subject: stub, - expectIncludeLen: 1, + name: "single subject", + subject: stub, + expectFilterLen: 1, }, { - name: "multiple subjects", - subject: twoStubs, - expectIncludeLen: 1, + name: "multiple subjects", + subject: twoStubs, + expectFilterLen: 1, }, { - name: "one of each", - after: stub, - before: stub, - sender: stub, - subject: stub, - expectIncludeLen: 4, + name: "one of each", + after: stub, + before: stub, + sender: stub, + subject: stub, + expectFilterLen: 4, }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { sel := selectors.NewExchangeRestore() - includeExchangeRestoreInfoSelectors( + filterExchangeRestoreInfoSelectors( sel, test.after, test.before, test.sender, test.subject) - assert.Equal(t, test.expectIncludeLen, len(sel.Includes)) + assert.Equal(t, test.expectFilterLen, len(sel.Filters)) }) } } diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 9aa568022..fcfb6bacc 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -95,7 +95,7 @@ func (op *RestoreOperation) Run(ctx context.Context) error { } // format the details and retrieve the items from kopia - fds := er.FilterDetails(d) + fds := er.Reduce(d) // todo: use path pkg for this fdsPaths := fds.Paths() paths := make([][]string, len(fdsPaths)) diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index ccbb61b90..65f77d752 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -76,53 +76,63 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) { // ------------------- // Exclude/Includes -// Include appends the provided scopes to the selector's inclusion set. -// -// All parts of the scope must match for data to be included. -// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1, -// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard -// a scope value. No value will match if selectors.None() is provided. -// -// Group-level scopes will automatically apply the Any() wildcard to child -// properties. -// ex: User(u1) is the same as Mail(u1, Any(), Any()). -func (s *exchange) Include(scopes ...[]ExchangeScope) { - if s.Includes == nil { - s.Includes = []map[string]string{} - } - concat := []ExchangeScope{} - for _, scopeSl := range scopes { - concat = append(concat, extendExchangeScopeValues(scopeSl)...) - } - for _, sc := range concat { - s.Includes = append(s.Includes, map[string]string(sc)) - } -} - // Exclude appends the provided scopes to the selector's exclusion set. // Every Exclusion scope applies globally, affecting all inclusion scopes. // -// All parts of the scope must match for data to be excluded. -// Ex: Mail(u1, f1, m1) => only excludes mail that is owned by user u1, -// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard -// a scope value. No value will match if selectors.None() is provided. +// All parts of the scope must match for data to be exclucded. +// Ex: Mail(u1, f1, m1) => only excludes mail if it is owned by user u1, +// located in folder f1, and ID'd as m1. MailSender(foo) => only excludes +// mail whose sender is foo. Use selectors.Any() to wildcard a scope value. +// No value will match if selectors.None() is provided. // // Group-level scopes will automatically apply the Any() wildcard to // child properties. -// ex: User(u1) automatically includes all mail, events, and contacts, +// ex: User(u1) automatically cascades to all mail, events, and contacts, // therefore it is the same as selecting all of the following: // Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any()) func (s *exchange) Exclude(scopes ...[]ExchangeScope) { - if s.Excludes == nil { - s.Excludes = []map[string]string{} - } - concat := []ExchangeScope{} - for _, scopeSl := range scopes { - concat = append(concat, extendExchangeScopeValues(scopeSl)...) - } - for _, sc := range concat { - s.Excludes = append(s.Excludes, map[string]string(sc)) - } + appendExcludes(&s.Selector, extendExchangeScopeValues, scopes...) +} + +// Filter appends the provided scopes to the selector's filters set. +// A selector with >0 filters and 0 inclusions will include any data +// that passes all filters. +// A selector with >0 filters and >0 inclusions will reduce the +// inclusion set to only the data that passes all filters. +// +// All parts of the scope must match for data to pass the filter. +// Ex: Mail(u1, f1, m1) => only passes mail that is owned by user u1, +// located in folder f1, and ID'd as m1. MailSender(foo) => only passes +// mail whose sender is foo. Use selectors.Any() to wildcard a scope value. +// No value will match if selectors.None() is provided. +// +// Group-level scopes will automatically apply the Any() wildcard to +// child properties. +// ex: User(u1) automatically cascades to all mail, events, and contacts, +// therefore it is the same as selecting all of the following: +// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any()) +func (s *exchange) Filter(scopes ...[]ExchangeScope) { + appendFilters(&s.Selector, extendExchangeScopeValues, scopes...) +} + +// Include appends the provided scopes to the selector's inclusion set. +// Data is included if it matches ANY inclusion. +// The inclusion set is later filtered (all included data must pass ALL +// filters) and excluded (all included data must not match ANY exclusion). +// +// All parts of the scope must match for data to be included. +// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1, +// located in folder f1, and ID'd as m1. MailSender(foo) => only includes +// mail whose sender is foo. Use selectors.Any() to wildcard a scope value. +// No value will match if selectors.None() is provided. +// +// Group-level scopes will automatically apply the Any() wildcard to +// child properties. +// ex: User(u1) automatically cascades to all mail, events, and contacts, +// therefore it is the same as selecting all of the following: +// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any()) +func (s *exchange) Include(scopes ...[]ExchangeScope) { + appendIncludes(&s.Selector, extendExchangeScopeValues, scopes...) } // completes population for certain scope properties, according to the @@ -282,9 +292,8 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha } } -// Produces one or more exchange contact info filter scopes. +// Produces one or more exchange mail received-after filter scopes. // Matches any mail which was received after the timestring. -// One scope is created per timeString entry. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] @@ -294,9 +303,8 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings []string) []ExchangeSco } } -// Produces one or more exchange mail subject filter scopes. +// Produces one or more exchange mail received-before filter scopes. // Matches any mail whose mail subject contains one of the provided strings. -// One scope is created per subject entry. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] @@ -306,9 +314,8 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings []string) []ExchangeSc } } -// Produces one or more exchange mail received-after filter scopes. +// Produces one or more exchange mail sender filter scopes. // Matches any mail which was received after the timestring. -// One scope is created per timeString entry. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] @@ -318,7 +325,7 @@ func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope { } } -// Produces one or more exchange mail received-before filter scopes. +// Produces one or more exchange mail subject line filter scopes. // Matches any mail which was received before the timestring. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] @@ -627,15 +634,16 @@ func exchangeIDPath(cat exchangeCategory, path []string) map[exchangeCategory]st return m } -// FilterDetails reduces the entries in a backupDetails struct to only -// those that match the inclusions and exclusions in the selector. -func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details { +// Reduce reduces the entries in a backupDetails struct to only +// those that match the inclusions, filters, and exclusions in the selector. +func (s *ExchangeRestore) Reduce(deets *backup.Details) *backup.Details { if deets == nil { return nil } - entIncs := exchangeScopesByCategory(s.Includes) entExcs := exchangeScopesByCategory(s.Excludes) + entFilt := exchangeScopesByCategory(s.Filters) + entIncs := exchangeScopesByCategory(s.Includes) ents := []backup.DetailsEntry{} @@ -660,8 +668,9 @@ func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details { cat, path, ent.Exchange, - entIncs[cat.String()], - entExcs[cat.String()]) + entExcs[cat.String()], + entFilt[cat.String()], + entIncs[cat.String()]) if matched { ents = append(ents, ent) } @@ -695,31 +704,47 @@ func exchangeScopesByCategory(scopes []map[string]string) map[string][]ExchangeS } // compare each path to the included and excluded exchange scopes. Returns true -// if the path is included, and not excluded. +// if the path is included, passes filters, and not excluded. func matchExchangeEntry( cat exchangeCategory, path []string, info *backup.ExchangeInfo, - incs, excs []ExchangeScope, + excs, filts, incs []ExchangeScope, ) bool { - var included bool - for _, inc := range incs { - if inc.matches(cat, path, info) { - included = true - break - } - } - if !included { + // a passing match requires either a filter or an inclusion + if len(incs)+len(filts) == 0 { return false } - var excluded bool - for _, exc := range excs { - if exc.matches(cat, path, info) { - excluded = true - break + // skip this check if 0 inclusions were populated + // since filters act as the inclusion check in that case + if len(incs) > 0 { + // at least one inclusion must apply. + var included bool + for _, inc := range incs { + if inc.matches(cat, path, info) { + included = true + break + } + } + if !included { + return false } } - return !excluded + // all filters must pass + for _, filt := range filts { + if !filt.matches(cat, path, info) { + return false + } + } + + // any matching exclusion means failure + for _, exc := range excs { + if exc.matches(cat, path, info) { + return false + } + } + + return true } diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index 44ab504ab..533ca1dc1 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -488,7 +488,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() { } } -func (suite *ExchangeSourceSuite) TestExchangeScope_Include_MatchesInfo() { +func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesInfo() { es := NewExchangeRestore() const ( sender = "smarf@2many.cooks" @@ -628,7 +628,7 @@ func (suite *ExchangeSourceSuite) TestIdPath() { } } -func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { +func (suite *ExchangeSourceSuite) TestExchangeRestore_Reduce() { makeDeets := func(refs ...string) *backup.Details { deets := &backup.Details{ DetailsModel: backup.DetailsModel{ @@ -773,7 +773,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { sel := test.makeSelector() - results := sel.FilterDetails(test.deets) + results := sel.Reduce(test.deets) paths := results.Paths() assert.Equal(t, test.expect, paths) }) @@ -825,44 +825,48 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { } func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() { - var TODO_EXCHANGE_INFO *backup.ExchangeInfo + var exchangeInfo *backup.ExchangeInfo const ( - mail = "mailID" - cat = ExchangeMail + mid = "mailID" + cat = ExchangeMail ) var ( - es = NewExchangeRestore() - inAny = extendExchangeScopeValues(es.Users(Any())) - inNone = extendExchangeScopeValues(es.Users(None())) - inMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail})) - inOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"})) - exAny = extendExchangeScopeValues(es.Users(Any())) - exNone = extendExchangeScopeValues(es.Users(None())) - exMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail})) - exOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"})) - path = []string{"tid", "user", "mail", "folder", mail} + es = NewExchangeRestore() + anyUser = extendExchangeScopeValues(es.Users(Any())) + noUser = extendExchangeScopeValues(es.Users(None())) + mail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mid})) + otherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"})) + noMail = extendExchangeScopeValues(es.Mails(Any(), Any(), None())) + path = []string{"tid", "user", "mail", "folder", mid} ) table := []struct { - name string - includes []ExchangeScope - excludes []ExchangeScope - expect assert.BoolAssertionFunc + name string + excludes, filters, includes []ExchangeScope + expect assert.BoolAssertionFunc }{ - {"empty", nil, nil, assert.False}, - {"in all", inAny, nil, assert.True}, - {"in None", inNone, nil, assert.False}, - {"in Mail", inMail, nil, assert.True}, - {"in Other", inOtherMail, nil, assert.False}, - {"ex all", inAny, exAny, assert.False}, - {"ex None", inAny, exNone, assert.True}, - {"in Mail", inAny, exMail, assert.False}, - {"in Other", inAny, exOtherMail, assert.True}, - {"in and ex Mail", inMail, exMail, assert.False}, + {"empty", nil, nil, nil, assert.False}, + {"in Any", nil, nil, anyUser, assert.True}, + {"in None", nil, nil, noUser, assert.False}, + {"in Mail", nil, nil, mail, assert.True}, + {"in Other", nil, nil, otherMail, assert.False}, + {"in no Mail", nil, nil, noMail, assert.False}, + {"ex Any", anyUser, nil, anyUser, assert.False}, + {"ex Any filter", anyUser, anyUser, nil, assert.False}, + {"ex None", noUser, nil, anyUser, assert.True}, + {"ex None filter mail", noUser, mail, nil, assert.True}, + {"ex None filter any user", noUser, anyUser, nil, assert.False}, + {"ex Mail", mail, nil, anyUser, assert.False}, + {"ex Other", otherMail, nil, anyUser, assert.True}, + {"in and ex Mail", mail, nil, mail, assert.False}, + {"filter Any", nil, anyUser, nil, assert.False}, + {"filter None", nil, noUser, anyUser, assert.False}, + {"filter Mail", nil, mail, anyUser, assert.True}, + {"filter Other", nil, otherMail, anyUser, assert.False}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - test.expect(t, matchExchangeEntry(cat, path, TODO_EXCHANGE_INFO, test.includes, test.excludes)) + test.expect(t, matchExchangeEntry(cat, path, exchangeInfo, test.excludes, test.filters, test.includes)) }) } } diff --git a/src/pkg/selectors/selectors.go b/src/pkg/selectors/selectors.go index 73ea6628b..552d11e56 100644 --- a/src/pkg/selectors/selectors.go +++ b/src/pkg/selectors/selectors.go @@ -55,8 +55,9 @@ const ( // Is only used to pass along more specific selector instances. type Selector struct { Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc. - Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusions. Each exclusion applies to all inclusions. - Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusions. Expected to get cast to a service wrapper within each service handler. + Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusion scopes. Exclusions apply globally to all inclusions/filters, with any-match behavior. + Filters []map[string]string `json:"filters,omitempty"` // A slice of filter scopes. All inclusions must also match ALL filters. + Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusion scopes. Comparators must match either one of these, or all filters, to be included. } // helper for specific selector instance constructors. @@ -81,6 +82,61 @@ func None() []string { return []string{NoneTgt} } +type baseScope interface { + ~map[string]string +} + +func appendExcludes[T baseScope]( + s *Selector, + tform func([]T) []T, + scopes ...[]T, +) { + if s.Excludes == nil { + s.Excludes = []map[string]string{} + } + concat := []T{} + for _, scopeSl := range scopes { + concat = append(concat, tform(scopeSl)...) + } + for _, sc := range concat { + s.Excludes = append(s.Excludes, map[string]string(sc)) + } +} + +func appendFilters[T baseScope]( + s *Selector, + tform func([]T) []T, + scopes ...[]T, +) { + if s.Filters == nil { + s.Filters = []map[string]string{} + } + concat := []T{} + for _, scopeSl := range scopes { + concat = append(concat, tform(scopeSl)...) + } + for _, sc := range concat { + s.Filters = append(s.Filters, map[string]string(sc)) + } +} + +func appendIncludes[T baseScope]( + s *Selector, + tform func([]T) []T, + scopes ...[]T, +) { + if s.Includes == nil { + s.Includes = []map[string]string{} + } + concat := []T{} + for _, scopeSl := range scopes { + concat = append(concat, tform(scopeSl)...) + } + for _, sc := range concat { + s.Includes = append(s.Includes, map[string]string(sc)) + } +} + // --------------------------------------------------------------------------- // Destination // ---------------------------------------------------------------------------