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.
This commit is contained in:
Keepers 2022-07-19 16:04:07 -06:00 committed by GitHub
parent 0422b9ecbe
commit 746c88a233
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 327 additions and 229 deletions

View File

@ -118,42 +118,22 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector { func exchangeBackupCreateSelectors(all bool, users, data []string) selectors.Selector {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
if all { if all {
sel.Include(sel.Users(selectors.All)) sel.Include(sel.Users(selectors.All()))
return sel.Selector return sel.Selector
} }
if len(data) == 0 { if len(data) == 0 {
for _, user := range users { sel.Include(sel.ContactFolders(user, selectors.All()))
if user == utils.Wildcard { sel.Include(sel.MailFolders(user, selectors.All()))
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 { for _, d := range data {
switch d { switch d {
case dataContacts: case dataContacts:
for _, user := range users { sel.Include(sel.ContactFolders(users, selectors.All()))
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.ContactFolders(user, selectors.All))
}
case dataEmail: case dataEmail:
for _, user := range users { sel.Include(sel.MailFolders(users, selectors.All()))
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.MailFolders(user, selectors.All))
}
case dataEvents: case dataEvents:
for _, user := range users { sel.Include(sel.Events(users, selectors.All()))
if user == utils.Wildcard {
user = selectors.All
}
sel.Include(sel.Events(user, selectors.All))
}
} }
} }
return sel.Selector return sel.Selector

View File

@ -103,23 +103,18 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
func exchangeRestoreSelectors(u, f, m string) selectors.Selector { func exchangeRestoreSelectors(u, f, m string) selectors.Selector {
sel := selectors.NewExchangeRestore() sel := selectors.NewExchangeRestore()
if u == "*" {
u = selectors.All
}
if f == "*" {
f = selectors.All
}
if m == "*" {
m = selectors.All
}
if len(m) > 0 { 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 { 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 { if len(f) == 0 && len(m) == 0 {
sel.Include(sel.Users(u)) sel.Include(sel.Users([]string{u}))
} }
return sel.Selector return sel.Selector
} }

View File

@ -187,7 +187,7 @@ func (gc *GraphConnector) ExchangeDataCollection(ctx context.Context, selector s
// TODO: handle "get mail for all users" // TODO: handle "get mail for all users"
// this would probably no-op without this check, // this would probably no-op without this check,
// but we want it made obvious that we're punting. // but we want it made obvious that we're punting.
if user == selectors.All { if user == selectors.AllTgt {
errs = support.WrapAndAppend( errs = support.WrapAndAppend(
"all-users", "all-users",
errors.New("all users selector currently not handled"), errors.New("all users selector currently not handled"),

View File

@ -59,7 +59,7 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_setTenantUsers()
func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_ExchangeDataCollection() { func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_ExchangeDataCollection() {
sel := selectors.NewExchangeBackup() 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) collectionList, err := suite.connector.ExchangeDataCollection(context.Background(), sel.Selector)
assert.NotNil(suite.T(), collectionList, "collection list") assert.NotNil(suite.T(), collectionList, "collection list")
assert.Nil(suite.T(), err) assert.Nil(suite.T(), err)

View File

@ -158,7 +158,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
sw := store.NewKopiaStore(ms) sw := store.NewKopiaStore(ms)
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.Users(m365User)) sel.Include(sel.Users([]string{m365User}))
bo, err := NewBackupOperation( bo, err := NewBackupOperation(
ctx, ctx,

View File

@ -150,7 +150,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
sw := store.NewKopiaStore(ms) sw := store.NewKopiaStore(ms)
bsel := selectors.NewExchangeBackup() bsel := selectors.NewExchangeBackup()
bsel.Include(bsel.Users(m365User)) bsel.Include(bsel.Users([]string{m365User}))
bo, err := NewBackupOperation( bo, err := NewBackupOperation(
ctx, ctx,
@ -164,7 +164,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
require.NotEmpty(t, bo.Results.BackupID) require.NotEmpty(t, bo.Results.BackupID)
rsel := selectors.NewExchangeRestore() rsel := selectors.NewExchangeRestore()
rsel.Include(rsel.Users(m365User)) rsel.Include(rsel.Users([]string{m365User}))
ro, err := NewRestoreOperation( ro, err := NewRestoreOperation(
ctx, ctx,

View File

@ -76,102 +76,180 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
// Exclude/Includes // Exclude/Includes
// Include appends the provided scopes to the selector's inclusion set. // 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 { if s.Includes == nil {
s.Includes = []map[string]string{} s.Includes = []map[string]string{}
} }
for _, sc := range scopes { concat := []exchangeScope{}
sc = extendExchangeScopeValues(All, sc) for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(All(), scopeSl)...)
}
for _, sc := range concat {
s.Includes = append(s.Includes, map[string]string(sc)) s.Includes = append(s.Includes, map[string]string(sc))
} }
} }
// Exclude appends the provided scopes to the selector's exclusion set. // Exclude appends the provided scopes to the selector's exclusion set.
// Every Exclusion scope applies globally, affecting all inclusion scopes. // 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 { if s.Excludes == nil {
s.Excludes = []map[string]string{} s.Excludes = []map[string]string{}
} }
for _, sc := range scopes { concat := []exchangeScope{}
sc = extendExchangeScopeValues(None, sc) for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(None(), scopeSl)...)
}
for _, sc := range concat {
s.Excludes = append(s.Excludes, map[string]string(sc)) s.Excludes = append(s.Excludes, map[string]string(sc))
} }
} }
// completes population for certain scope properties, according to the // completes population for certain scope properties, according to the
// expecations of Include and Exclude behavior. // expecations of Include and Exclude behavior.
func extendExchangeScopeValues(v string, sc exchangeScope) exchangeScope { func extendExchangeScopeValues(v []string, es []exchangeScope) []exchangeScope {
switch sc.Category() { vv := join(v...)
case ExchangeContactFolder: for i := range es {
sc[ExchangeContact.String()] = v switch es[i].Category() {
case ExchangeMailFolder: case ExchangeContactFolder:
sc[ExchangeMail.String()] = v es[i][ExchangeContact.String()] = vv
case ExchangeUser: case ExchangeMailFolder:
sc[ExchangeContactFolder.String()] = v es[i][ExchangeMail.String()] = vv
sc[ExchangeContact.String()] = v case ExchangeUser:
sc[ExchangeEvent.String()] = v es[i][ExchangeContactFolder.String()] = vv
sc[ExchangeMailFolder.String()] = v es[i][ExchangeContact.String()] = vv
sc[ExchangeMail.String()] = v es[i][ExchangeEvent.String()] = vv
es[i][ExchangeMailFolder.String()] = vv
es[i][ExchangeMail.String()] = vv
}
} }
return sc return es
} }
// ------------------- // -------------------
// Scope Factory // Scope Factory
func (s *exchange) Contacts(u, f string, vs ...string) exchangeScope { // Produces one or more exchange contact scopes.
return exchangeScope{ // One scope is created per combination of users,folders,contacts.
scopeKeyGranularity: Item, // If any slice contains selectors.All, that slice is reduced to [selectors.All]
scopeKeyCategory: ExchangeContact.String(), // If any slice contains selectors.None, that slice is reduced to [selectors.None]
ExchangeUser.String(): u, // If any slice is empty, it defaults to [selectors.None]
ExchangeContactFolder.String(): f, func (s *exchange) Contacts(users, folders, contacts []string) []exchangeScope {
ExchangeContact.String(): join(vs...), 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 { // Produces one or more exchange contact folder scopes.
return exchangeScope{ // One scope is created per combination of users,folders.
scopeKeyGranularity: Group, // If any slice contains selectors.All, that slice is reduced to [selectors.All]
scopeKeyCategory: ExchangeContactFolder.String(), // If any slice contains selectors.None, that slice is reduced to [selectors.None]
ExchangeUser.String(): u, // If any slice is empty, it defaults to [selectors.None]
ExchangeContactFolder.String(): join(vs...), 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 { // Produces one or more exchange event scopes.
return map[string]string{ // One scope is created per combination of users,events.
scopeKeyGranularity: Item, // If any slice contains selectors.All, that slice is reduced to [selectors.All]
scopeKeyCategory: ExchangeEvent.String(), // If any slice contains selectors.None, that slice is reduced to [selectors.None]
ExchangeUser.String(): u, // If any slice is empty, it defaults to [selectors.None]
ExchangeEvent.String(): join(vs...), 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 { // Produces one or more mail scopes.
return map[string]string{ // One scope is created per combination of users,folders,mails.
scopeKeyGranularity: Item, // If any slice contains selectors.All, that slice is reduced to [selectors.All]
scopeKeyCategory: ExchangeMail.String(), // If any slice contains selectors.None, that slice is reduced to [selectors.None]
ExchangeUser.String(): u, // If any slice is empty, it defaults to [selectors.None]
ExchangeMailFolder.String(): f, func (s *exchange) Mails(users, folders, mails []string) []exchangeScope {
ExchangeMail.String(): join(vs...), 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 { // Produces one or more exchange mail folder scopes.
return map[string]string{ // One scope is created per combination of users,folders.
scopeKeyGranularity: Group, // If any slice contains selectors.All, that slice is reduced to [selectors.All]
scopeKeyCategory: ExchangeMailFolder.String(), // If any slice contains selectors.None, that slice is reduced to [selectors.None]
ExchangeUser.String(): u, // If any slice is empty, it defaults to [selectors.None]
ExchangeMailFolder.String(): join(vs...), 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 { // Produces one or more exchange contact user scopes.
return map[string]string{ // 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, scopeKeyGranularity: Group,
scopeKeyCategory: ExchangeUser.String(), 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 { func (s exchangeScope) Get(cat exchangeCategory) []string {
v, ok := s[cat.String()] v, ok := s[cat.String()]
if !ok { if !ok {
return []string{None} return None()
} }
return split(v) return split(v)
} }
@ -323,7 +401,7 @@ func (s exchangeScope) includesPath(cat exchangeCategory, path []string) bool {
if !ok { if !ok {
return false return false
} }
if target[0] != All && !contains(target, id) { if target[0] != AllTgt && !contains(target, id) {
return false return false
} }
} }
@ -342,7 +420,7 @@ func (s exchangeScope) excludesPath(cat exchangeCategory, path []string) bool {
if !ok { if !ok {
return true return true
} }
if target[0] == All || contains(target, id) { if target[0] == AllTgt || contains(target, id) {
return true return true
} }
} }

View File

@ -58,12 +58,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Contacts() {
const ( const (
user = "user" user = "user"
folder = All folder = AllTgt
c1 = "c1" c1 = "c1"
c2 = "c2" c2 = "c2"
) )
sel.Exclude(sel.Contacts(user, folder, c1, c2)) sel.Exclude(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -79,12 +79,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Contacts() {
const ( const (
user = "user" user = "user"
folder = All folder = AllTgt
c1 = "c1" c1 = "c1"
c2 = "c2" c2 = "c2"
) )
sel.Include(sel.Contacts(user, folder, c1, c2)) sel.Include(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -106,14 +106,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_ContactFolders()
f2 = "f2" f2 = "f2"
) )
sel.Exclude(sel.ContactFolders(user, f1, f2)) sel.Exclude(sel.ContactFolders([]string{user}, []string{f1, f2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) 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() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders() {
@ -126,14 +126,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders()
f2 = "f2" f2 = "f2"
) )
sel.Include(sel.ContactFolders(user, f1, f2)) sel.Include(sel.ContactFolders([]string{user}, []string{f1, f2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeContactFolder.String()], join(f1, f2)) 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) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder)
} }
@ -148,7 +148,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Events() {
e2 = "e2" e2 = "e2"
) )
sel.Exclude(sel.Events(user, e1, e2)) sel.Exclude(sel.Events([]string{user}, []string{e1, e2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -167,7 +167,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Events() {
e2 = "e2" e2 = "e2"
) )
sel.Include(sel.Events(user, e1, e2)) sel.Include(sel.Events([]string{user}, []string{e1, e2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -184,12 +184,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Mails() {
const ( const (
user = "user" user = "user"
folder = All folder = AllTgt
m1 = "m1" m1 = "m1"
m2 = "m2" m2 = "m2"
) )
sel.Exclude(sel.Mails(user, folder, m1, m2)) sel.Exclude(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -205,12 +205,12 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Mails() {
const ( const (
user = "user" user = "user"
folder = All folder = AllTgt
m1 = "m1" m1 = "m1"
m2 = "m2" m2 = "m2"
) )
sel.Include(sel.Mails(user, folder, m1, m2)) sel.Include(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
@ -232,14 +232,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_MailFolders() {
f2 = "f2" f2 = "f2"
) )
sel.Exclude(sel.MailFolders(user, f1, f2)) sel.Exclude(sel.MailFolders([]string{user}, []string{f1, f2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) 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() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() {
@ -252,14 +252,14 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() {
f2 = "f2" f2 = "f2"
) )
sel.Include(sel.MailFolders(user, f1, f2)) sel.Include(sel.MailFolders([]string{user}, []string{f1, f2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], user) assert.Equal(t, scope[ExchangeUser.String()], user)
assert.Equal(t, scope[ExchangeMailFolder.String()], join(f1, f2)) 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) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder)
} }
@ -273,17 +273,17 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Users() {
u2 = "u2" u2 = "u2"
) )
sel.Exclude(sel.Users(u1, u2)) sel.Exclude(sel.Users([]string{u1, u2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2))
assert.Equal(t, scope[ExchangeContact.String()], None) assert.Equal(t, scope[ExchangeContact.String()], NoneTgt)
assert.Equal(t, scope[ExchangeContactFolder.String()], None) assert.Equal(t, scope[ExchangeContactFolder.String()], NoneTgt)
assert.Equal(t, scope[ExchangeEvent.String()], None) assert.Equal(t, scope[ExchangeEvent.String()], NoneTgt)
assert.Equal(t, scope[ExchangeMail.String()], None) assert.Equal(t, scope[ExchangeMail.String()], NoneTgt)
assert.Equal(t, scope[ExchangeMailFolder.String()], None) assert.Equal(t, scope[ExchangeMailFolder.String()], NoneTgt)
} }
func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() { func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() {
@ -295,17 +295,17 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() {
u2 = "u2" u2 = "u2"
) )
sel.Include(sel.Users(u1, u2)) sel.Include(sel.Users([]string{u1, u2}))
scopes := sel.Includes scopes := sel.Includes
require.Equal(t, 1, len(scopes)) require.Equal(t, 1, len(scopes))
scope := scopes[0] scope := scopes[0]
assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2)) assert.Equal(t, scope[ExchangeUser.String()], join(u1, u2))
assert.Equal(t, scope[ExchangeContact.String()], All) assert.Equal(t, scope[ExchangeContact.String()], AllTgt)
assert.Equal(t, scope[ExchangeContactFolder.String()], All) assert.Equal(t, scope[ExchangeContactFolder.String()], AllTgt)
assert.Equal(t, scope[ExchangeEvent.String()], All) assert.Equal(t, scope[ExchangeEvent.String()], AllTgt)
assert.Equal(t, scope[ExchangeMail.String()], All) assert.Equal(t, scope[ExchangeMail.String()], AllTgt)
assert.Equal(t, scope[ExchangeMailFolder.String()], All) assert.Equal(t, scope[ExchangeMailFolder.String()], AllTgt)
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeUser) assert.Equal(t, sel.Scopes()[0].Category(), ExchangeUser)
} }
@ -360,19 +360,19 @@ func (suite *ExchangeSourceSuite) TestExchangeDestination_GetOrDefault() {
} }
var allScopesExceptUnknown = map[string]string{ var allScopesExceptUnknown = map[string]string{
ExchangeContact.String(): All, ExchangeContact.String(): AllTgt,
ExchangeContactFolder.String(): All, ExchangeContactFolder.String(): AllTgt,
ExchangeEvent.String(): All, ExchangeEvent.String(): AllTgt,
ExchangeMail.String(): All, ExchangeMail.String(): AllTgt,
ExchangeMailFolder.String(): All, ExchangeMailFolder.String(): AllTgt,
ExchangeUser.String(): All, ExchangeUser.String(): AllTgt,
} }
func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() { func (suite *ExchangeSourceSuite) TestExchangeBackup_Scopes() {
eb := NewExchangeBackup() eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown} eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this // todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(All) // eb := NewExchangeBackup().IncludeUsers(AllTgt)
scopes := eb.Scopes() scopes := eb.Scopes()
assert.Len(suite.T(), scopes, 1) assert.Len(suite.T(), scopes, 1)
@ -449,7 +449,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
eb := NewExchangeBackup() eb := NewExchangeBackup()
eb.Includes = []map[string]string{allScopesExceptUnknown} eb.Includes = []map[string]string{allScopesExceptUnknown}
// todo: swap the above for this // todo: swap the above for this
// eb := NewExchangeBackup().IncludeUsers(All) // eb := NewExchangeBackup().IncludeUsers(AllTgt)
scope := eb.Scopes()[0] scope := eb.Scopes()[0]
@ -464,10 +464,10 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
assert.Equal( assert.Equal(
suite.T(), suite.T(),
[]string{None}, None(),
scope.Get(ExchangeCategoryUnknown)) scope.Get(ExchangeCategoryUnknown))
expect := []string{All} expect := All()
for _, test := range table { for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) { suite.T().Run(test.String(), func(t *testing.T) {
assert.Equal(t, expect, scope.Get(test)) assert.Equal(t, expect, scope.Get(test))
@ -488,29 +488,31 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_IncludesPath() {
table := []struct { table := []struct {
name string name string
scope exchangeScope scope []exchangeScope
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"all user's items", es.Users(All), assert.True}, {"all user's items", es.Users(All()), assert.True},
{"no user's items", es.Users(None), assert.False}, {"no user's items", es.Users(None()), assert.False},
{"matching user", es.Users(usr), assert.True}, {"matching user", es.Users([]string{usr}), assert.True},
{"non-maching user", es.Users("smarf"), assert.False}, {"non-maching user", es.Users([]string{"smarf"}), assert.False},
{"one of multiple users", es.Users("smarf", usr), assert.True}, {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True},
{"all folders", es.MailFolders(All, All), assert.True}, {"all folders", es.MailFolders(All(), All()), assert.True},
{"no folders", es.MailFolders(All, None), assert.False}, {"no folders", es.MailFolders(All(), None()), assert.False},
{"matching folder", es.MailFolders(All, fld), assert.True}, {"matching folder", es.MailFolders(All(), []string{fld}), assert.True},
{"non-matching folder", es.MailFolders(All, "smarf"), assert.False}, {"non-matching folder", es.MailFolders(All(), []string{"smarf"}), assert.False},
{"one of multiple folders", es.MailFolders(All, "smarf", fld), assert.True}, {"one of multiple folders", es.MailFolders(All(), []string{"smarf", fld}), assert.True},
{"all mail", es.Mails(All, All, All), assert.True}, {"all mail", es.Mails(All(), All(), All()), assert.True},
{"no mail", es.Mails(All, All, None), assert.False}, {"no mail", es.Mails(All(), All(), None()), assert.False},
{"matching mail", es.Mails(All, All, mail), assert.True}, {"matching mail", es.Mails(All(), All(), []string{mail}), assert.True},
{"non-matching mail", es.Mails(All, All, "smarf"), assert.False}, {"non-matching mail", es.Mails(All(), All(), []string{"smarf"}), assert.False},
{"one of multiple mails", es.Mails(All, All, "smarf", mail), assert.True}, {"one of multiple mails", es.Mails(All(), All(), []string{"smarf", mail}), assert.True},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
scope := extendExchangeScopeValues(All, test.scope) scopes := extendExchangeScopeValues(All(), test.scope)
test.expect(t, scope.includesPath(ExchangeMail, path)) for _, scope := range scopes {
test.expect(t, scope.includesPath(ExchangeMail, path))
}
}) })
} }
} }
@ -528,29 +530,31 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_ExcludesPath() {
table := []struct { table := []struct {
name string name string
scope exchangeScope scope []exchangeScope
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"all user's items", es.Users(All), assert.True}, {"all user's items", es.Users(All()), assert.True},
{"no user's items", es.Users(None), assert.False}, {"no user's items", es.Users(None()), assert.False},
{"matching user", es.Users(usr), assert.True}, {"matching user", es.Users([]string{usr}), assert.True},
{"non-maching user", es.Users("smarf"), assert.False}, {"non-maching user", es.Users([]string{"smarf"}), assert.False},
{"one of multiple users", es.Users("smarf", usr), assert.True}, {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True},
{"all folders", es.MailFolders(None, All), assert.True}, {"all folders", es.MailFolders(None(), All()), assert.True},
{"no folders", es.MailFolders(None, None), assert.False}, {"no folders", es.MailFolders(None(), None()), assert.False},
{"matching folder", es.MailFolders(None, fld), assert.True}, {"matching folder", es.MailFolders(None(), []string{fld}), assert.True},
{"non-matching folder", es.MailFolders(None, "smarf"), assert.False}, {"non-matching folder", es.MailFolders(None(), []string{"smarf"}), assert.False},
{"one of multiple folders", es.MailFolders(None, "smarf", fld), assert.True}, {"one of multiple folders", es.MailFolders(None(), []string{"smarf", fld}), assert.True},
{"all mail", es.Mails(None, None, All), assert.True}, {"all mail", es.Mails(None(), None(), All()), assert.True},
{"no mail", es.Mails(None, None, None), assert.False}, {"no mail", es.Mails(None(), None(), None()), assert.False},
{"matching mail", es.Mails(None, None, mail), assert.True}, {"matching mail", es.Mails(None(), None(), []string{mail}), assert.True},
{"non-matching mail", es.Mails(None, None, "smarf"), assert.False}, {"non-matching mail", es.Mails(None(), None(), []string{"smarf"}), assert.False},
{"one of multiple mails", es.Mails(None, None, "smarf", mail), assert.True}, {"one of multiple mails", es.Mails(None(), None(), []string{"smarf", mail}), assert.True},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
scope := extendExchangeScopeValues(None, test.scope) scopes := extendExchangeScopeValues(None(), test.scope)
test.expect(t, scope.excludesPath(ExchangeMail, path)) for _, scope := range scopes {
test.expect(t, scope.excludesPath(ExchangeMail, path))
}
}) })
} }
} }
@ -637,7 +641,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(), makeDeets(),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
return er return er
}, },
[][]string{}, [][]string{},
@ -647,7 +651,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact), makeDeets(contact),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
return er return er
}, },
split(contact), split(contact),
@ -657,7 +661,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(event), makeDeets(event),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
return er return er
}, },
split(event), split(event),
@ -667,7 +671,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(mail), makeDeets(mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
return er return er
}, },
split(mail), split(mail),
@ -677,7 +681,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
return er return er
}, },
split(contact, event, mail), split(contact, event, mail),
@ -687,7 +691,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Contacts("uid", "cfld", "cid")) er.Include(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"}))
return er return er
}, },
split(contact), split(contact),
@ -697,7 +701,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Events("uid", "eid")) er.Include(er.Events([]string{"uid"}, []string{"eid"}))
return er return er
}, },
split(event), split(event),
@ -707,7 +711,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Mails("uid", "mfld", "mid")) er.Include(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"}))
return er return er
}, },
split(mail), split(mail),
@ -717,8 +721,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
er.Exclude(er.Contacts("uid", "cfld", "cid")) er.Exclude(er.Contacts([]string{"uid"}, []string{"cfld"}, []string{"cid"}))
return er return er
}, },
split(event, mail), split(event, mail),
@ -728,8 +732,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
er.Exclude(er.Events("uid", "eid")) er.Exclude(er.Events([]string{"uid"}, []string{"eid"}))
return er return er
}, },
split(contact, mail), split(contact, mail),
@ -739,8 +743,8 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
makeDeets(contact, event, mail), makeDeets(contact, event, mail),
func() *ExchangeRestore { func() *ExchangeRestore {
er := NewExchangeRestore() er := NewExchangeRestore()
er.Include(er.Users(All)) er.Include(er.Users(All()))
er.Exclude(er.Mails("uid", "mfld", "mid")) er.Exclude(er.Mails([]string{"uid"}, []string{"mfld"}, []string{"mid"}))
return er return er
}, },
split(contact, event), split(contact, event),
@ -758,10 +762,10 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() { func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
var ( var (
es = NewExchangeRestore() es = NewExchangeRestore()
users = es.Users(All) users = es.Users(All())
contacts = es.ContactFolders(All, All) contacts = es.ContactFolders(All(), All())
events = es.Events(All, All) events = es.Events(All(), All())
mail = es.MailFolders(All, All) mail = es.MailFolders(All(), All())
) )
type expect struct { type expect struct {
contact int contact int
@ -769,16 +773,25 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
mail int mail int
} }
type input []map[string]string 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 { table := []struct {
name string name string
scopes input scopes input
expect expect expect expect
}{ }{
{"users: one of each", input{users}, expect{1, 1, 1}}, {"users: one of each", makeInput(users), expect{1, 1, 1}},
{"contacts only", input{contacts}, expect{1, 0, 0}}, {"contacts only", makeInput(contacts), expect{1, 0, 0}},
{"events only", input{events}, expect{0, 1, 0}}, {"events only", makeInput(events), expect{0, 1, 0}},
{"mail only", input{mail}, expect{0, 0, 1}}, {"mail only", makeInput(mail), expect{0, 0, 1}},
{"all", input{users, contacts, events, mail}, expect{2, 2, 2}}, {"all", makeInput(users, contacts, events, mail), expect{2, 2, 2}},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
@ -795,22 +808,22 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
mail = "mailID" mail = "mailID"
cat = ExchangeMail cat = ExchangeMail
) )
include := func(s map[string]string) exchangeScope { include := func(s []exchangeScope) []exchangeScope {
return extendExchangeScopeValues(All, exchangeScope(s)) return extendExchangeScopeValues(All(), s)
} }
exclude := func(s map[string]string) exchangeScope { exclude := func(s []exchangeScope) []exchangeScope {
return extendExchangeScopeValues(None, exchangeScope(s)) return extendExchangeScopeValues(None(), s)
} }
var ( var (
es = NewExchangeRestore() es = NewExchangeRestore()
inAll = include(es.Users(All)) inAll = include(es.Users(All()))
inNone = include(es.Users(None)) inNone = include(es.Users(None()))
inMail = include(es.Mails(All, All, mail)) inMail = include(es.Mails(All(), All(), []string{mail}))
inOtherMail = include(es.Mails(All, All, "smarf")) inOtherMail = include(es.Mails(All(), All(), []string{"smarf"}))
exAll = exclude(es.Users(All)) exAll = exclude(es.Users(All()))
exNone = exclude(es.Users(None)) exNone = exclude(es.Users(None()))
exMail = exclude(es.Mails(None, None, mail)) exMail = exclude(es.Mails(None(), None(), []string{mail}))
exOtherMail = exclude(es.Mails(None, None, "smarf")) exOtherMail = exclude(es.Mails(None(), None(), []string{"smarf"}))
path = []string{"tid", "user", "mail", "folder", mail} path = []string{"tid", "user", "mail", "folder", mail}
) )
@ -820,16 +833,16 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
excludes []exchangeScope excludes []exchangeScope
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"empty", []exchangeScope{}, []exchangeScope{}, assert.False}, {"empty", nil, nil, assert.False},
{"in all", []exchangeScope{inAll}, []exchangeScope{}, assert.True}, {"in all", inAll, nil, assert.True},
{"in None", []exchangeScope{inNone}, []exchangeScope{}, assert.False}, {"in None", inNone, nil, assert.False},
{"in Mail", []exchangeScope{inMail}, []exchangeScope{}, assert.True}, {"in Mail", inMail, nil, assert.True},
{"in Other", []exchangeScope{inOtherMail}, []exchangeScope{}, assert.False}, {"in Other", inOtherMail, nil, assert.False},
{"ex all", []exchangeScope{inAll}, []exchangeScope{exAll}, assert.False}, {"ex all", inAll, exAll, assert.False},
{"ex None", []exchangeScope{inAll}, []exchangeScope{exNone}, assert.True}, {"ex None", inAll, exNone, assert.True},
{"in Mail", []exchangeScope{inAll}, []exchangeScope{exMail}, assert.False}, {"in Mail", inAll, exMail, assert.False},
{"in Other", []exchangeScope{inAll}, []exchangeScope{exOtherMail}, assert.True}, {"in Other", inAll, exOtherMail, assert.True},
{"in and ex mail", []exchangeScope{inMail}, []exchangeScope{exMail}, assert.False}, {"in and ex mail", inMail, exMail, assert.False},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {

View File

@ -27,12 +27,14 @@ const (
) )
const ( const (
// All is the wildcard value used to express "all data of <type>" // AllTgt is the target value used to select "all data of <type>"
// Ex: {user: u1, events: All) => all events for user u1. // Ex: {user: u1, events: AllTgt) => all events for user u1.
All = "ß∂ƒ∑´®≈ç√¬˜" // In the event that "*" conflicts with a user value, such as a
// None is usesd to express "no data of <type>" // folder named "*", calls to corso should escape the value with "\*"
// Ex: {user: u1, events: None} => no events for user u1. AllTgt = "*"
None = "" // NoneTgt is the target value used to select "no data of <type>"
// Ex: {user: u1, events: NoneTgt} => no events for user u1.
NoneTgt = ""
delimiter = "," 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 // Destination
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -85,3 +97,23 @@ func join(s ...string) string {
func split(s string) []string { func split(s string) []string {
return strings.Split(s, delimiter) 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
}