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