From 746c88a2338073c0fbd87f86a55023fb35624bf0 Mon Sep 17 00:00:00 2001 From: Keepers <104464746+ryanfkeepers@users.noreply.github.com> Date: Tue, 19 Jul 2022 16:04:07 -0600 Subject: [PATCH] refactor selector scopes to accept slices (#352) * refactor selector scopes to accept slices Cli flag implementation was showcasing a toil issue: building selectors required a lot of repetitious code for combining inputs into sets of scopes. Since all of these productions were effectively identical (eg: for each user, then each folder, create a scope with the ids), the cleaner solution is to pack that behavior into the scope constructors themselves. --- src/cli/backup/exchange.go | 34 +-- src/cli/restore/exchange.go | 19 +- src/internal/connector/graph_connector.go | 2 +- .../connector/graph_connector_test.go | 2 +- src/internal/operations/backup_test.go | 2 +- src/internal/operations/restore_test.go | 4 +- src/pkg/selectors/exchange.go | 194 +++++++++---- src/pkg/selectors/exchange_test.go | 255 +++++++++--------- src/pkg/selectors/selectors.go | 44 ++- 9 files changed, 327 insertions(+), 229 deletions(-) diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 6621c8464..0ff79e9ea 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -118,42 +118,22 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error { func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector { sel := selectors.NewExchangeBackup() if all { - sel.Include(sel.Users(selectors.All)) + sel.Include(sel.Users(selectors.All())) return sel.Selector } if len(data) == 0 { - for _, user := range users { - if user == utils.Wildcard { - user = selectors.All - } - sel.Include(sel.ContactFolders(user, selectors.All)) - sel.Include(sel.MailFolders(user, selectors.All)) - sel.Include(sel.Events(user, selectors.All)) - } + sel.Include(sel.ContactFolders(user, selectors.All())) + sel.Include(sel.MailFolders(user, selectors.All())) + sel.Include(sel.Events(user, selectors.All())) } for _, d := range data { switch d { case dataContacts: - for _, user := range users { - if user == utils.Wildcard { - user = selectors.All - } - sel.Include(sel.ContactFolders(user, selectors.All)) - } + sel.Include(sel.ContactFolders(users, selectors.All())) case dataEmail: - for _, user := range users { - if user == utils.Wildcard { - user = selectors.All - } - sel.Include(sel.MailFolders(user, selectors.All)) - } + sel.Include(sel.MailFolders(users, selectors.All())) case dataEvents: - for _, user := range users { - if user == utils.Wildcard { - user = selectors.All - } - sel.Include(sel.Events(user, selectors.All)) - } + sel.Include(sel.Events(users, selectors.All())) } } return sel.Selector diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index b558cdd1d..dc2dfa72f 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -103,23 +103,18 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error { func exchangeRestoreSelectors(u, f, m string) selectors.Selector { sel := selectors.NewExchangeRestore() - if u == "*" { - u = selectors.All - } - if f == "*" { - f = selectors.All - } - if m == "*" { - m = selectors.All - } if len(m) > 0 { - sel.Include(sel.Mails(u, f, m)) + sel.Include(sel.Mails( + []string{u}, []string{f}, []string{m}, + )) } if len(f) > 0 && len(m) == 0 { - sel.Include(sel.MailFolders(u, f)) + sel.Include(sel.MailFolders( + []string{u}, []string{f}, + )) } if len(f) == 0 && len(m) == 0 { - sel.Include(sel.Users(u)) + sel.Include(sel.Users([]string{u})) } return sel.Selector } diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index f82b3e810..c324722d5 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -187,7 +187,7 @@ func (gc *GraphConnector) ExchangeDataCollection(ctx context.Context, selector s // TODO: handle "get mail for all users" // this would probably no-op without this check, // but we want it made obvious that we're punting. - if user == selectors.All { + if user == selectors.AllTgt { errs = support.WrapAndAppend( "all-users", errors.New("all users selector currently not handled"), diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index d14fc6732..70472f0be 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -59,7 +59,7 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_setTenantUsers() func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_ExchangeDataCollection() { sel := selectors.NewExchangeBackup() - sel.Include(sel.Users("meganb@8qzvrj.onmicrosoft.com")) + sel.Include(sel.Users([]string{"lidiah@8qzvrj.onmicrosoft.com"})) collectionList, err := suite.connector.ExchangeDataCollection(context.Background(), sel.Selector) assert.NotNil(suite.T(), collectionList, "collection list") assert.Nil(suite.T(), err) diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index 43fa4c093..4a100544f 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -158,7 +158,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() { sw := store.NewKopiaStore(ms) sel := selectors.NewExchangeBackup() - sel.Include(sel.Users(m365User)) + sel.Include(sel.Users([]string{m365User})) bo, err := NewBackupOperation( ctx, diff --git a/src/internal/operations/restore_test.go b/src/internal/operations/restore_test.go index da28c159d..7800834a5 100644 --- a/src/internal/operations/restore_test.go +++ b/src/internal/operations/restore_test.go @@ -150,7 +150,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { sw := store.NewKopiaStore(ms) bsel := selectors.NewExchangeBackup() - bsel.Include(bsel.Users(m365User)) + bsel.Include(bsel.Users([]string{m365User})) bo, err := NewBackupOperation( ctx, @@ -164,7 +164,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { require.NotEmpty(t, bo.Results.BackupID) rsel := selectors.NewExchangeRestore() - rsel.Include(rsel.Users(m365User)) + rsel.Include(rsel.Users([]string{m365User})) ro, err := NewRestoreOperation( ctx, diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 911b34a14..8d628ea72 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -76,102 +76,180 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) { // Exclude/Includes // Include appends the provided scopes to the selector's inclusion set. -func (s *exchange) Include(scopes ...exchangeScope) { +func (s *exchange) Include(scopes ...[]exchangeScope) { if s.Includes == nil { s.Includes = []map[string]string{} } - for _, sc := range scopes { - sc = extendExchangeScopeValues(All, sc) + concat := []exchangeScope{} + for _, scopeSl := range scopes { + concat = append(concat, extendExchangeScopeValues(All(), 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. -func (s *exchange) Exclude(scopes ...exchangeScope) { +func (s *exchange) Exclude(scopes ...[]exchangeScope) { if s.Excludes == nil { s.Excludes = []map[string]string{} } - for _, sc := range scopes { - sc = extendExchangeScopeValues(None, sc) + concat := []exchangeScope{} + for _, scopeSl := range scopes { + concat = append(concat, extendExchangeScopeValues(None(), scopeSl)...) + } + for _, sc := range concat { s.Excludes = append(s.Excludes, map[string]string(sc)) } } // completes population for certain scope properties, according to the // expecations of Include and Exclude behavior. -func extendExchangeScopeValues(v string, sc exchangeScope) exchangeScope { - switch sc.Category() { - case ExchangeContactFolder: - sc[ExchangeContact.String()] = v - case ExchangeMailFolder: - sc[ExchangeMail.String()] = v - case ExchangeUser: - sc[ExchangeContactFolder.String()] = v - sc[ExchangeContact.String()] = v - sc[ExchangeEvent.String()] = v - sc[ExchangeMailFolder.String()] = v - sc[ExchangeMail.String()] = v +func extendExchangeScopeValues(v []string, es []exchangeScope) []exchangeScope { + vv := join(v...) + for i := range es { + switch es[i].Category() { + case ExchangeContactFolder: + es[i][ExchangeContact.String()] = vv + case ExchangeMailFolder: + es[i][ExchangeMail.String()] = vv + case ExchangeUser: + es[i][ExchangeContactFolder.String()] = vv + es[i][ExchangeContact.String()] = vv + es[i][ExchangeEvent.String()] = vv + es[i][ExchangeMailFolder.String()] = vv + es[i][ExchangeMail.String()] = vv + } } - return sc + return es } // ------------------- // Scope Factory -func (s *exchange) Contacts(u, f string, vs ...string) exchangeScope { - return exchangeScope{ - scopeKeyGranularity: Item, - scopeKeyCategory: ExchangeContact.String(), - ExchangeUser.String(): u, - ExchangeContactFolder.String(): f, - ExchangeContact.String(): join(vs...), +// Produces one or more exchange contact scopes. +// One scope is created per combination of users,folders,contacts. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) Contacts(users, folders, contacts []string) []exchangeScope { + users = normalize(users) + folders = normalize(folders) + contacts = normalize(contacts) + scopes := []exchangeScope{} + for _, u := range users { + for _, f := range folders { + scopes = append(scopes, exchangeScope{ + scopeKeyGranularity: Item, + scopeKeyCategory: ExchangeContact.String(), + ExchangeUser.String(): u, + ExchangeContactFolder.String(): f, + ExchangeContact.String(): join(contacts...), + }) + } } + return scopes } -func (s *exchange) ContactFolders(u string, vs ...string) exchangeScope { - return exchangeScope{ - scopeKeyGranularity: Group, - scopeKeyCategory: ExchangeContactFolder.String(), - ExchangeUser.String(): u, - ExchangeContactFolder.String(): join(vs...), +// Produces one or more exchange contact folder scopes. +// One scope is created per combination of users,folders. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) ContactFolders(users, folders []string) []exchangeScope { + users = normalize(users) + folders = normalize(folders) + scopes := []exchangeScope{} + for _, u := range users { + scopes = append(scopes, exchangeScope{ + scopeKeyGranularity: Group, + scopeKeyCategory: ExchangeContactFolder.String(), + ExchangeUser.String(): u, + ExchangeContactFolder.String(): join(folders...), + }) } + return scopes } -func (s *exchange) Events(u string, vs ...string) map[string]string { - return map[string]string{ - scopeKeyGranularity: Item, - scopeKeyCategory: ExchangeEvent.String(), - ExchangeUser.String(): u, - ExchangeEvent.String(): join(vs...), +// Produces one or more exchange event scopes. +// One scope is created per combination of users,events. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) Events(users, events []string) []exchangeScope { + users = normalize(users) + events = normalize(events) + scopes := []exchangeScope{} + for _, u := range users { + scopes = append(scopes, exchangeScope{ + scopeKeyGranularity: Item, + scopeKeyCategory: ExchangeEvent.String(), + ExchangeUser.String(): u, + ExchangeEvent.String(): join(events...), + }) } + return scopes } -func (s *exchange) Mails(u, f string, vs ...string) map[string]string { - return map[string]string{ - scopeKeyGranularity: Item, - scopeKeyCategory: ExchangeMail.String(), - ExchangeUser.String(): u, - ExchangeMailFolder.String(): f, - ExchangeMail.String(): join(vs...), +// Produces one or more mail scopes. +// One scope is created per combination of users,folders,mails. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) Mails(users, folders, mails []string) []exchangeScope { + users = normalize(users) + folders = normalize(folders) + mails = normalize(mails) + scopes := []exchangeScope{} + for _, u := range users { + for _, f := range folders { + scopes = append(scopes, exchangeScope{ + scopeKeyGranularity: Item, + scopeKeyCategory: ExchangeMail.String(), + ExchangeUser.String(): u, + ExchangeMailFolder.String(): f, + ExchangeMail.String(): join(mails...), + }) + } } + return scopes } -func (s *exchange) MailFolders(u string, vs ...string) map[string]string { - return map[string]string{ - scopeKeyGranularity: Group, - scopeKeyCategory: ExchangeMailFolder.String(), - ExchangeUser.String(): u, - ExchangeMailFolder.String(): join(vs...), +// Produces one or more exchange mail folder scopes. +// One scope is created per combination of users,folders. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) MailFolders(users, folders []string) []exchangeScope { + users = normalize(users) + folders = normalize(folders) + scopes := []exchangeScope{} + for _, u := range users { + scopes = append(scopes, exchangeScope{ + scopeKeyGranularity: Group, + scopeKeyCategory: ExchangeMailFolder.String(), + ExchangeUser.String(): u, + ExchangeMailFolder.String(): join(folders...), + }) } + return scopes } -func (s *exchange) Users(vs ...string) map[string]string { - return map[string]string{ +// Produces one or more exchange contact user scopes. +// One scope is created per user entry. +// If any slice contains selectors.All, that slice is reduced to [selectors.All] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *exchange) Users(users []string) []exchangeScope { + users = normalize(users) + scopes := []exchangeScope{} + scopes = append(scopes, exchangeScope{ scopeKeyGranularity: Group, scopeKeyCategory: ExchangeUser.String(), - ExchangeUser.String(): join(vs...), - } + ExchangeUser.String(): join(users...), + }) + return scopes } // --------------------------------------------------------------------------- @@ -300,7 +378,7 @@ func (s exchangeScope) IncludesCategory(cat exchangeCategory) bool { func (s exchangeScope) Get(cat exchangeCategory) []string { v, ok := s[cat.String()] if !ok { - return []string{None} + return None() } return split(v) } @@ -323,7 +401,7 @@ func (s exchangeScope) includesPath(cat exchangeCategory, path []string) bool { if !ok { return false } - if target[0] != All && !contains(target, id) { + if target[0] != AllTgt && !contains(target, id) { return false } } @@ -342,7 +420,7 @@ func (s exchangeScope) excludesPath(cat exchangeCategory, path []string) bool { if !ok { return true } - if target[0] == All || contains(target, id) { + if target[0] == AllTgt || contains(target, id) { return true } } diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index a6c2f8f66..d5b838459 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -58,12 +58,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Contacts() { const ( user = "user" - folder = All + folder = AllTgt c1 = "c1" c2 = "c2" ) - sel.Exclude(sel.Contacts(user, folder, c1, c2)) + sel.Exclude(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) @@ -79,12 +79,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Contacts() { const ( user = "user" - folder = All + folder = AllTgt c1 = "c1" c2 = "c2" ) - sel.Include(sel.Contacts(user, folder, c1, c2)) + sel.Include(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) @@ -106,14 +106,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_ContactFolders() f2 = "f2" ) - sel.Exclude(sel.ContactFolders(user, f1, f2)) + sel.Exclude(sel.ContactFolders([]string{user}, []string{f1, f2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) - assert.Equal(t, scope[ExchangeContact.String()], None) + assert.Equal(t, scope[ExchangeContact.String()], NoneTgt) } func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders() { @@ -126,14 +126,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders() f2 = "f2" ) - sel.Include(sel.ContactFolders(user, f1, f2)) + sel.Include(sel.ContactFolders([]string{user}, []string{f1, f2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) - assert.Equal(t, scope[ExchangeContact.String()], All) + assert.Equal(t, scope[ExchangeContact.String()], AllTgt) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder) } @@ -148,7 +148,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Events() { e2 = "e2" ) - sel.Exclude(sel.Events(user, e1, e2)) + sel.Exclude(sel.Events([]string{user}, []string{e1, e2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) @@ -167,7 +167,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Events() { e2 = "e2" ) - sel.Include(sel.Events(user, e1, e2)) + sel.Include(sel.Events([]string{user}, []string{e1, e2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) @@ -184,12 +184,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Mails() { const ( user = "user" - folder = All + folder = AllTgt m1 = "m1" m2 = "m2" ) - sel.Exclude(sel.Mails(user, folder, m1, m2)) + sel.Exclude(sel.Mails([]string{user}, []string{folder}, []string{m1, m2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) @@ -205,12 +205,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Mails() { const ( user = "user" - folder = All + folder = AllTgt m1 = "m1" m2 = "m2" ) - sel.Include(sel.Mails(user, folder, m1, m2)) + sel.Include(sel.Mails([]string{user}, []string{folder}, []string{m1, m2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) @@ -232,14 +232,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_MailFolders() { f2 = "f2" ) - sel.Exclude(sel.MailFolders(user, f1, f2)) + sel.Exclude(sel.MailFolders([]string{user}, []string{f1, f2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) - assert.Equal(t, scope[ExchangeMail.String()], None) + assert.Equal(t, scope[ExchangeMail.String()], NoneTgt) } func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() { @@ -252,14 +252,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() { f2 = "f2" ) - sel.Include(sel.MailFolders(user, f1, f2)) + sel.Include(sel.MailFolders([]string{user}, []string{f1, f2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) - assert.Equal(t, scope[ExchangeMail.String()], All) + assert.Equal(t, scope[ExchangeMail.String()], AllTgt) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder) } @@ -273,17 +273,17 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Users() { u2 = "u2" ) - sel.Exclude(sel.Users(u1, u2)) + sel.Exclude(sel.Users([]string{u1, u2})) scopes := sel.Excludes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) - assert.Equal(t, scope[ExchangeContact.String()], None) - assert.Equal(t, scope[ExchangeContactFolder.String()], None) - assert.Equal(t, scope[ExchangeEvent.String()], None) - assert.Equal(t, scope[ExchangeMail.String()], None) - assert.Equal(t, scope[ExchangeMailFolder.String()], None) + assert.Equal(t, scope[ExchangeContact.String()], NoneTgt) + assert.Equal(t, scope[ExchangeContactFolder.String()], NoneTgt) + assert.Equal(t, scope[ExchangeEvent.String()], NoneTgt) + assert.Equal(t, scope[ExchangeMail.String()], NoneTgt) + assert.Equal(t, scope[ExchangeMailFolder.String()], NoneTgt) } func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() { @@ -295,17 +295,17 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() { u2 = "u2" ) - sel.Include(sel.Users(u1, u2)) + sel.Include(sel.Users([]string{u1, u2})) scopes := sel.Includes require.Equal(t, 1, len(scopes)) scope := scopes[0] assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) - assert.Equal(t, scope[ExchangeContact.String()], All) - assert.Equal(t, scope[ExchangeContactFolder.String()], All) - assert.Equal(t, scope[ExchangeEvent.String()], All) - assert.Equal(t, scope[ExchangeMail.String()], All) - assert.Equal(t, scope[ExchangeMailFolder.String()], All) + assert.Equal(t, scope[ExchangeContact.String()], AllTgt) + assert.Equal(t, scope[ExchangeContactFolder.String()], AllTgt) + assert.Equal(t, scope[ExchangeEvent.String()], AllTgt) + assert.Equal(t, scope[ExchangeMail.String()], AllTgt) + assert.Equal(t, scope[ExchangeMailFolder.String()], AllTgt) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeUser) } @@ -360,19 +360,19 @@ func (suite *ExchangeSourceSuite) TestExchangeDestination_GetOrDefault() { } var allScopesExceptUnknown = map[string]string{ - ExchangeContact.String(): All, - ExchangeContactFolder.String(): All, - ExchangeEvent.String(): All, - ExchangeMail.String(): All, - ExchangeMailFolder.String(): All, - ExchangeUser.String(): All, + ExchangeContact.String(): AllTgt, + ExchangeContactFolder.String(): AllTgt, + ExchangeEvent.String(): AllTgt, + ExchangeMail.String(): AllTgt, + ExchangeMailFolder.String(): AllTgt, + ExchangeUser.String(): AllTgt, } func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() { eb := NewExchangeBackup() eb.Includes = []map[string]string{allScopesExceptUnknown} // todo: swap the above for this - // eb := NewExchangeBackup().IncludeUsers(All) + // eb := NewExchangeBackup().IncludeUsers(AllTgt) scopes := eb.Scopes() assert.Len(suite.T(), scopes, 1) @@ -449,7 +449,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() { eb := NewExchangeBackup() eb.Includes = []map[string]string{allScopesExceptUnknown} // todo: swap the above for this - // eb := NewExchangeBackup().IncludeUsers(All) + // eb := NewExchangeBackup().IncludeUsers(AllTgt) scope := eb.Scopes()[0] @@ -464,10 +464,10 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() { assert.Equal( suite.T(), - []string{None}, + None(), scope.Get(ExchangeCategoryUnknown)) - expect := []string{All} + expect := All() for _, test := range table { suite.T().Run(test.String(), func(t *testing.T) { assert.Equal(t, expect, scope.Get(test)) @@ -488,29 +488,31 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() { table := []struct { name string - scope exchangeScope + scope []exchangeScope expect assert.BoolAssertionFunc }{ - {"all user's items", es.Users(All), assert.True}, - {"no user's items", es.Users(None), assert.False}, - {"matching user", es.Users(usr), assert.True}, - {"non-maching user", es.Users("smarf"), assert.False}, - {"one of multiple users", es.Users("smarf", usr), assert.True}, - {"all folders", es.MailFolders(All, All), assert.True}, - {"no folders", es.MailFolders(All, None), assert.False}, - {"matching folder", es.MailFolders(All, fld), assert.True}, - {"non-matching folder", es.MailFolders(All, "smarf"), assert.False}, - {"one of multiple folders", es.MailFolders(All, "smarf", fld), assert.True}, - {"all mail", es.Mails(All, All, All), assert.True}, - {"no mail", es.Mails(All, All, None), assert.False}, - {"matching mail", es.Mails(All, All, mail), assert.True}, - {"non-matching mail", es.Mails(All, All, "smarf"), assert.False}, - {"one of multiple mails", es.Mails(All, All, "smarf", mail), assert.True}, + {"all user's items", es.Users(All()), assert.True}, + {"no user's items", es.Users(None()), assert.False}, + {"matching user", es.Users([]string{usr}), assert.True}, + {"non-maching user", es.Users([]string{"smarf"}), assert.False}, + {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True}, + {"all folders", es.MailFolders(All(), All()), assert.True}, + {"no folders", es.MailFolders(All(), None()), assert.False}, + {"matching folder", es.MailFolders(All(), []string{fld}), assert.True}, + {"non-matching folder", es.MailFolders(All(), []string{"smarf"}), assert.False}, + {"one of multiple folders", es.MailFolders(All(), []string{"smarf", fld}), assert.True}, + {"all mail", es.Mails(All(), All(), All()), assert.True}, + {"no mail", es.Mails(All(), All(), None()), assert.False}, + {"matching mail", es.Mails(All(), All(), []string{mail}), assert.True}, + {"non-matching mail", es.Mails(All(), All(), []string{"smarf"}), assert.False}, + {"one of multiple mails", es.Mails(All(), All(), []string{"smarf", mail}), assert.True}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - scope := extendExchangeScopeValues(All, test.scope) - test.expect(t, scope.includesPath(ExchangeMail, path)) + scopes := extendExchangeScopeValues(All(), test.scope) + for _, scope := range scopes { + test.expect(t, scope.includesPath(ExchangeMail, path)) + } }) } } @@ -528,29 +530,31 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesPath() { table := []struct { name string - scope exchangeScope + scope []exchangeScope expect assert.BoolAssertionFunc }{ - {"all user's items", es.Users(All), assert.True}, - {"no user's items", es.Users(None), assert.False}, - {"matching user", es.Users(usr), assert.True}, - {"non-maching user", es.Users("smarf"), assert.False}, - {"one of multiple users", es.Users("smarf", usr), assert.True}, - {"all folders", es.MailFolders(None, All), assert.True}, - {"no folders", es.MailFolders(None, None), assert.False}, - {"matching folder", es.MailFolders(None, fld), assert.True}, - {"non-matching folder", es.MailFolders(None, "smarf"), assert.False}, - {"one of multiple folders", es.MailFolders(None, "smarf", fld), assert.True}, - {"all mail", es.Mails(None, None, All), assert.True}, - {"no mail", es.Mails(None, None, None), assert.False}, - {"matching mail", es.Mails(None, None, mail), assert.True}, - {"non-matching mail", es.Mails(None, None, "smarf"), assert.False}, - {"one of multiple mails", es.Mails(None, None, "smarf", mail), assert.True}, + {"all user's items", es.Users(All()), assert.True}, + {"no user's items", es.Users(None()), assert.False}, + {"matching user", es.Users([]string{usr}), assert.True}, + {"non-maching user", es.Users([]string{"smarf"}), assert.False}, + {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True}, + {"all folders", es.MailFolders(None(), All()), assert.True}, + {"no folders", es.MailFolders(None(), None()), assert.False}, + {"matching folder", es.MailFolders(None(), []string{fld}), assert.True}, + {"non-matching folder", es.MailFolders(None(), []string{"smarf"}), assert.False}, + {"one of multiple folders", es.MailFolders(None(), []string{"smarf", fld}), assert.True}, + {"all mail", es.Mails(None(), None(), All()), assert.True}, + {"no mail", es.Mails(None(), None(), None()), assert.False}, + {"matching mail", es.Mails(None(), None(), []string{mail}), assert.True}, + {"non-matching mail", es.Mails(None(), None(), []string{"smarf"}), assert.False}, + {"one of multiple mails", es.Mails(None(), None(), []string{"smarf", mail}), assert.True}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - scope := extendExchangeScopeValues(None, test.scope) - test.expect(t, scope.excludesPath(ExchangeMail, path)) + scopes := extendExchangeScopeValues(None(), test.scope) + for _, scope := range scopes { + test.expect(t, scope.excludesPath(ExchangeMail, path)) + } }) } } @@ -637,7 +641,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) + er.Include(er.Users(All())) return er }, [][]string{}, @@ -647,7 +651,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) + er.Include(er.Users(All())) return er }, split(contact), @@ -657,7 +661,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(event), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) + er.Include(er.Users(All())) return er }, split(event), @@ -667,7 +671,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) + er.Include(er.Users(All())) return er }, split(mail), @@ -677,7 +681,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) + er.Include(er.Users(All())) return er }, split(contact, event, mail), @@ -687,7 +691,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Contacts("uid", "cfld", "cid")) + er.Include(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"})) return er }, split(contact), @@ -697,7 +701,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Events("uid", "eid")) + er.Include(er.Events([]string{"uid"}, []string{"eid"})) return er }, split(event), @@ -707,7 +711,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Mails("uid", "mfld", "mid")) + er.Include(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"})) return er }, split(mail), @@ -717,8 +721,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) - er.Exclude(er.Contacts("uid", "cfld", "cid")) + er.Include(er.Users(All())) + er.Exclude(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"})) return er }, split(event, mail), @@ -728,8 +732,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) - er.Exclude(er.Events("uid", "eid")) + er.Include(er.Users(All())) + er.Exclude(er.Events([]string{"uid"}, []string{"eid"})) return er }, split(contact, mail), @@ -739,8 +743,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets(contact, event, mail), func() *ExchangeRestore { er := NewExchangeRestore() - er.Include(er.Users(All)) - er.Exclude(er.Mails("uid", "mfld", "mid")) + er.Include(er.Users(All())) + er.Exclude(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"})) return er }, split(contact, event), @@ -758,10 +762,10 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { var ( es = NewExchangeRestore() - users = es.Users(All) - contacts = es.ContactFolders(All, All) - events = es.Events(All, All) - mail = es.MailFolders(All, All) + users = es.Users(All()) + contacts = es.ContactFolders(All(), All()) + events = es.Events(All(), All()) + mail = es.MailFolders(All(), All()) ) type expect struct { contact int @@ -769,16 +773,25 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { mail int } type input []map[string]string + makeInput := func(es ...[]exchangeScope) []map[string]string { + mss := []map[string]string{} + for _, sl := range es { + for _, s := range sl { + mss = append(mss, map[string]string(s)) + } + } + return mss + } table := []struct { name string scopes input expect expect }{ - {"users: one of each", input{users}, expect{1, 1, 1}}, - {"contacts only", input{contacts}, expect{1, 0, 0}}, - {"events only", input{events}, expect{0, 1, 0}}, - {"mail only", input{mail}, expect{0, 0, 1}}, - {"all", input{users, contacts, events, mail}, expect{2, 2, 2}}, + {"users: one of each", makeInput(users), expect{1, 1, 1}}, + {"contacts only", makeInput(contacts), expect{1, 0, 0}}, + {"events only", makeInput(events), expect{0, 1, 0}}, + {"mail only", makeInput(mail), expect{0, 0, 1}}, + {"all", makeInput(users, contacts, events, mail), expect{2, 2, 2}}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { @@ -795,22 +808,22 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() { mail = "mailID" cat = ExchangeMail ) - include := func(s map[string]string) exchangeScope { - return extendExchangeScopeValues(All, exchangeScope(s)) + include := func(s []exchangeScope) []exchangeScope { + return extendExchangeScopeValues(All(), s) } - exclude := func(s map[string]string) exchangeScope { - return extendExchangeScopeValues(None, exchangeScope(s)) + exclude := func(s []exchangeScope) []exchangeScope { + return extendExchangeScopeValues(None(), s) } var ( es = NewExchangeRestore() - inAll = include(es.Users(All)) - inNone = include(es.Users(None)) - inMail = include(es.Mails(All, All, mail)) - inOtherMail = include(es.Mails(All, All, "smarf")) - exAll = exclude(es.Users(All)) - exNone = exclude(es.Users(None)) - exMail = exclude(es.Mails(None, None, mail)) - exOtherMail = exclude(es.Mails(None, None, "smarf")) + inAll = include(es.Users(All())) + inNone = include(es.Users(None())) + inMail = include(es.Mails(All(), All(), []string{mail})) + inOtherMail = include(es.Mails(All(), All(), []string{"smarf"})) + exAll = exclude(es.Users(All())) + exNone = exclude(es.Users(None())) + exMail = exclude(es.Mails(None(), None(), []string{mail})) + exOtherMail = exclude(es.Mails(None(), None(), []string{"smarf"})) path = []string{"tid", "user", "mail", "folder", mail} ) @@ -820,16 +833,16 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() { excludes []exchangeScope expect assert.BoolAssertionFunc }{ - {"empty", []exchangeScope{}, []exchangeScope{}, assert.False}, - {"in all", []exchangeScope{inAll}, []exchangeScope{}, assert.True}, - {"in None", []exchangeScope{inNone}, []exchangeScope{}, assert.False}, - {"in Mail", []exchangeScope{inMail}, []exchangeScope{}, assert.True}, - {"in Other", []exchangeScope{inOtherMail}, []exchangeScope{}, assert.False}, - {"ex all", []exchangeScope{inAll}, []exchangeScope{exAll}, assert.False}, - {"ex None", []exchangeScope{inAll}, []exchangeScope{exNone}, assert.True}, - {"in Mail", []exchangeScope{inAll}, []exchangeScope{exMail}, assert.False}, - {"in Other", []exchangeScope{inAll}, []exchangeScope{exOtherMail}, assert.True}, - {"in and ex mail", []exchangeScope{inMail}, []exchangeScope{exMail}, assert.False}, + {"empty", nil, nil, assert.False}, + {"in all", inAll, nil, assert.True}, + {"in None", inNone, nil, assert.False}, + {"in Mail", inMail, nil, assert.True}, + {"in Other", inOtherMail, nil, assert.False}, + {"ex all", inAll, exAll, assert.False}, + {"ex None", inAll, exNone, assert.True}, + {"in Mail", inAll, exMail, assert.False}, + {"in Other", inAll, exOtherMail, assert.True}, + {"in and ex mail", inMail, exMail, assert.False}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { diff --git a/src/pkg/selectors/selectors.go b/src/pkg/selectors/selectors.go index a9f76759e..dd3cc2e44 100644 --- a/src/pkg/selectors/selectors.go +++ b/src/pkg/selectors/selectors.go @@ -27,12 +27,14 @@ const ( ) const ( - // All is the wildcard value used to express "all data of " - // Ex: {user: u1, events: All) => all events for user u1. - All = "ß∂ƒ∑´®≈ç√¬˜" - // None is usesd to express "no data of " - // Ex: {user: u1, events: None} => no events for user u1. - None = "" + // AllTgt is the target value used to select "all data of " + // Ex: {user: u1, events: AllTgt) => all events for user u1. + // In the event that "*" conflicts with a user value, such as a + // folder named "*", calls to corso should escape the value with "\*" + AllTgt = "*" + // NoneTgt is the target value used to select "no data of " + // Ex: {user: u1, events: NoneTgt} => no events for user u1. + NoneTgt = "" delimiter = "," ) @@ -58,6 +60,16 @@ func newSelector(s service) Selector { } } +// All returns the set matching All values. +func All() []string { + return []string{AllTgt} +} + +// None returns the set matching None of the values. +func None() []string { + return []string{NoneTgt} +} + // --------------------------------------------------------------------------- // Destination // --------------------------------------------------------------------------- @@ -85,3 +97,23 @@ func join(s ...string) string { func split(s string) []string { return strings.Split(s, delimiter) } + +// if the provided slice contains All, returns [All] +// if the slice contains None, returns [None] +// if the slice contains All and None, returns the first +// if the slice is empty, returns [None] +// otherwise returns the input unchanged +func normalize(s []string) []string { + if len(s) == 0 { + return None() + } + for _, e := range s { + if e == AllTgt { + return All() + } + if e == NoneTgt { + return None() + } + } + return s +}