replace scope values with filters (#674)
The filters package allows callers to specify both a target to match on, and behavior of the comparison. While data- type scopes always equate to "equals", the control over different comparison behavior is useful for info-type filters. This change integrates filters into scopes for built-in control of those comparisons.
This commit is contained in:
parent
e3abc281d6
commit
6f04321a60
@ -199,13 +199,13 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() {
|
||||
name: "many users, events",
|
||||
user: []string{"fnord", "smarf"},
|
||||
data: []string{dataEvents},
|
||||
expectIncludeLen: 2,
|
||||
expectIncludeLen: 1,
|
||||
},
|
||||
{
|
||||
name: "many users, events + contacts",
|
||||
user: []string{"fnord", "smarf"},
|
||||
data: []string{dataEvents, dataContacts},
|
||||
expectIncludeLen: 4,
|
||||
expectIncludeLen: 2,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
@ -327,7 +327,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
|
||||
{
|
||||
name: "multiple users",
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 6,
|
||||
expectIncludeLen: 3,
|
||||
},
|
||||
{
|
||||
name: "any users, any data",
|
||||
@ -457,7 +457,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
|
||||
name: "many users, events",
|
||||
events: []string{"foo", "bar"},
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 2,
|
||||
expectIncludeLen: 1,
|
||||
},
|
||||
{
|
||||
name: "many users, events + contacts",
|
||||
@ -465,7 +465,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() {
|
||||
contactFolders: []string{"foo", "bar"},
|
||||
events: []string{"foo", "bar"},
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 6,
|
||||
expectIncludeLen: 2,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
|
||||
@ -162,7 +162,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
|
||||
{
|
||||
name: "multiple users",
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 6,
|
||||
expectIncludeLen: 3,
|
||||
},
|
||||
{
|
||||
name: "any users, any data",
|
||||
@ -292,7 +292,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
|
||||
name: "many users, events",
|
||||
events: []string{"foo", "bar"},
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 2,
|
||||
expectIncludeLen: 1,
|
||||
},
|
||||
{
|
||||
name: "many users, events + contacts",
|
||||
@ -300,7 +300,7 @@ func (suite *ExchangeSuite) TestIncludeExchangeRestoreDataSelectors() {
|
||||
contactFolders: []string{"foo", "bar"},
|
||||
events: []string{"foo", "bar"},
|
||||
users: []string{"fnord", "smarf"},
|
||||
expectIncludeLen: 6,
|
||||
expectIncludeLen: 2,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
|
||||
@ -266,7 +266,7 @@ func IterateFilterFolderDirectoriesForCollections(
|
||||
return true
|
||||
}
|
||||
|
||||
if !scope.Contains(selectors.ExchangeMailFolder, *folder.GetDisplayName()) {
|
||||
if !scope.Matches(selectors.ExchangeMailFolder, *folder.GetDisplayName()) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ func (pb Builder) String() string {
|
||||
return join(escaped)
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func (pb Builder) join(start, end int) string {
|
||||
return join(pb.elements[start:end])
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package filters
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type comparator int
|
||||
|
||||
@ -14,10 +16,16 @@ const (
|
||||
Less
|
||||
// a < b < c
|
||||
Between
|
||||
// "foo" contains "f"
|
||||
// "foo,bar,baz" contains "foo"
|
||||
Contains
|
||||
// "f" is found in "foo"
|
||||
// "foo" is found in "foo,bar,baz"
|
||||
In
|
||||
// always passes
|
||||
Pass
|
||||
// always fails
|
||||
Fail
|
||||
// passthrough for the target
|
||||
Identity
|
||||
)
|
||||
|
||||
const delimiter = ","
|
||||
@ -44,48 +52,72 @@ type Filter struct {
|
||||
Negate bool `json:"negate"` // when true, negate the comparator result
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Constructors
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// NewEquals creates a filter which Matches(v) is true if
|
||||
// target == v
|
||||
func NewEquals(negate bool, category any, target string) Filter {
|
||||
return Filter{Equal, category, norm(target), negate}
|
||||
return Filter{Equal, category, target, negate}
|
||||
}
|
||||
|
||||
// NewGreater creates a filter which Matches(v) is true if
|
||||
// target > v
|
||||
func NewGreater(negate bool, category any, target string) Filter {
|
||||
return Filter{Greater, category, norm(target), negate}
|
||||
return Filter{Greater, category, target, negate}
|
||||
}
|
||||
|
||||
// NewLess creates a filter which Matches(v) is true if
|
||||
// target < v
|
||||
func NewLess(negate bool, category any, target string) Filter {
|
||||
return Filter{Less, category, norm(target), negate}
|
||||
return Filter{Less, category, target, negate}
|
||||
}
|
||||
|
||||
// NewBetween creates a filter which Matches(v) is true if
|
||||
// lesser < v && v < greater
|
||||
func NewBetween(negate bool, category any, lesser, greater string) Filter {
|
||||
return Filter{Between, category, norm(join(lesser, greater)), negate}
|
||||
return Filter{Between, category, join(lesser, greater), negate}
|
||||
}
|
||||
|
||||
// NewContains creates a filter which Matches(v) is true if
|
||||
// super.Contains(v)
|
||||
func NewContains(negate bool, category any, super string) Filter {
|
||||
return Filter{Contains, category, norm(super), negate}
|
||||
return Filter{Contains, category, super, negate}
|
||||
}
|
||||
|
||||
// NewIn creates a filter which Matches(v) is true if
|
||||
// v.Contains(substr)
|
||||
func NewIn(negate bool, category any, substr string) Filter {
|
||||
return Filter{In, category, norm(substr), negate}
|
||||
return Filter{In, category, substr, negate}
|
||||
}
|
||||
|
||||
// NewPass creates a filter where Matches(v) always returns true
|
||||
func NewPass() Filter {
|
||||
return Filter{Pass, nil, "*", false}
|
||||
}
|
||||
|
||||
// NewFail creates a filter where Matches(v) always returns false
|
||||
func NewFail() Filter {
|
||||
return Filter{Fail, nil, "", false}
|
||||
}
|
||||
|
||||
// NewIdentity creates a filter intended to hold values, rather than
|
||||
// compare them. Functionally, it'll behave the same as Equals.
|
||||
func NewIdentity(id string) Filter {
|
||||
return Filter{Identity, nil, id, false}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Comparisons
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Checks whether the filter matches the input
|
||||
func (f Filter) Matches(input string) bool {
|
||||
var cmp func(string, string) bool
|
||||
|
||||
switch f.Comparator {
|
||||
case Equal:
|
||||
case Equal, Identity:
|
||||
cmp = equals
|
||||
case Greater:
|
||||
cmp = greater
|
||||
@ -97,9 +129,13 @@ func (f Filter) Matches(input string) bool {
|
||||
cmp = contains
|
||||
case In:
|
||||
cmp = in
|
||||
case Pass:
|
||||
return true
|
||||
case Fail:
|
||||
return false
|
||||
}
|
||||
|
||||
result := cmp(f.Target, norm(input))
|
||||
result := cmp(norm(f.Target), norm(input))
|
||||
if f.Negate {
|
||||
result = !result
|
||||
}
|
||||
@ -140,3 +176,39 @@ func contains(target, input string) bool {
|
||||
func in(target, input string) bool {
|
||||
return strings.Contains(input, target)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Targets returns the Target value split into a slice.
|
||||
func (f Filter) Targets() []string {
|
||||
return split(f.Target)
|
||||
}
|
||||
|
||||
func (f Filter) String() string {
|
||||
var prefix string
|
||||
|
||||
switch f.Comparator {
|
||||
case Equal:
|
||||
prefix = "eq:"
|
||||
case Greater:
|
||||
prefix = "gt:"
|
||||
case Less:
|
||||
prefix = "lt:"
|
||||
case Between:
|
||||
prefix = "btwn:"
|
||||
case Contains:
|
||||
prefix = "cont:"
|
||||
case In:
|
||||
prefix = "in:"
|
||||
case Pass:
|
||||
return "pass"
|
||||
case Fail:
|
||||
return "fail"
|
||||
case Identity:
|
||||
default: // no prefix
|
||||
}
|
||||
|
||||
return prefix + f.Target
|
||||
}
|
||||
|
||||
@ -126,6 +126,28 @@ func (suite *FiltersSuite) TestContains() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestContains_Joined() {
|
||||
makeFilt := filters.NewContains
|
||||
f := makeFilt(false, "", "smarf,userid")
|
||||
nf := makeFilt(true, "", "smarf,userid")
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
expectF assert.BoolAssertionFunc
|
||||
expectNF assert.BoolAssertionFunc
|
||||
}{
|
||||
{"userid", assert.True, assert.False},
|
||||
{"f,userid", assert.True, assert.False},
|
||||
{"fnords", assert.False, assert.True},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.input, func(t *testing.T) {
|
||||
test.expectF(t, f.Matches(test.input), "filter")
|
||||
test.expectNF(t, nf.Matches(test.input), "negated filter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn() {
|
||||
makeFilt := filters.NewIn
|
||||
f := makeFilt(false, "", "murf")
|
||||
@ -146,3 +168,24 @@ func (suite *FiltersSuite) TestIn() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn_Joined() {
|
||||
makeFilt := filters.NewIn
|
||||
f := makeFilt(false, "", "userid")
|
||||
nf := makeFilt(true, "", "userid")
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
expectF assert.BoolAssertionFunc
|
||||
expectNF assert.BoolAssertionFunc
|
||||
}{
|
||||
{"smarf,userid", assert.True, assert.False},
|
||||
{"arf,user", assert.False, assert.True},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.input, func(t *testing.T) {
|
||||
test.expectF(t, f.Matches(test.input), "filter")
|
||||
test.expectNF(t, nf.Matches(test.input), "negated filter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package selectors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -165,19 +164,13 @@ func (s *exchange) DiscreteScopes(userPNs []string) []ExchangeScope {
|
||||
// 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,
|
||||
makeScope[ExchangeScope](u, Item, ExchangeContact, contacts).set(ExchangeContactFolder, f),
|
||||
makeScope[ExchangeScope](Item, ExchangeContact, users, contacts).
|
||||
set(ExchangeContactFolder, folders),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -188,16 +181,12 @@ func (s *exchange) Contacts(users, folders, contacts []string) []ExchangeScope {
|
||||
// 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,
|
||||
makeScope[ExchangeScope](u, Group, ExchangeContactFolder, folders),
|
||||
makeScope[ExchangeScope](Group, ExchangeContactFolder, users, folders),
|
||||
)
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -208,16 +197,12 @@ func (s *exchange) ContactFolders(users, folders []string) []ExchangeScope {
|
||||
// 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,
|
||||
makeScope[ExchangeScope](u, Item, ExchangeEvent, events),
|
||||
makeScope[ExchangeScope](Item, ExchangeEvent, users, events),
|
||||
)
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -228,19 +213,13 @@ func (s *exchange) Events(users, events []string) []ExchangeScope {
|
||||
// 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,
|
||||
makeScope[ExchangeScope](u, Item, ExchangeMail, mails).set(ExchangeMailFolder, f),
|
||||
makeScope[ExchangeScope](Item, ExchangeMail, users, mails).
|
||||
set(ExchangeMailFolder, folders),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -251,16 +230,12 @@ func (s *exchange) Mails(users, folders, mails []string) []ExchangeScope {
|
||||
// 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,
|
||||
makeScope[ExchangeScope](u, Group, ExchangeMailFolder, folders),
|
||||
makeScope[ExchangeScope](Group, ExchangeMailFolder, users, folders),
|
||||
)
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -271,14 +246,13 @@ func (s *exchange) MailFolders(users, folders []string) []ExchangeScope {
|
||||
// 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{}
|
||||
|
||||
for _, u := range users {
|
||||
scopes = append(scopes, makeScope[ExchangeScope](u, Group, ExchangeContactFolder, Any()))
|
||||
scopes = append(scopes, makeScope[ExchangeScope](u, Item, ExchangeEvent, Any()))
|
||||
scopes = append(scopes, makeScope[ExchangeScope](u, Group, ExchangeMailFolder, Any()))
|
||||
}
|
||||
scopes = append(scopes,
|
||||
makeScope[ExchangeScope](Group, ExchangeContactFolder, users, Any()),
|
||||
makeScope[ExchangeScope](Item, ExchangeEvent, users, Any()),
|
||||
makeScope[ExchangeScope](Group, ExchangeMailFolder, users, Any()),
|
||||
)
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -292,7 +266,11 @@ func (s *exchange) Users(users []string) []ExchangeScope {
|
||||
// If the input is empty or selectors.None, the scope will always fail comparisons.
|
||||
func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](ExchangeMail, ExchangeFilterMailReceivedAfter, []string{timeStrings}),
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailReceivedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.NewLess)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +280,11 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope
|
||||
// If the input is empty or selectors.None, the scope will always fail comparisons.
|
||||
func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](ExchangeMail, ExchangeFilterMailReceivedBefore, []string{timeStrings}),
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailReceivedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.NewGreater)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +295,11 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScop
|
||||
// If any slice is empty, it defaults to [selectors.None]
|
||||
func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](ExchangeMail, ExchangeFilterMailSender, senderIDs),
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailSender,
|
||||
senderIDs,
|
||||
wrapFilter(filters.NewIn)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +310,11 @@ func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope {
|
||||
// If any slice is empty, it defaults to [selectors.None]
|
||||
func (sr *ExchangeRestore) MailSubject(subjectSubstrings []string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](ExchangeMail, ExchangeFilterMailSubject, subjectSubstrings),
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailSubject,
|
||||
subjectSubstrings,
|
||||
wrapFilter(filters.NewIn)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +336,7 @@ func (d ExchangeDestination) GetOrDefault(cat exchangeCategory, current string)
|
||||
return current
|
||||
}
|
||||
|
||||
return dest
|
||||
return dest.Target
|
||||
}
|
||||
|
||||
// Sets the destination value of the provided category. Returns an error
|
||||
@ -358,10 +348,10 @@ func (d ExchangeDestination) Set(cat exchangeCategory, dest string) error {
|
||||
|
||||
cs := cat.String()
|
||||
if curr, ok := d[cs]; ok {
|
||||
return existingDestinationErr(cs, curr)
|
||||
return existingDestinationErr(cs, curr.Target)
|
||||
}
|
||||
|
||||
d[cs] = dest
|
||||
d[cs] = filterize(dest)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -410,6 +400,8 @@ func (ec exchangeCategory) String() string {
|
||||
// leafCat returns the leaf category of the receiver.
|
||||
// If the receiver category has multiple leaves (ex: User) or no leaves,
|
||||
// (ex: Unknown), the receiver itself is returned.
|
||||
// If the receiver category is a filter type (ex: ExchangeFilterMailSubject),
|
||||
// returns the category covered by the filter.
|
||||
// Ex: ExchangeContactFolder.leafCat() => ExchangeContact
|
||||
// Ex: ExchangeEvent.leafCat() => ExchangeEvent
|
||||
// Ex: ExchangeUser.leafCat() => ExchangeUser
|
||||
@ -417,7 +409,8 @@ func (ec exchangeCategory) leafCat() categorizer {
|
||||
switch ec {
|
||||
case ExchangeContact, ExchangeContactFolder:
|
||||
return ExchangeContact
|
||||
case ExchangeMail, ExchangeMailFolder:
|
||||
case ExchangeMail, ExchangeMailFolder, ExchangeFilterMailReceivedAfter,
|
||||
ExchangeFilterMailReceivedBefore, ExchangeFilterMailSender, ExchangeFilterMailSubject:
|
||||
return ExchangeMail
|
||||
}
|
||||
|
||||
@ -504,7 +497,7 @@ var _ scoper = &ExchangeScope{}
|
||||
|
||||
// Category describes the type of the data in scope.
|
||||
func (s ExchangeScope) Category() exchangeCategory {
|
||||
return exchangeCategory(s[scopeKeyCategory])
|
||||
return exchangeCategory(getCategory(s))
|
||||
}
|
||||
|
||||
// categorizer type is a generic wrapper around Category.
|
||||
@ -513,22 +506,22 @@ func (s ExchangeScope) categorizer() categorizer {
|
||||
return s.Category()
|
||||
}
|
||||
|
||||
// Contains returns true if the category is included in the scope's
|
||||
// data type, and the target string is included in the scope.
|
||||
func (s ExchangeScope) Contains(cat exchangeCategory, target string) bool {
|
||||
return contains(s, cat, target)
|
||||
// Matches returns true if the category is included in the scope's
|
||||
// data type, and the target string matches that category's comparator.
|
||||
func (s ExchangeScope) Matches(cat exchangeCategory, target string) bool {
|
||||
return matches(s, cat, target)
|
||||
}
|
||||
|
||||
// FilterCategory returns the category enum of the scope filter.
|
||||
// If the scope is not a filter type, returns ExchangeUnknownCategory.
|
||||
func (s ExchangeScope) FilterCategory() exchangeCategory {
|
||||
return exchangeCategory(s[scopeKeyInfoFilter])
|
||||
return exchangeCategory(getFilterCategory(s))
|
||||
}
|
||||
|
||||
// Granularity describes the granularity (directory || item)
|
||||
// of the data in scope.
|
||||
func (s ExchangeScope) Granularity() string {
|
||||
return s[scopeKeyGranularity]
|
||||
return getGranularity(s)
|
||||
}
|
||||
|
||||
// IncludeCategory checks whether the scope includes a certain category of data.
|
||||
@ -552,7 +545,7 @@ func (s ExchangeScope) Get(cat exchangeCategory) []string {
|
||||
}
|
||||
|
||||
// sets a value by category to the scope. Only intended for internal use.
|
||||
func (s ExchangeScope) set(cat exchangeCategory, v string) ExchangeScope {
|
||||
func (s ExchangeScope) set(cat exchangeCategory, v []string) ExchangeScope {
|
||||
return set(s, cat, v)
|
||||
}
|
||||
|
||||
@ -561,15 +554,15 @@ func (s ExchangeScope) set(cat exchangeCategory, v string) ExchangeScope {
|
||||
func (s ExchangeScope) setDefaults() {
|
||||
switch s.Category() {
|
||||
case ExchangeContactFolder:
|
||||
s[ExchangeContact.String()] = AnyTgt
|
||||
s[ExchangeContact.String()] = passAny
|
||||
case ExchangeMailFolder:
|
||||
s[ExchangeMail.String()] = AnyTgt
|
||||
s[ExchangeMail.String()] = passAny
|
||||
case ExchangeUser:
|
||||
s[ExchangeContactFolder.String()] = AnyTgt
|
||||
s[ExchangeContact.String()] = AnyTgt
|
||||
s[ExchangeEvent.String()] = AnyTgt
|
||||
s[ExchangeMailFolder.String()] = AnyTgt
|
||||
s[ExchangeMail.String()] = AnyTgt
|
||||
s[ExchangeContactFolder.String()] = passAny
|
||||
s[ExchangeContact.String()] = passAny
|
||||
s[ExchangeEvent.String()] = passAny
|
||||
s[ExchangeMailFolder.String()] = passAny
|
||||
s[ExchangeMail.String()] = passAny
|
||||
}
|
||||
}
|
||||
|
||||
@ -609,43 +602,19 @@ func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// the scope must define targets to match on
|
||||
filterCat := s.FilterCategory()
|
||||
targets := s.Get(filterCat)
|
||||
i := ""
|
||||
|
||||
if len(targets) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if targets[0] == AnyTgt {
|
||||
return true
|
||||
}
|
||||
|
||||
if targets[0] == NoneTgt {
|
||||
return false
|
||||
}
|
||||
|
||||
// any of the targets for a given info filter may succeed.
|
||||
for _, target := range targets {
|
||||
switch filterCat {
|
||||
case ExchangeFilterMailSender:
|
||||
if target == info.Sender {
|
||||
return true
|
||||
}
|
||||
i = info.Sender
|
||||
case ExchangeFilterMailSubject:
|
||||
if strings.Contains(info.Subject, target) {
|
||||
return true
|
||||
}
|
||||
i = info.Subject
|
||||
case ExchangeFilterMailReceivedAfter:
|
||||
if target < common.FormatTime(info.Received) {
|
||||
return true
|
||||
}
|
||||
i = common.FormatTime(info.Received)
|
||||
case ExchangeFilterMailReceivedBefore:
|
||||
if target > common.FormatTime(info.Received) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
i = common.FormatTime(info.Received)
|
||||
}
|
||||
|
||||
return false
|
||||
return s.Matches(filterCat, i)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
type ExchangeSelectorSuite struct {
|
||||
@ -69,10 +70,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Contacts() {
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeContactFolder.String()], folder)
|
||||
assert.Equal(t, scope[ExchangeContact.String()], join(c1, c2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeContactFolder: folder,
|
||||
ExchangeContact: join(c1, c2),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Contacts() {
|
||||
@ -90,10 +96,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Contacts() {
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeContactFolder.String()], folder)
|
||||
assert.Equal(t, scope[ExchangeContact.String()], join(c1, c2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeContactFolder: folder,
|
||||
ExchangeContact: join(c1, c2),
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContact)
|
||||
}
|
||||
@ -112,10 +123,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_ContactFolders(
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
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()], AnyTgt)
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeContactFolder: join(f1, f2),
|
||||
ExchangeContact: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_ContactFolders() {
|
||||
@ -132,10 +148,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_ContactFolders(
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
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()], AnyTgt)
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeContactFolder: join(f1, f2),
|
||||
ExchangeContact: AnyTgt,
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder)
|
||||
}
|
||||
@ -154,9 +175,14 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeEvent.String()], join(e1, e2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeEvent: join(e1, e2),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
|
||||
@ -173,9 +199,14 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeEvent.String()], join(e1, e2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeEvent: join(e1, e2),
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeEvent)
|
||||
}
|
||||
@ -195,10 +226,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() {
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeMailFolder.String()], folder)
|
||||
assert.Equal(t, scope[ExchangeMail.String()], join(m1, m2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeMailFolder: folder,
|
||||
ExchangeMail: join(m1, m2),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Mails() {
|
||||
@ -216,10 +252,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Mails() {
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
scope := scopes[0]
|
||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||
assert.Equal(t, scope[ExchangeMailFolder.String()], folder)
|
||||
assert.Equal(t, scope[ExchangeMail.String()], join(m1, m2))
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeMailFolder: folder,
|
||||
ExchangeMail: join(m1, m2),
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMail)
|
||||
}
|
||||
@ -238,10 +279,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_MailFolders() {
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
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()], AnyTgt)
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeMailFolder: join(f1, f2),
|
||||
ExchangeMail: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_MailFolders() {
|
||||
@ -258,10 +304,15 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_MailFolders() {
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
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()], AnyTgt)
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeUser: user,
|
||||
ExchangeMailFolder: join(f1, f2),
|
||||
ExchangeMail: AnyTgt,
|
||||
},
|
||||
)
|
||||
|
||||
assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder)
|
||||
}
|
||||
@ -277,23 +328,45 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Users() {
|
||||
|
||||
sel.Exclude(sel.Users([]string{u1, u2}))
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 6)
|
||||
require.Len(t, scopes, 3)
|
||||
|
||||
for _, scope := range scopes {
|
||||
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
||||
for _, sc := range scopes {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{ExchangeUser: join(u1, u2)},
|
||||
)
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeContactFolder.String() {
|
||||
assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
|
||||
assert.Equal(t, scope[ExchangeContactFolder.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeContactFolder.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeContact: AnyTgt,
|
||||
ExchangeContactFolder: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeEvent.String() {
|
||||
assert.Equal(t, scope[ExchangeEvent.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeEvent.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeEvent: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeMailFolder.String() {
|
||||
assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
|
||||
assert.Equal(t, scope[ExchangeMailFolder.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeMailFolder.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeMail: AnyTgt,
|
||||
ExchangeMailFolder: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,23 +382,45 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Users() {
|
||||
|
||||
sel.Include(sel.Users([]string{u1, u2}))
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 6)
|
||||
require.Len(t, scopes, 3)
|
||||
|
||||
for _, scope := range scopes {
|
||||
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
||||
for _, sc := range scopes {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{ExchangeUser: join(u1, u2)},
|
||||
)
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeContactFolder.String() {
|
||||
assert.Equal(t, scope[ExchangeContact.String()], AnyTgt)
|
||||
assert.Equal(t, scope[ExchangeContactFolder.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeContactFolder.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeContact: AnyTgt,
|
||||
ExchangeContactFolder: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeEvent.String() {
|
||||
assert.Equal(t, scope[ExchangeEvent.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeEvent.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeEvent: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if scope[scopeKeyCategory] == ExchangeMailFolder.String() {
|
||||
assert.Equal(t, scope[ExchangeMail.String()], AnyTgt)
|
||||
assert.Equal(t, scope[ExchangeMailFolder.String()], AnyTgt)
|
||||
if sc[scopeKeyCategory].Matches(ExchangeMailFolder.String()) {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeMail: AnyTgt,
|
||||
ExchangeMailFolder: AnyTgt,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -470,7 +565,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_Category() {
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.is.String()+test.expect.String(), func(t *testing.T) {
|
||||
eb := NewExchangeBackup()
|
||||
eb.Includes = []scope{{scopeKeyCategory: test.is.String()}}
|
||||
eb.Includes = []scope{
|
||||
{scopeKeyCategory: filters.NewIdentity(test.is.String())},
|
||||
}
|
||||
scope := eb.Scopes()[0]
|
||||
test.check(t, test.expect, scope.Category())
|
||||
})
|
||||
@ -502,7 +599,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_IncludesCategory() {
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.is.String()+test.expect.String(), func(t *testing.T) {
|
||||
eb := NewExchangeBackup()
|
||||
eb.Includes = []scope{{scopeKeyCategory: test.is.String()}}
|
||||
eb.Includes = []scope{
|
||||
{scopeKeyCategory: filters.NewIdentity(test.is.String())},
|
||||
}
|
||||
scope := eb.Scopes()[0]
|
||||
test.check(t, scope.IncludesCategory(test.expect))
|
||||
})
|
||||
@ -984,7 +1083,7 @@ func (suite *ExchangeSelectorSuite) TestContains() {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
var result bool
|
||||
for _, scope := range test.scopes {
|
||||
if scope.Contains(ExchangeMail, target) {
|
||||
if scope.Matches(ExchangeMail, target) {
|
||||
result = true
|
||||
break
|
||||
}
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
package selectors
|
||||
|
||||
import "github.com/alcionai/corso/pkg/backup/details"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// categorizers
|
||||
@ -58,7 +65,7 @@ type mockScope scope
|
||||
var _ scoper = &mockScope{}
|
||||
|
||||
func (ms mockScope) categorizer() categorizer {
|
||||
switch ms[scopeKeyCategory] {
|
||||
switch ms[scopeKeyCategory].Target {
|
||||
case rootCatStub.String():
|
||||
return rootCatStub
|
||||
case leafCatStub.String():
|
||||
@ -73,7 +80,7 @@ func (ms mockScope) matchesEntry(
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
) bool {
|
||||
return ms[shouldMatch] == "true"
|
||||
return ms[shouldMatch].Target == "true"
|
||||
}
|
||||
|
||||
func (ms mockScope) setDefaults() {}
|
||||
@ -91,12 +98,12 @@ func stubScope(match string) mockScope {
|
||||
}
|
||||
|
||||
return mockScope{
|
||||
rootCatStub.String(): AnyTgt,
|
||||
scopeKeyCategory: rootCatStub.String(),
|
||||
scopeKeyGranularity: Item,
|
||||
scopeKeyResource: stubResource,
|
||||
scopeKeyDataType: rootCatStub.String(),
|
||||
shouldMatch: sm,
|
||||
rootCatStub.String(): passAny,
|
||||
scopeKeyCategory: filters.NewIdentity(rootCatStub.String()),
|
||||
scopeKeyGranularity: filters.NewIdentity(Item),
|
||||
scopeKeyResource: filters.NewIdentity(stubResource),
|
||||
scopeKeyDataType: filters.NewIdentity(rootCatStub.String()),
|
||||
shouldMatch: filters.NewIdentity(sm),
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,3 +131,12 @@ func setScopesToDefault[T scopeT](ts []T) []T {
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// calls assert.Equal(t, getCatValue(sc, k)[0], v) on each k:v pair in the map
|
||||
func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer]string) {
|
||||
for k, v := range m {
|
||||
t.Run(k.String(), func(t *testing.T) {
|
||||
assert.Equal(t, getCatValue(sc, k), split(v))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,12 +111,9 @@ func (s *oneDrive) Filter(scopes ...[]OneDriveScope) {
|
||||
// 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 *oneDrive) Users(users []string) []OneDriveScope {
|
||||
users = normalize(users)
|
||||
scopes := []OneDriveScope{}
|
||||
|
||||
for _, u := range users {
|
||||
scopes = append(scopes, makeScope[OneDriveScope](u, Group, OneDriveUser, users))
|
||||
}
|
||||
scopes = append(scopes, makeScope[OneDriveScope](Group, OneDriveUser, users, users))
|
||||
|
||||
return scopes
|
||||
}
|
||||
@ -226,7 +223,7 @@ var _ scoper = &OneDriveScope{}
|
||||
|
||||
// Category describes the type of the data in scope.
|
||||
func (s OneDriveScope) Category() oneDriveCategory {
|
||||
return oneDriveCategory(s[scopeKeyCategory])
|
||||
return oneDriveCategory(getCategory(s))
|
||||
}
|
||||
|
||||
// categorizer type is a generic wrapper around Category.
|
||||
@ -238,13 +235,13 @@ func (s OneDriveScope) categorizer() categorizer {
|
||||
// FilterCategory returns the category enum of the scope filter.
|
||||
// If the scope is not a filter type, returns OneDriveUnknownCategory.
|
||||
func (s OneDriveScope) FilterCategory() oneDriveCategory {
|
||||
return oneDriveCategory(s[scopeKeyInfoFilter])
|
||||
return oneDriveCategory(getFilterCategory(s))
|
||||
}
|
||||
|
||||
// Granularity describes the granularity (directory || item)
|
||||
// of the data in scope.
|
||||
func (s OneDriveScope) Granularity() string {
|
||||
return s[scopeKeyGranularity]
|
||||
return getGranularity(s)
|
||||
}
|
||||
|
||||
// IncludeCategory checks whether the scope includes a
|
||||
@ -255,10 +252,10 @@ func (s OneDriveScope) IncludesCategory(cat oneDriveCategory) bool {
|
||||
return categoryMatches(s.Category(), cat)
|
||||
}
|
||||
|
||||
// Contains returns true if the category is included in the scope's
|
||||
// data type, and the target string is included in the scope.
|
||||
func (s OneDriveScope) Contains(cat oneDriveCategory, target string) bool {
|
||||
return contains(s, cat, target)
|
||||
// Matches returns true if the category is included in the scope's
|
||||
// data type, and the target string matches that category's comparator.
|
||||
func (s OneDriveScope) Matches(cat oneDriveCategory, target string) bool {
|
||||
return matches(s, cat, target)
|
||||
}
|
||||
|
||||
// returns true if the category is included in the scope's data type,
|
||||
|
||||
@ -87,7 +87,7 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Users() {
|
||||
userScopes := sel.Users([]string{u1, u2})
|
||||
for _, scope := range userScopes {
|
||||
// Scope value is either u1 or u2
|
||||
assert.Contains(t, []string{u1, u2}, scope[OneDriveUser.String()])
|
||||
assert.Contains(t, join(u1, u2), scope[OneDriveUser.String()].Target)
|
||||
}
|
||||
|
||||
// Initialize the selector Include, Exclude, Filter
|
||||
@ -105,10 +105,10 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Users() {
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, 2, len(test.scopesToCheck))
|
||||
require.Len(t, test.scopesToCheck, 1)
|
||||
for _, scope := range test.scopesToCheck {
|
||||
// Scope value is u1,u2
|
||||
assert.Contains(t, join(u1, u2), scope[OneDriveUser.String()])
|
||||
assert.Contains(t, join(u1, u2), scope[OneDriveUser.String()].Target)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -125,10 +125,14 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Include_Users() {
|
||||
|
||||
sel.Include(sel.Users([]string{u1, u2}))
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 2)
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
for _, scope := range scopes {
|
||||
assert.Contains(t, join(u1, u2), scope[OneDriveUser.String()])
|
||||
for _, sc := range scopes {
|
||||
scopeMustHave(
|
||||
t,
|
||||
OneDriveScope(sc),
|
||||
map[categorizer]string{OneDriveUser: join(u1, u2)},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,9 +147,13 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Exclude_Users() {
|
||||
|
||||
sel.Exclude(sel.Users([]string{u1, u2}))
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 2)
|
||||
require.Len(t, scopes, 1)
|
||||
|
||||
for _, scope := range scopes {
|
||||
assert.Contains(t, join(u1, u2), scope[OneDriveUser.String()])
|
||||
for _, sc := range scopes {
|
||||
scopeMustHave(
|
||||
t,
|
||||
OneDriveScope(sc),
|
||||
map[categorizer]string{OneDriveUser: join(u1, u2)},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ package selectors
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -75,7 +75,7 @@ type (
|
||||
// (human readable), or whether the scope is a filter-type or an inclusion-/exclusion-type.
|
||||
// Metadata values can be used in either logical processing of scopes, and/or for presentation
|
||||
// to end users.
|
||||
scope map[string]string
|
||||
scope map[string]filters.Filter
|
||||
|
||||
// scoper describes the minimum necessary interface that a soundly built scope should
|
||||
// comply with.
|
||||
@ -109,24 +109,24 @@ type (
|
||||
}
|
||||
// scopeT is the generic type interface of a scoper.
|
||||
scopeT interface {
|
||||
~map[string]string
|
||||
~map[string]filters.Filter
|
||||
scoper
|
||||
}
|
||||
)
|
||||
|
||||
// makeScope produces a well formatted, typed scope that ensures all base values are populated.
|
||||
func makeScope[T scopeT](
|
||||
resource, granularity string,
|
||||
granularity string,
|
||||
cat categorizer,
|
||||
vs []string,
|
||||
resources, vs []string,
|
||||
) T {
|
||||
s := T{
|
||||
scopeKeyCategory: cat.String(),
|
||||
scopeKeyDataType: cat.leafCat().String(),
|
||||
scopeKeyGranularity: granularity,
|
||||
scopeKeyResource: resource,
|
||||
cat.String(): join(vs...),
|
||||
cat.rootCat().String(): resource,
|
||||
scopeKeyCategory: filters.NewIdentity(cat.String()),
|
||||
scopeKeyDataType: filters.NewIdentity(cat.leafCat().String()),
|
||||
scopeKeyGranularity: filters.NewIdentity(granularity),
|
||||
scopeKeyResource: filters.NewIdentity(join(resources...)),
|
||||
cat.String(): filterize(vs...),
|
||||
cat.rootCat().String(): filterize(resources...),
|
||||
}
|
||||
|
||||
return s
|
||||
@ -137,14 +137,15 @@ func makeScope[T scopeT](
|
||||
func makeFilterScope[T scopeT](
|
||||
cat, filterCat categorizer,
|
||||
vs []string,
|
||||
f func([]string) filters.Filter,
|
||||
) T {
|
||||
return T{
|
||||
scopeKeyCategory: cat.String(),
|
||||
scopeKeyDataType: cat.leafCat().String(),
|
||||
scopeKeyGranularity: Filter,
|
||||
scopeKeyInfoFilter: filterCat.String(),
|
||||
scopeKeyResource: Filter,
|
||||
filterCat.String(): join(vs...),
|
||||
scopeKeyCategory: filters.NewIdentity(cat.String()),
|
||||
scopeKeyDataType: filters.NewIdentity(cat.leafCat().String()),
|
||||
scopeKeyGranularity: filters.NewIdentity(Filter),
|
||||
scopeKeyInfoFilter: filters.NewIdentity(filterCat.String()),
|
||||
scopeKeyResource: filters.NewIdentity(Filter),
|
||||
filterCat.String(): f(clean(vs)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,9 +153,9 @@ func makeFilterScope[T scopeT](
|
||||
// scope funcs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// contains returns true if the category is included in the scope's
|
||||
// matches returns true if the category is included in the scope's
|
||||
// data type, and the target string is included in the scope.
|
||||
func contains[T scopeT, C categoryT](s T, cat C, target string) bool {
|
||||
func matches[T scopeT, C categoryT](s T, cat C, target string) bool {
|
||||
if !typeAndCategoryMatches(cat, s.categorizer()) {
|
||||
return false
|
||||
}
|
||||
@ -163,20 +164,23 @@ func contains[T scopeT, C categoryT](s T, cat C, target string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
compare := s[cat.String()]
|
||||
if len(compare) == 0 {
|
||||
return false
|
||||
return s[cat.String()].Matches(target)
|
||||
}
|
||||
|
||||
if compare == NoneTgt {
|
||||
return false
|
||||
// getCategory returns the scope's category value.
|
||||
// if s is a filter-type scope, returns the filter category.
|
||||
func getCategory[T scopeT](s T) string {
|
||||
return s[scopeKeyCategory].Target
|
||||
}
|
||||
|
||||
if compare == AnyTgt {
|
||||
return true
|
||||
// getFilterCategory returns the scope's infoFilter category value.
|
||||
func getFilterCategory[T scopeT](s T) string {
|
||||
return s[scopeKeyInfoFilter].Target
|
||||
}
|
||||
|
||||
return strings.Contains(compare, target)
|
||||
// getGranularity returns the scope's granularity value.
|
||||
func getGranularity[T scopeT](s T) string {
|
||||
return s[scopeKeyGranularity].Target
|
||||
}
|
||||
|
||||
// getCatValue takes the value of s[cat], split it by the standard
|
||||
@ -188,20 +192,20 @@ func getCatValue[T scopeT](s T, cat categorizer) []string {
|
||||
return None()
|
||||
}
|
||||
|
||||
return split(v)
|
||||
return split(v.Target)
|
||||
}
|
||||
|
||||
// set sets a value by category to the scope. Only intended for internal
|
||||
// use, not for exporting to callers.
|
||||
func set[T scopeT](s T, cat categorizer, v string) T {
|
||||
s[cat.String()] = v
|
||||
func set[T scopeT](s T, cat categorizer, v []string) T {
|
||||
s[cat.String()] = filterize(v...)
|
||||
return s
|
||||
}
|
||||
|
||||
// granularity describes the granularity (directory || item)
|
||||
// of the data in scope.
|
||||
func granularity[T scopeT](s T) string {
|
||||
return s[scopeKeyGranularity]
|
||||
return s[scopeKeyGranularity].Target
|
||||
}
|
||||
|
||||
// returns true if the category is included in the scope's category type,
|
||||
@ -211,7 +215,7 @@ func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return s[cat.String()] == AnyTgt
|
||||
return s[cat.String()].Target == AnyTgt
|
||||
}
|
||||
|
||||
// reduce filters the entries in the details to only those that match the
|
||||
@ -227,9 +231,9 @@ func reduce[T scopeT, C categoryT](
|
||||
}
|
||||
|
||||
// aggregate each scope type by category for easier isolation in future processing.
|
||||
excludes := scopesByCategory[T](s.Excludes, dataCategories)
|
||||
filters := scopesByCategory[T](s.Filters, dataCategories)
|
||||
includes := scopesByCategory[T](s.Includes, dataCategories)
|
||||
excls := scopesByCategory[T](s.Excludes, dataCategories)
|
||||
filts := scopesByCategory[T](s.Filters, dataCategories)
|
||||
incls := scopesByCategory[T](s.Includes, dataCategories)
|
||||
|
||||
ents := []details.DetailsEntry{}
|
||||
|
||||
@ -247,9 +251,9 @@ func reduce[T scopeT, C categoryT](
|
||||
dc,
|
||||
dc.pathValues(path),
|
||||
ent,
|
||||
excludes[dc],
|
||||
filters[dc],
|
||||
includes[dc],
|
||||
excls[dc],
|
||||
filts[dc],
|
||||
incls[dc],
|
||||
)
|
||||
if passed {
|
||||
ents = append(ents, ent)
|
||||
@ -381,25 +385,32 @@ func matchesPathValues[T scopeT, C categoryT](
|
||||
cat C,
|
||||
pathValues map[categorizer]string,
|
||||
) bool {
|
||||
// if scope specifies a filter category,
|
||||
// path checking is automatically skipped.
|
||||
if len(getFilterCategory(sc)) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range cat.pathKeys() {
|
||||
target := getCatValue(sc, c)
|
||||
scopeVals := getCatValue(sc, c)
|
||||
// the scope must define the targets to match on
|
||||
if len(target) == 0 {
|
||||
if len(scopeVals) == 0 {
|
||||
return false
|
||||
}
|
||||
// None() fails all matches
|
||||
if target[0] == NoneTgt {
|
||||
if scopeVals[0] == NoneTgt {
|
||||
return false
|
||||
}
|
||||
// the path must contain a value to match against
|
||||
pv, ok := pathValues[c]
|
||||
pathVal, ok := pathValues[c]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// all parts of the scope must match
|
||||
cc := c.(C)
|
||||
if !isAnyTarget(sc, cc) {
|
||||
if !common.ContainsString(target, pv) {
|
||||
f := filters.NewContains(false, cc, join(scopeVals...))
|
||||
if !f.Matches(pathVal) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -42,7 +43,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "none",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = NoneTgt
|
||||
stub[rootCatStub.String()] = failAny
|
||||
return stub
|
||||
},
|
||||
check: rootCatStub.String(),
|
||||
@ -52,7 +53,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "blank value",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = ""
|
||||
stub[rootCatStub.String()] = filters.NewEquals(false, nil, "")
|
||||
return stub
|
||||
},
|
||||
check: rootCatStub.String(),
|
||||
@ -62,7 +63,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "blank target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = "fnords"
|
||||
stub[rootCatStub.String()] = filterize("fnords")
|
||||
return stub
|
||||
},
|
||||
check: "",
|
||||
@ -72,7 +73,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "matching target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = rootCatStub.String()
|
||||
stub[rootCatStub.String()] = filterize(rootCatStub.String())
|
||||
return stub
|
||||
},
|
||||
check: rootCatStub.String(),
|
||||
@ -82,7 +83,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "non-matching target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = rootCatStub.String()
|
||||
stub[rootCatStub.String()] = filterize(rootCatStub.String())
|
||||
return stub
|
||||
},
|
||||
check: "smarf",
|
||||
@ -93,7 +94,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
test.expect(
|
||||
t,
|
||||
contains(test.scope(), rootCatStub, test.check))
|
||||
matches(test.scope(), rootCatStub, test.check))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -101,8 +102,10 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
func (suite *SelectorScopesSuite) TestGetCatValue() {
|
||||
t := suite.T()
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = rootCatStub.String()
|
||||
assert.Equal(t, []string{rootCatStub.String()}, getCatValue(stub, rootCatStub))
|
||||
stub[rootCatStub.String()] = filterize(rootCatStub.String())
|
||||
assert.Equal(t,
|
||||
[]string{rootCatStub.String()},
|
||||
getCatValue(stub, rootCatStub))
|
||||
assert.Equal(t, None(), getCatValue(stub, leafCatStub))
|
||||
}
|
||||
|
||||
@ -250,7 +253,7 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||
t := suite.T()
|
||||
s1 := stubScope("")
|
||||
s2 := stubScope("")
|
||||
s2[scopeKeyCategory] = unknownCatStub.String()
|
||||
s2[scopeKeyCategory] = filterize(unknownCatStub.String())
|
||||
result := scopesByCategory[mockScope](
|
||||
[]scope{scope(s1), scope(s2)},
|
||||
map[pathType]mockCategorizer{
|
||||
@ -297,22 +300,158 @@ func toMockScope(sc []scope) []mockScope {
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestMatchesPathValues() {
|
||||
t := suite.T()
|
||||
cat := rootCatStub
|
||||
sc := stubScope("")
|
||||
sc[rootCatStub.String()] = rootCatStub.String()
|
||||
sc[leafCatStub.String()] = leafCatStub.String()
|
||||
pvs := stubPathValues()
|
||||
assert.True(t, matchesPathValues(sc, cat, pvs), "matching values")
|
||||
// "any" seems like it should pass, but this is the path value,
|
||||
// not the scope value, so unless the scope is also "any", it fails.
|
||||
pvs[rootCatStub] = AnyTgt
|
||||
pvs[leafCatStub] = AnyTgt
|
||||
assert.False(t, matchesPathValues(sc, cat, pvs), "any")
|
||||
pvs[rootCatStub] = NoneTgt
|
||||
pvs[leafCatStub] = NoneTgt
|
||||
assert.False(t, matchesPathValues(sc, cat, pvs), "none")
|
||||
pvs[rootCatStub] = "foo"
|
||||
pvs[leafCatStub] = "bar"
|
||||
assert.False(t, matchesPathValues(sc, cat, pvs), "mismatched values")
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
rootVal string
|
||||
leafVal string
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "matching values",
|
||||
rootVal: rootCatStub.String(),
|
||||
leafVal: leafCatStub.String(),
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
name: "any",
|
||||
rootVal: AnyTgt,
|
||||
leafVal: AnyTgt,
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
rootVal: NoneTgt,
|
||||
leafVal: NoneTgt,
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
name: "mismatched values",
|
||||
rootVal: "fnords",
|
||||
leafVal: "smarf",
|
||||
expect: assert.False,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
sc := stubScope("")
|
||||
sc[rootCatStub.String()] = filterize(test.rootVal)
|
||||
sc[leafCatStub.String()] = filterize(test.leafVal)
|
||||
|
||||
test.expect(t, matchesPathValues(sc, cat, pvs))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestAddToSet() {
|
||||
t := suite.T()
|
||||
set := []string{}
|
||||
|
||||
set = addToSet(set, []string{})
|
||||
assert.Len(t, set, 0)
|
||||
|
||||
set = addToSet(set, []string{"a"})
|
||||
assert.Len(t, set, 1)
|
||||
assert.Equal(t, set[0], "a")
|
||||
|
||||
set = addToSet(set, []string{"a"})
|
||||
assert.Len(t, set, 1)
|
||||
|
||||
set = addToSet(set, []string{"a", "b"})
|
||||
assert.Len(t, set, 2)
|
||||
assert.Equal(t, set[0], "a")
|
||||
assert.Equal(t, set[1], "b")
|
||||
|
||||
set = addToSet(set, []string{"c", "d"})
|
||||
assert.Len(t, set, 4)
|
||||
assert.Equal(t, set[0], "a")
|
||||
assert.Equal(t, set[1], "b")
|
||||
assert.Equal(t, set[2], "c")
|
||||
assert.Equal(t, set[3], "d")
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestClean() {
|
||||
table := []struct {
|
||||
name string
|
||||
input []string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
input: nil,
|
||||
expect: None(),
|
||||
},
|
||||
{
|
||||
name: "has anyTgt",
|
||||
input: []string{"a", AnyTgt},
|
||||
expect: Any(),
|
||||
},
|
||||
{
|
||||
name: "has noneTgt",
|
||||
input: []string{"a", NoneTgt},
|
||||
expect: None(),
|
||||
},
|
||||
{
|
||||
name: "has anyTgt and noneTgt, any first",
|
||||
input: []string{"a", AnyTgt, NoneTgt},
|
||||
expect: Any(),
|
||||
},
|
||||
{
|
||||
name: "has noneTgt and anyTgt, none first",
|
||||
input: []string{"a", NoneTgt, AnyTgt},
|
||||
expect: None(),
|
||||
},
|
||||
{
|
||||
name: "already clean",
|
||||
input: []string{"a", "b"},
|
||||
expect: []string{"a", "b"},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
result := clean(test.input)
|
||||
assert.Equal(t, result, test.expect)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestWrapFilter() {
|
||||
table := []struct {
|
||||
name string
|
||||
filter filterFunc
|
||||
input []string
|
||||
comparator int
|
||||
target string
|
||||
}{
|
||||
{
|
||||
name: "any",
|
||||
filter: filters.NewContains,
|
||||
input: Any(),
|
||||
comparator: int(filters.Pass),
|
||||
target: AnyTgt,
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
filter: filters.NewIn,
|
||||
input: None(),
|
||||
comparator: int(filters.Fail),
|
||||
target: NoneTgt,
|
||||
},
|
||||
{
|
||||
name: "something",
|
||||
filter: filters.NewEquals,
|
||||
input: []string{"userid"},
|
||||
comparator: int(filters.Equal),
|
||||
target: "userid",
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
ff := wrapFilter(test.filter)(test.input)
|
||||
assert.Equal(t, int(ff.Comparator), test.comparator)
|
||||
assert.Equal(t, ff.Target, test.target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/pkg/filters"
|
||||
)
|
||||
|
||||
type service int
|
||||
@ -53,6 +55,11 @@ const (
|
||||
delimiter = ","
|
||||
)
|
||||
|
||||
var (
|
||||
passAny = filters.NewPass()
|
||||
failAny = filters.NewFail()
|
||||
)
|
||||
|
||||
// All is the resource name that gets output when the resource is AnyTgt.
|
||||
// It is not used aside from printing resources.
|
||||
const All = "All"
|
||||
@ -149,9 +156,8 @@ func discreteScopes[T scopeT, C categoryT](
|
||||
discreteIDs []string,
|
||||
) []T {
|
||||
sl := []T{}
|
||||
jdid := join(discreteIDs...)
|
||||
|
||||
if len(jdid) == 0 {
|
||||
if len(discreteIDs) == 0 {
|
||||
return scopes[T](s)
|
||||
}
|
||||
|
||||
@ -164,7 +170,7 @@ func discreteScopes[T scopeT, C categoryT](
|
||||
w[k] = v
|
||||
}
|
||||
|
||||
set(w, rootCat, jdid)
|
||||
set(w, rootCat, discreteIDs)
|
||||
t = w
|
||||
}
|
||||
|
||||
@ -246,31 +252,41 @@ func toResourceTypeMap(ms []scope) map[string][]string {
|
||||
|
||||
for _, m := range ms {
|
||||
res := m[scopeKeyResource]
|
||||
if res == AnyTgt {
|
||||
res = All
|
||||
k := res.Target
|
||||
|
||||
if res.Target == AnyTgt {
|
||||
k = All
|
||||
}
|
||||
|
||||
r[res] = addToSet(r[res], m[scopeKeyDataType])
|
||||
r[k] = addToSet(r[k], m[scopeKeyDataType].Targets())
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// returns [v] if set is empty,
|
||||
// returns self if set contains v,
|
||||
// appends v to self, otherwise.
|
||||
func addToSet(set []string, v string) []string {
|
||||
// returns v if set is empty,
|
||||
// unions v with set, otherwise.
|
||||
func addToSet(set []string, v []string) []string {
|
||||
if len(set) == 0 {
|
||||
return []string{v}
|
||||
return v
|
||||
}
|
||||
|
||||
for _, vv := range v {
|
||||
var matched bool
|
||||
|
||||
for _, s := range set {
|
||||
if s == v {
|
||||
return set
|
||||
if vv == s {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return append(set, v)
|
||||
if !matched {
|
||||
set = append(set, vv)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -305,8 +321,8 @@ func split(s string) []string {
|
||||
// if the slice contains None, returns [None]
|
||||
// if the slice contains Any and None, returns the first
|
||||
// if the slice is empty, returns [None]
|
||||
// otherwise returns the input unchanged
|
||||
func normalize(s []string) []string {
|
||||
// otherwise returns the input
|
||||
func clean(s []string) []string {
|
||||
if len(s) == 0 {
|
||||
return None()
|
||||
}
|
||||
@ -323,3 +339,53 @@ func normalize(s []string) []string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// filterize turns the slice into a filter.
|
||||
// if the input is Any(), returns a passAny filter.
|
||||
// if the input is None(), returns a failAny filter.
|
||||
// if the input is len(1), returns an Equals filter.
|
||||
// otherwise returns a Contains filter.
|
||||
func filterize(s ...string) filters.Filter {
|
||||
s = clean(s)
|
||||
|
||||
if len(s) == 1 {
|
||||
if s[0] == AnyTgt {
|
||||
return passAny
|
||||
}
|
||||
|
||||
if s[0] == NoneTgt {
|
||||
return failAny
|
||||
}
|
||||
|
||||
return filters.NewEquals(false, "", s[0])
|
||||
}
|
||||
|
||||
return filters.NewContains(false, "", join(s...))
|
||||
}
|
||||
|
||||
type filterFunc func(bool, any, string) filters.Filter
|
||||
|
||||
// wrapFilter produces a func that filterizes the input by:
|
||||
// - cleans the input string
|
||||
// - normalizes the cleaned input (returns anyFail if empty, allFail if *)
|
||||
// - joins the string
|
||||
// - and generates a filter with the joined input.
|
||||
func wrapFilter(ff filterFunc) func([]string) filters.Filter {
|
||||
return func(s []string) filters.Filter {
|
||||
s = clean(s)
|
||||
|
||||
if len(s) == 1 {
|
||||
if s[0] == AnyTgt {
|
||||
return passAny
|
||||
}
|
||||
|
||||
if s[0] == NoneTgt {
|
||||
return failAny
|
||||
}
|
||||
}
|
||||
|
||||
ss := join(s...)
|
||||
|
||||
return ff(false, nil, ss)
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() {
|
||||
|
||||
sel.Includes = []scope{
|
||||
scope(stubScope("")),
|
||||
{scopeKeyResource: "smarf", scopeKeyDataType: unknownCatStub.String()},
|
||||
{scopeKeyResource: "smurf", scopeKeyDataType: unknownCatStub.String()},
|
||||
{scopeKeyResource: filterize("smarf"), scopeKeyDataType: filterize(unknownCatStub.String())},
|
||||
{scopeKeyResource: filterize("smurf"), scopeKeyDataType: filterize(unknownCatStub.String())},
|
||||
}
|
||||
p = sel.Printable()
|
||||
res = p.Resources()
|
||||
@ -94,8 +94,8 @@ func (suite *SelectorSuite) TestToResourceTypeMap() {
|
||||
input: []scope{
|
||||
scope(stubScope("")),
|
||||
{
|
||||
scopeKeyResource: "smarf",
|
||||
scopeKeyDataType: unknownCatStub.String(),
|
||||
scopeKeyResource: filterize("smarf"),
|
||||
scopeKeyDataType: filterize(unknownCatStub.String()),
|
||||
},
|
||||
},
|
||||
expect: map[string][]string{
|
||||
@ -108,8 +108,8 @@ func (suite *SelectorSuite) TestToResourceTypeMap() {
|
||||
input: []scope{
|
||||
scope(stubScope("")),
|
||||
{
|
||||
scopeKeyResource: stubResource,
|
||||
scopeKeyDataType: "other",
|
||||
scopeKeyResource: filterize(stubResource),
|
||||
scopeKeyDataType: filterize("other"),
|
||||
},
|
||||
},
|
||||
expect: map[string][]string{
|
||||
@ -130,10 +130,10 @@ func (suite *SelectorSuite) TestContains() {
|
||||
key := rootCatStub
|
||||
target := "fnords"
|
||||
does := stubScope("")
|
||||
does[key.String()] = target
|
||||
does[key.String()] = filterize(target)
|
||||
doesNot := stubScope("")
|
||||
doesNot[key.String()] = "smarf"
|
||||
doesNot[key.String()] = filterize("smarf")
|
||||
|
||||
assert.True(t, contains(does, key, target), "does contain")
|
||||
assert.False(t, contains(doesNot, key, target), "does not contain")
|
||||
assert.True(t, matches(does, key, target), "does contain")
|
||||
assert.False(t, matches(doesNot, key, target), "does not contain")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user