Issue-271-mustfilter: add must-pass filters to selectors (#392)

* add must-pass filters to selectors

Extends the selectors scope set to include Filters.  This
allows users to define all-pass matchers (filters),
separate from any-pass matchers (includes), and global
exclusions.
This commit is contained in:
Keepers 2022-07-25 16:41:49 -06:00 committed by GitHub
parent 75e8317bf9
commit 350d27dcb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 313 additions and 228 deletions

View File

@ -316,7 +316,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
emailFolder,
event,
user)
includeExchangeBackupDetailInfoSelectors(
filterExchangeBackupDetailInfoSelectors(
sel,
emailReceivedAfter,
emailReceivedBefore,
@ -328,7 +328,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
sel.Include(sel.Users(selectors.Any()))
}
ds := sel.FilterDetails(d)
ds := sel.Reduce(d)
print.Entries(ds.Entries)
return nil
@ -389,43 +389,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
sel.Include(sel.Events(users, events))
}
// builds the info-selector inclusions for `backup details exchange`
func includeExchangeBackupDetailInfoSelectors(
// builds the info-selector filters for `backup details exchange`
func filterExchangeBackupDetailInfoSelectors(
sel *selectors.ExchangeRestore,
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
) {
includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
includeExchangeInfoMailSender(sel, emailSender)
includeExchangeInfoMailSubject(sel, emailSubject)
filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
filterExchangeInfoMailSender(sel, emailSender)
filterExchangeInfoMailSubject(sel, emailSubject)
}
func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
if len(receivedAfter) == 0 {
return
}
sel.Include(sel.MailReceivedAfter(receivedAfter))
sel.Filter(sel.MailReceivedAfter(receivedAfter))
}
func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
if len(receivedBefore) == 0 {
return
}
sel.Include(sel.MailReceivedBefore(receivedBefore))
sel.Filter(sel.MailReceivedBefore(receivedBefore))
}
func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
if len(sender) == 0 {
return
}
sel.Include(sel.MailSender(sender))
sel.Filter(sel.MailSender(sender))
}
func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
if len(subject) == 0 {
return
}
sel.Include(sel.MailSubject(subject))
sel.Filter(sel.MailSubject(subject))
}
// checks all flags for correctness and interdependencies

View File

@ -480,78 +480,78 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
}
}
func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() {
func (suite *ExchangeSuite) TestFilterExchangeBackupDetailInfoSelectors() {
stub := []string{"id-stub"}
twoStubs := []string{"smarfs", "fnords"}
any := []string{utils.Wildcard}
table := []struct {
name string
after, before, sender, subject []string
expectIncludeLen int
expectFilterLen int
}{
{
name: "no selectors",
expectIncludeLen: 0,
expectFilterLen: 0,
},
{
name: "any receivedAfter",
after: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single receivedAfter",
after: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple receivedAfter",
after: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any receivedBefore",
before: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single receivedBefore",
before: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple receivedBefore",
before: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any sender",
sender: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single sender",
sender: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple senders",
sender: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any subject",
subject: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single subject",
subject: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple subjects",
subject: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "one of each",
@ -559,19 +559,19 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() {
before: stub,
sender: stub,
subject: stub,
expectIncludeLen: 4,
expectFilterLen: 4,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := selectors.NewExchangeRestore()
includeExchangeBackupDetailInfoSelectors(
filterExchangeBackupDetailInfoSelectors(
sel,
test.after,
test.before,
test.sender,
test.subject)
assert.Equal(t, test.expectIncludeLen, len(sel.Includes))
assert.Equal(t, test.expectFilterLen, len(sel.Filters))
})
}
}

View File

@ -138,7 +138,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
emailFolder,
event,
user)
includeExchangeRestoreInfoSelectors(
filterExchangeRestoreInfoSelectors(
sel,
emailReceivedAfter,
emailReceivedBefore,
@ -218,43 +218,43 @@ func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []strin
sel.Include(sel.Events(users, events))
}
// builds the info-selector inclusions for `restore exchange`
func includeExchangeRestoreInfoSelectors(
// builds the info-selector filters for `restore exchange`
func filterExchangeRestoreInfoSelectors(
sel *selectors.ExchangeRestore,
emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string,
) {
includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
includeExchangeInfoMailSender(sel, emailSender)
includeExchangeInfoMailSubject(sel, emailSubject)
filterExchangeInfoMailReceivedAfter(sel, emailReceivedAfter)
filterExchangeInfoMailReceivedBefore(sel, emailReceivedBefore)
filterExchangeInfoMailSender(sel, emailSender)
filterExchangeInfoMailSubject(sel, emailSubject)
}
func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
func filterExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) {
if len(receivedAfter) == 0 {
return
}
sel.Include(sel.MailReceivedAfter(receivedAfter))
sel.Filter(sel.MailReceivedAfter(receivedAfter))
}
func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
func filterExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) {
if len(receivedBefore) == 0 {
return
}
sel.Include(sel.MailReceivedBefore(receivedBefore))
sel.Filter(sel.MailReceivedBefore(receivedBefore))
}
func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
func filterExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) {
if len(sender) == 0 {
return
}
sel.Include(sel.MailSender(sender))
sel.Filter(sel.MailSender(sender))
}
func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
func filterExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) {
if len(subject) == 0 {
return
}
sel.Include(sel.MailSubject(subject))
sel.Filter(sel.MailSubject(subject))
}
// checks all flags for correctness and interdependencies

View File

@ -316,78 +316,78 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
}
}
func (suite *ExchangeSuite) TestIncludeExchangeRestoreInfoSelectors() {
func (suite *ExchangeSuite) TestFilterExchangeRestoreInfoSelectors() {
stub := []string{"id-stub"}
twoStubs := []string{"a-stub", "b-stub"}
any := []string{utils.Wildcard}
table := []struct {
name string
after, before, sender, subject []string
expectIncludeLen int
expectFilterLen int
}{
{
name: "no selectors",
expectIncludeLen: 0,
expectFilterLen: 0,
},
{
name: "any receivedAfter",
after: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single receivedAfter",
after: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple receivedAfter",
after: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any receivedBefore",
before: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single receivedBefore",
before: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple receivedBefore",
before: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any senders",
sender: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single sender",
sender: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple senders",
sender: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "any subjects",
subject: any,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "single subject",
subject: stub,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "multiple subjects",
subject: twoStubs,
expectIncludeLen: 1,
expectFilterLen: 1,
},
{
name: "one of each",
@ -395,19 +395,19 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreInfoSelectors() {
before: stub,
sender: stub,
subject: stub,
expectIncludeLen: 4,
expectFilterLen: 4,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := selectors.NewExchangeRestore()
includeExchangeRestoreInfoSelectors(
filterExchangeRestoreInfoSelectors(
sel,
test.after,
test.before,
test.sender,
test.subject)
assert.Equal(t, test.expectIncludeLen, len(sel.Includes))
assert.Equal(t, test.expectFilterLen, len(sel.Filters))
})
}
}

View File

@ -95,7 +95,7 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
}
// format the details and retrieve the items from kopia
fds := er.FilterDetails(d)
fds := er.Reduce(d)
// todo: use path pkg for this
fdsPaths := fds.Paths()
paths := make([][]string, len(fdsPaths))

View File

@ -76,53 +76,63 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
// -------------------
// Exclude/Includes
// Include appends the provided scopes to the selector's inclusion set.
//
// All parts of the scope must match for data to be included.
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
// a scope value. No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to child
// properties.
// ex: User(u1) is the same as Mail(u1, Any(), Any()).
func (s *exchange) Include(scopes ...[]ExchangeScope) {
if s.Includes == nil {
s.Includes = []map[string]string{}
}
concat := []ExchangeScope{}
for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(scopeSl)...)
}
for _, sc := range concat {
s.Includes = append(s.Includes, map[string]string(sc))
}
}
// Exclude appends the provided scopes to the selector's exclusion set.
// Every Exclusion scope applies globally, affecting all inclusion scopes.
//
// All parts of the scope must match for data to be excluded.
// Ex: Mail(u1, f1, m1) => only excludes mail that is owned by user u1,
// located in folder f1, and ID'd as m1. Use selectors.Any() to wildcard
// a scope value. No value will match if selectors.None() is provided.
// All parts of the scope must match for data to be exclucded.
// Ex: Mail(u1, f1, m1) => only excludes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only excludes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically includes all mail, events, and contacts,
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Exclude(scopes ...[]ExchangeScope) {
if s.Excludes == nil {
s.Excludes = []map[string]string{}
}
concat := []ExchangeScope{}
for _, scopeSl := range scopes {
concat = append(concat, extendExchangeScopeValues(scopeSl)...)
}
for _, sc := range concat {
s.Excludes = append(s.Excludes, map[string]string(sc))
}
appendExcludes(&s.Selector, extendExchangeScopeValues, scopes...)
}
// Filter appends the provided scopes to the selector's filters set.
// A selector with >0 filters and 0 inclusions will include any data
// that passes all filters.
// A selector with >0 filters and >0 inclusions will reduce the
// inclusion set to only the data that passes all filters.
//
// All parts of the scope must match for data to pass the filter.
// Ex: Mail(u1, f1, m1) => only passes mail that is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only passes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Filter(scopes ...[]ExchangeScope) {
appendFilters(&s.Selector, extendExchangeScopeValues, scopes...)
}
// Include appends the provided scopes to the selector's inclusion set.
// Data is included if it matches ANY inclusion.
// The inclusion set is later filtered (all included data must pass ALL
// filters) and excluded (all included data must not match ANY exclusion).
//
// All parts of the scope must match for data to be included.
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only includes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Include(scopes ...[]ExchangeScope) {
appendIncludes(&s.Selector, extendExchangeScopeValues, scopes...)
}
// completes population for certain scope properties, according to the
@ -282,9 +292,8 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
}
}
// Produces one or more exchange contact info filter scopes.
// Produces one or more exchange mail received-after filter scopes.
// Matches any mail which was received after the timestring.
// One scope is created per timeString entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
@ -294,9 +303,8 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings []string) []ExchangeSco
}
}
// Produces one or more exchange mail subject filter scopes.
// Produces one or more exchange mail received-before filter scopes.
// Matches any mail whose mail subject contains one of the provided strings.
// One scope is created per subject entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
@ -306,9 +314,8 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings []string) []ExchangeSc
}
}
// Produces one or more exchange mail received-after filter scopes.
// Produces one or more exchange mail sender filter scopes.
// Matches any mail which was received after the timestring.
// One scope is created per timeString entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
@ -318,7 +325,7 @@ func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope {
}
}
// Produces one or more exchange mail received-before filter scopes.
// Produces one or more exchange mail subject line filter scopes.
// Matches any mail which was received before the timestring.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
@ -627,15 +634,16 @@ func exchangeIDPath(cat exchangeCategory, path []string) map[exchangeCategory]st
return m
}
// FilterDetails reduces the entries in a backupDetails struct to only
// those that match the inclusions and exclusions in the selector.
func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details {
// Reduce reduces the entries in a backupDetails struct to only
// those that match the inclusions, filters, and exclusions in the selector.
func (s *ExchangeRestore) Reduce(deets *backup.Details) *backup.Details {
if deets == nil {
return nil
}
entIncs := exchangeScopesByCategory(s.Includes)
entExcs := exchangeScopesByCategory(s.Excludes)
entFilt := exchangeScopesByCategory(s.Filters)
entIncs := exchangeScopesByCategory(s.Includes)
ents := []backup.DetailsEntry{}
@ -660,8 +668,9 @@ func (s *ExchangeRestore) FilterDetails(deets *backup.Details) *backup.Details {
cat,
path,
ent.Exchange,
entIncs[cat.String()],
entExcs[cat.String()])
entExcs[cat.String()],
entFilt[cat.String()],
entIncs[cat.String()])
if matched {
ents = append(ents, ent)
}
@ -695,13 +704,22 @@ func exchangeScopesByCategory(scopes []map[string]string) map[string][]ExchangeS
}
// compare each path to the included and excluded exchange scopes. Returns true
// if the path is included, and not excluded.
// if the path is included, passes filters, and not excluded.
func matchExchangeEntry(
cat exchangeCategory,
path []string,
info *backup.ExchangeInfo,
incs, excs []ExchangeScope,
excs, filts, incs []ExchangeScope,
) bool {
// a passing match requires either a filter or an inclusion
if len(incs)+len(filts) == 0 {
return false
}
// skip this check if 0 inclusions were populated
// since filters act as the inclusion check in that case
if len(incs) > 0 {
// at least one inclusion must apply.
var included bool
for _, inc := range incs {
if inc.matches(cat, path, info) {
@ -712,14 +730,21 @@ func matchExchangeEntry(
if !included {
return false
}
}
var excluded bool
// all filters must pass
for _, filt := range filts {
if !filt.matches(cat, path, info) {
return false
}
}
// any matching exclusion means failure
for _, exc := range excs {
if exc.matches(cat, path, info) {
excluded = true
break
return false
}
}
return !excluded
return true
}

View File

@ -488,7 +488,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_Get() {
}
}
func (suite *ExchangeSourceSuite) TestExchangeScope_Include_MatchesInfo() {
func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesInfo() {
es := NewExchangeRestore()
const (
sender = "smarf@2many.cooks"
@ -628,7 +628,7 @@ func (suite *ExchangeSourceSuite) TestIdPath() {
}
}
func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
func (suite *ExchangeSourceSuite) TestExchangeRestore_Reduce() {
makeDeets := func(refs ...string) *backup.Details {
deets := &backup.Details{
DetailsModel: backup.DetailsModel{
@ -773,7 +773,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() {
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := test.makeSelector()
results := sel.FilterDetails(test.deets)
results := sel.Reduce(test.deets)
paths := results.Paths()
assert.Equal(t, test.expect, paths)
})
@ -825,44 +825,48 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
}
func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
var TODO_EXCHANGE_INFO *backup.ExchangeInfo
var exchangeInfo *backup.ExchangeInfo
const (
mail = "mailID"
mid = "mailID"
cat = ExchangeMail
)
var (
es = NewExchangeRestore()
inAny = extendExchangeScopeValues(es.Users(Any()))
inNone = extendExchangeScopeValues(es.Users(None()))
inMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
inOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
exAny = extendExchangeScopeValues(es.Users(Any()))
exNone = extendExchangeScopeValues(es.Users(None()))
exMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mail}))
exOtherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
path = []string{"tid", "user", "mail", "folder", mail}
anyUser = extendExchangeScopeValues(es.Users(Any()))
noUser = extendExchangeScopeValues(es.Users(None()))
mail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{mid}))
otherMail = extendExchangeScopeValues(es.Mails(Any(), Any(), []string{"smarf"}))
noMail = extendExchangeScopeValues(es.Mails(Any(), Any(), None()))
path = []string{"tid", "user", "mail", "folder", mid}
)
table := []struct {
name string
includes []ExchangeScope
excludes []ExchangeScope
excludes, filters, includes []ExchangeScope
expect assert.BoolAssertionFunc
}{
{"empty", nil, nil, assert.False},
{"in all", inAny, nil, assert.True},
{"in None", inNone, nil, assert.False},
{"in Mail", inMail, nil, assert.True},
{"in Other", inOtherMail, nil, assert.False},
{"ex all", inAny, exAny, assert.False},
{"ex None", inAny, exNone, assert.True},
{"in Mail", inAny, exMail, assert.False},
{"in Other", inAny, exOtherMail, assert.True},
{"in and ex Mail", inMail, exMail, assert.False},
{"empty", nil, nil, nil, assert.False},
{"in Any", nil, nil, anyUser, assert.True},
{"in None", nil, nil, noUser, assert.False},
{"in Mail", nil, nil, mail, assert.True},
{"in Other", nil, nil, otherMail, assert.False},
{"in no Mail", nil, nil, noMail, assert.False},
{"ex Any", anyUser, nil, anyUser, assert.False},
{"ex Any filter", anyUser, anyUser, nil, assert.False},
{"ex None", noUser, nil, anyUser, assert.True},
{"ex None filter mail", noUser, mail, nil, assert.True},
{"ex None filter any user", noUser, anyUser, nil, assert.False},
{"ex Mail", mail, nil, anyUser, assert.False},
{"ex Other", otherMail, nil, anyUser, assert.True},
{"in and ex Mail", mail, nil, mail, assert.False},
{"filter Any", nil, anyUser, nil, assert.False},
{"filter None", nil, noUser, anyUser, assert.False},
{"filter Mail", nil, mail, anyUser, assert.True},
{"filter Other", nil, otherMail, anyUser, assert.False},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
test.expect(t, matchExchangeEntry(cat, path, TODO_EXCHANGE_INFO, test.includes, test.excludes))
test.expect(t, matchExchangeEntry(cat, path, exchangeInfo, test.excludes, test.filters, test.includes))
})
}
}

View File

@ -55,8 +55,9 @@ const (
// Is only used to pass along more specific selector instances.
type Selector struct {
Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc.
Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusions. Each exclusion applies to all inclusions.
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusions. Expected to get cast to a service wrapper within each service handler.
Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusion scopes. Exclusions apply globally to all inclusions/filters, with any-match behavior.
Filters []map[string]string `json:"filters,omitempty"` // A slice of filter scopes. All inclusions must also match ALL filters.
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusion scopes. Comparators must match either one of these, or all filters, to be included.
}
// helper for specific selector instance constructors.
@ -81,6 +82,61 @@ func None() []string {
return []string{NoneTgt}
}
type baseScope interface {
~map[string]string
}
func appendExcludes[T baseScope](
s *Selector,
tform func([]T) []T,
scopes ...[]T,
) {
if s.Excludes == nil {
s.Excludes = []map[string]string{}
}
concat := []T{}
for _, scopeSl := range scopes {
concat = append(concat, tform(scopeSl)...)
}
for _, sc := range concat {
s.Excludes = append(s.Excludes, map[string]string(sc))
}
}
func appendFilters[T baseScope](
s *Selector,
tform func([]T) []T,
scopes ...[]T,
) {
if s.Filters == nil {
s.Filters = []map[string]string{}
}
concat := []T{}
for _, scopeSl := range scopes {
concat = append(concat, tform(scopeSl)...)
}
for _, sc := range concat {
s.Filters = append(s.Filters, map[string]string(sc))
}
}
func appendIncludes[T baseScope](
s *Selector,
tform func([]T) []T,
scopes ...[]T,
) {
if s.Includes == nil {
s.Includes = []map[string]string{}
}
concat := []T{}
for _, scopeSl := range scopes {
concat = append(concat, tform(scopeSl)...)
}
for _, sc := range concat {
s.Includes = append(s.Includes, map[string]string(sc))
}
}
// ---------------------------------------------------------------------------
// Destination
// ---------------------------------------------------------------------------