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:
parent
75e8317bf9
commit
350d27dcb4
@ -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
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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{}
|
||||
appendExcludes(&s.Selector, extendExchangeScopeValues, scopes...)
|
||||
}
|
||||
concat := []ExchangeScope{}
|
||||
for _, scopeSl := range scopes {
|
||||
concat = append(concat, extendExchangeScopeValues(scopeSl)...)
|
||||
}
|
||||
for _, sc := range concat {
|
||||
s.Excludes = append(s.Excludes, map[string]string(sc))
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user