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 {
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

View File

@ -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
}

View File

@ -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"),

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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
}
}

View File

@ -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) {

View File

@ -27,12 +27,14 @@ const (
)
const (
// All is the wildcard value used to express "all data of <type>"
// Ex: {user: u1, events: All) => all events for user u1.
All = "ß∂ƒ∑´®≈ç√¬˜"
// None is usesd to express "no data of <type>"
// Ex: {user: u1, events: None} => no events for user u1.
None = ""
// AllTgt is the target value used to select "all data of <type>"
// 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 <type>"
// 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
}