use slices for all filter targets (#3028)
Simple concept for a large footprint of change: filters should always accept a slice of targets to compare against, instead of a string. Currently, we're faking a slice by way of using a const separator anyway, so we might as well support slices outright. Lots of changes (simplification, mostly) in selectors cascade from this shift. Any updates you see here are purely to comply with the updated filters interface. This step will make concealing PII in selector and filter logging significantly easier than before. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🤖 Supportability/Tests #### Issue(s) * #2024 #### Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
86f9d60bf7
commit
0458d04b9e
@ -12,11 +12,13 @@ func _() {
|
||||
_ = x[BackupOpSchema-1]
|
||||
_ = x[RestoreOpSchema-2]
|
||||
_ = x[BackupSchema-3]
|
||||
_ = x[BackupDetailsSchema-4]
|
||||
_ = x[RepositorySchema-5]
|
||||
}
|
||||
|
||||
const _Schema_name = "UnknownSchemaBackupOpSchemaRestoreOpSchemaBackupSchema"
|
||||
const _Schema_name = "UnknownSchemaBackupOpSchemaRestoreOpSchemaBackupSchemaBackupDetailsSchemaRepositorySchema"
|
||||
|
||||
var _Schema_index = [...]uint8{0, 13, 27, 42, 54}
|
||||
var _Schema_index = [...]uint8{0, 13, 27, 42, 54, 73, 89}
|
||||
|
||||
func (i Schema) String() string {
|
||||
if i < 0 || i >= Schema(len(_Schema_index)-1) {
|
||||
|
||||
37
src/pkg/filters/comparator_string.go
Normal file
37
src/pkg/filters/comparator_string.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Code generated by "stringer -type=comparator -linecomment"; DO NOT EDIT.
|
||||
|
||||
package filters
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[UnknownComparator-0]
|
||||
_ = x[EqualTo-1]
|
||||
_ = x[GreaterThan-2]
|
||||
_ = x[LessThan-3]
|
||||
_ = x[TargetContains-4]
|
||||
_ = x[TargetIn-5]
|
||||
_ = x[Passes-6]
|
||||
_ = x[Fails-7]
|
||||
_ = x[IdentityValue-8]
|
||||
_ = x[TargetPrefixes-9]
|
||||
_ = x[TargetSuffixes-10]
|
||||
_ = x[TargetPathPrefix-11]
|
||||
_ = x[TargetPathContains-12]
|
||||
_ = x[TargetPathSuffix-13]
|
||||
_ = x[TargetPathEquals-14]
|
||||
}
|
||||
|
||||
const _comparator_name = "UnknownComparisonEQGTLTContINPassFailIdentityPfxSfxPathPfxPathContPathSfxPathEQ"
|
||||
|
||||
var _comparator_index = [...]uint8{0, 17, 19, 21, 23, 27, 29, 33, 37, 45, 48, 51, 58, 66, 73, 79}
|
||||
|
||||
func (i comparator) String() string {
|
||||
if i < 0 || i >= comparator(len(_comparator_index)-1) {
|
||||
return "comparator(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _comparator_name[_comparator_index[i]:_comparator_index[i+1]]
|
||||
}
|
||||
@ -1,45 +1,60 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/pii"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
)
|
||||
|
||||
type comparator int
|
||||
|
||||
//go:generate stringer -type=comparator -linecomment
|
||||
const (
|
||||
UnknownComparator comparator = iota
|
||||
UnknownComparator comparator = iota // UnknownComparison
|
||||
// a == b
|
||||
EqualTo
|
||||
EqualTo // EQ
|
||||
// a > b
|
||||
GreaterThan
|
||||
GreaterThan // GT
|
||||
// a < b
|
||||
LessThan
|
||||
LessThan // LT
|
||||
// "foo,bar,baz" contains "foo"
|
||||
TargetContains
|
||||
TargetContains // Cont
|
||||
// "foo" is found in "foo,bar,baz"
|
||||
TargetIn
|
||||
TargetIn // IN
|
||||
// always passes
|
||||
Passes
|
||||
Passes // Pass
|
||||
// always fails
|
||||
Fails
|
||||
Fails // Fail
|
||||
// passthrough for the target
|
||||
IdentityValue
|
||||
IdentityValue // Identity
|
||||
// "foo" is a prefix of "foobarbaz"
|
||||
TargetPrefixes
|
||||
TargetPrefixes // Pfx
|
||||
// "baz" is a suffix of "foobarbaz"
|
||||
TargetSuffixes
|
||||
TargetSuffixes // Sfx
|
||||
// "foo" equals any complete element prefix of "foo/bar/baz"
|
||||
TargetPathPrefix
|
||||
TargetPathPrefix // PathPfx
|
||||
// "foo" equals any complete element in "foo/bar/baz"
|
||||
TargetPathContains
|
||||
TargetPathContains // PathCont
|
||||
// "baz" equals any complete element suffix of "foo/bar/baz"
|
||||
TargetPathSuffix
|
||||
TargetPathSuffix // PathSfx
|
||||
// "foo/bar/baz" equals the complete path "foo/bar/baz"
|
||||
TargetPathEquals
|
||||
TargetPathEquals // PathEQ
|
||||
)
|
||||
|
||||
func normAll(ss []string) []string {
|
||||
r := slices.Clone(ss)
|
||||
for i := range r {
|
||||
r[i] = norm(r[i])
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func norm(s string) string {
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
@ -69,213 +84,198 @@ func normPathElem(s string) string {
|
||||
// true if Filter.Comparer(filter.target, v) is true.
|
||||
type Filter struct {
|
||||
Comparator comparator `json:"comparator"`
|
||||
Target string `json:"target"` // the value to compare against
|
||||
Targets []string `json:"targets"` // the set of values to compare
|
||||
NormalizedTargets []string `json:"normalizedTargets"` // the set of comparable values post normalization
|
||||
Negate bool `json:"negate"` // when true, negate the comparator result
|
||||
|
||||
// only used when the filter's purpose is to hold a value without intent for comparison
|
||||
Identity string `json:"identity"`
|
||||
|
||||
// deprecated, kept around for deserialization
|
||||
Target string `json:"target"` // the value to compare against
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Constructors
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Equal creates a filter where Compare(v) is true if
|
||||
// Identity creates a filter intended to hold values, rather than
|
||||
// compare them. Comparatively, it'll behave the same as Equals.
|
||||
func Identity(id string) Filter {
|
||||
return Filter{
|
||||
Comparator: IdentityValue,
|
||||
Identity: id,
|
||||
Targets: []string{id},
|
||||
NormalizedTargets: normAll([]string{id}),
|
||||
}
|
||||
}
|
||||
|
||||
// Equal creates a filter where Compare(v) is true if, for any target string,
|
||||
// target == v
|
||||
func Equal(target string) Filter {
|
||||
return newFilter(EqualTo, target, false)
|
||||
func Equal(target []string) Filter {
|
||||
return newFilter(EqualTo, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotEqual creates a filter where Compare(v) is true if
|
||||
// NotEqual creates a filter where Compare(v) is true if, for any target string,
|
||||
// target != v
|
||||
func NotEqual(target string) Filter {
|
||||
return newFilter(EqualTo, target, true)
|
||||
func NotEqual(target []string) Filter {
|
||||
return newFilter(EqualTo, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// Greater creates a filter where Compare(v) is true if
|
||||
// Greater creates a filter where Compare(v) is true if, for any target string,
|
||||
// target > v
|
||||
func Greater(target string) Filter {
|
||||
return newFilter(GreaterThan, target, false)
|
||||
func Greater(target []string) Filter {
|
||||
return newFilter(GreaterThan, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotGreater creates a filter where Compare(v) is true if
|
||||
// NotGreater creates a filter where Compare(v) is true if, for any target string,
|
||||
// target <= v
|
||||
func NotGreater(target string) Filter {
|
||||
return newFilter(GreaterThan, target, true)
|
||||
func NotGreater(target []string) Filter {
|
||||
return newFilter(GreaterThan, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// Less creates a filter where Compare(v) is true if
|
||||
// Less creates a filter where Compare(v) is true if, for any target string,
|
||||
// target < v
|
||||
func Less(target string) Filter {
|
||||
return newFilter(LessThan, target, false)
|
||||
func Less(target []string) Filter {
|
||||
return newFilter(LessThan, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotLess creates a filter where Compare(v) is true if
|
||||
// NotLess creates a filter where Compare(v) is true if, for any target string,
|
||||
// target >= v
|
||||
func NotLess(target string) Filter {
|
||||
return newFilter(LessThan, target, true)
|
||||
func NotLess(target []string) Filter {
|
||||
return newFilter(LessThan, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// Contains creates a filter where Compare(v) is true if
|
||||
// Contains creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Contains(v)
|
||||
func Contains(target string) Filter {
|
||||
return newFilter(TargetContains, target, false)
|
||||
func Contains(target []string) Filter {
|
||||
return newFilter(TargetContains, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotContains creates a filter where Compare(v) is true if
|
||||
// NotContains creates a filter where Compare(v) is true if, for any target string,
|
||||
// !target.Contains(v)
|
||||
func NotContains(target string) Filter {
|
||||
return newFilter(TargetContains, target, true)
|
||||
func NotContains(target []string) Filter {
|
||||
return newFilter(TargetContains, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// In creates a filter where Compare(v) is true if
|
||||
// In creates a filter where Compare(v) is true if, for any target string,
|
||||
// v.Contains(target)
|
||||
func In(targets []string) Filter {
|
||||
return newSliceFilter(TargetIn, targets, targets, false)
|
||||
func In(target []string) Filter {
|
||||
return newFilter(TargetIn, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotIn creates a filter where Compare(v) is true if
|
||||
// NotIn creates a filter where Compare(v) is true if, for any target string,
|
||||
// !v.Contains(target)
|
||||
func NotIn(targets []string) Filter {
|
||||
return newSliceFilter(TargetIn, targets, targets, true)
|
||||
func NotIn(target []string) Filter {
|
||||
return newFilter(TargetIn, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// Pass creates a filter where Compare(v) always returns true
|
||||
func Pass() Filter {
|
||||
return newFilter(Passes, "*", false)
|
||||
return newFilter(Passes, []string{"*"}, nil, false)
|
||||
}
|
||||
|
||||
// Fail creates a filter where Compare(v) always returns false
|
||||
func Fail() Filter {
|
||||
return newFilter(Fails, "", false)
|
||||
return newFilter(Fails, []string{""}, nil, false)
|
||||
}
|
||||
|
||||
// Identity creates a filter intended to hold values, rather than
|
||||
// compare them. Comparatively, it'll behave the same as Equals.
|
||||
func Identity(id string) Filter {
|
||||
return newFilter(IdentityValue, id, false)
|
||||
}
|
||||
|
||||
// Prefix creates a filter where Compare(v) is true if
|
||||
// Prefix creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Prefix(v)
|
||||
func Prefix(target string) Filter {
|
||||
return newFilter(TargetPrefixes, target, false)
|
||||
func Prefix(target []string) Filter {
|
||||
return newFilter(TargetPrefixes, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotPrefix creates a filter where Compare(v) is true if
|
||||
// NotPrefix creates a filter where Compare(v) is true if, for any target string,
|
||||
// !target.Prefix(v)
|
||||
func NotPrefix(target string) Filter {
|
||||
return newFilter(TargetPrefixes, target, true)
|
||||
func NotPrefix(target []string) Filter {
|
||||
return newFilter(TargetPrefixes, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// Suffix creates a filter where Compare(v) is true if
|
||||
// Suffix creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Suffix(v)
|
||||
func Suffix(target string) Filter {
|
||||
return newFilter(TargetSuffixes, target, false)
|
||||
func Suffix(target []string) Filter {
|
||||
return newFilter(TargetSuffixes, target, normAll(target), false)
|
||||
}
|
||||
|
||||
// NotSuffix creates a filter where Compare(v) is true if
|
||||
// NotSuffix creates a filter where Compare(v) is true if, for any target string,
|
||||
// !target.Suffix(v)
|
||||
func NotSuffix(target string) Filter {
|
||||
return newFilter(TargetSuffixes, target, true)
|
||||
func NotSuffix(target []string) Filter {
|
||||
return newFilter(TargetSuffixes, target, normAll(target), true)
|
||||
}
|
||||
|
||||
// PathPrefix creates a filter where Compare(v) is true if
|
||||
// PathPrefix creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Prefix(v) &&
|
||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "/foo/bar" returns true for input "/foo/bar/baz",
|
||||
// but false for "/foo/barbaz"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func PathPrefix(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathPrefix, targets, tgts, false)
|
||||
return newFilter(TargetPathPrefix, targets, tgts, false)
|
||||
}
|
||||
|
||||
// NotPathPrefix creates a filter where Compare(v) is true if
|
||||
// NotPathPrefix creates a filter where Compare(v) is true if, for any target string,
|
||||
// !target.Prefix(v) ||
|
||||
// !split(target)[i].Equals(split(v)[i]) for _any_ i in 0..len(target)-1
|
||||
// ex: target "/foo/bar" returns false for input "/foo/bar/baz",
|
||||
// but true for "/foo/barbaz"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func NotPathPrefix(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathPrefix, targets, tgts, true)
|
||||
return newFilter(TargetPathPrefix, targets, tgts, true)
|
||||
}
|
||||
|
||||
// PathContains creates a filter where Compare(v) is true if
|
||||
// PathContains creates a filter where Compare(v) is true if, for any target string,
|
||||
// for _any_ elem e in split(v), target.Equals(e) ||
|
||||
// for _any_ sequence of elems in split(v), target.Equals(path.Join(e[n:m]))
|
||||
// ex: target "foo" returns true for input "/baz/foo/bar",
|
||||
// but false for "/baz/foobar"
|
||||
// ex: target "baz/foo" returns true for input "/baz/foo/bar",
|
||||
// but false for "/baz/foobar"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func PathContains(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathContains, targets, tgts, false)
|
||||
return newFilter(TargetPathContains, targets, tgts, false)
|
||||
}
|
||||
|
||||
// NotPathContains creates a filter where Compare(v) is true if
|
||||
// NotPathContains creates a filter where Compare(v) is true if, for any target string,
|
||||
// for _every_ elem e in split(v), !target.Equals(e) ||
|
||||
// for _every_ sequence of elems in split(v), !target.Equals(path.Join(e[n:m]))
|
||||
// ex: target "foo" returns false for input "/baz/foo/bar",
|
||||
// but true for "/baz/foobar"
|
||||
// ex: target "baz/foo" returns false for input "/baz/foo/bar",
|
||||
// but true for "/baz/foobar"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func NotPathContains(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathContains, targets, tgts, true)
|
||||
return newFilter(TargetPathContains, targets, tgts, true)
|
||||
}
|
||||
|
||||
// PathSuffix creates a filter where Compare(v) is true if
|
||||
// PathSuffix creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Suffix(v) &&
|
||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "/bar/baz" returns true for input "/foo/bar/baz",
|
||||
// but false for "/foobar/baz"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func PathSuffix(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathSuffix, targets, tgts, false)
|
||||
return newFilter(TargetPathSuffix, targets, tgts, false)
|
||||
}
|
||||
|
||||
// NotPathSuffix creates a filter where Compare(v) is true if
|
||||
@ -283,69 +283,45 @@ func PathSuffix(targets []string) Filter {
|
||||
// !split(target)[i].Equals(split(v)[i]) for _any_ i in 0..len(target)-1
|
||||
// ex: target "/bar/baz" returns false for input "/foo/bar/baz",
|
||||
// but true for "/foobar/baz"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func NotPathSuffix(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathSuffix, targets, tgts, true)
|
||||
return newFilter(TargetPathSuffix, targets, tgts, true)
|
||||
}
|
||||
|
||||
// PathEquals creates a filter where Compare(v) is true if
|
||||
// PathEquals creates a filter where Compare(v) is true if, for any target string,
|
||||
// target.Equals(v) &&
|
||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "foo" returns true for inputs "/foo/", "/foo", and "foo/"
|
||||
// but false for "/foo/bar", "bar/foo/", and "/foobar/"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func PathEquals(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathEquals, targets, tgts, false)
|
||||
return newFilter(TargetPathEquals, targets, tgts, false)
|
||||
}
|
||||
|
||||
// NotPathEquals creates a filter where Compare(v) is true if
|
||||
// NotPathEquals creates a filter where Compare(v) is true if, for any target string,
|
||||
// !target.Equals(v) ||
|
||||
// !split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "foo" returns true "/foo/bar", "bar/foo/", and "/foobar/"
|
||||
// but false for for inputs "/foo/", "/foo", and "foo/"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func NotPathEquals(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathEquals, targets, tgts, true)
|
||||
return newFilter(TargetPathEquals, targets, tgts, true)
|
||||
}
|
||||
|
||||
// newFilter is the standard filter constructor.
|
||||
func newFilter(c comparator, target string, negate bool) Filter {
|
||||
return Filter{
|
||||
Comparator: c,
|
||||
Target: target,
|
||||
Negate: negate,
|
||||
}
|
||||
}
|
||||
|
||||
// newSliceFilter constructs filters that contain multiple targets
|
||||
func newSliceFilter(c comparator, targets, normTargets []string, negate bool) Filter {
|
||||
// newFilter constructs filters that contain multiple targets
|
||||
func newFilter(c comparator, targets, normTargets []string, negate bool) Filter {
|
||||
return Filter{
|
||||
Comparator: c,
|
||||
Targets: targets,
|
||||
@ -378,10 +354,7 @@ func (f Filter) CompareAny(inputs ...string) bool {
|
||||
|
||||
// Compare checks whether the input passes the filter.
|
||||
func (f Filter) Compare(input string) bool {
|
||||
var (
|
||||
cmp func(string, string) bool
|
||||
hasSlice bool
|
||||
)
|
||||
var cmp func(string, string) bool
|
||||
|
||||
switch f.Comparator {
|
||||
case EqualTo, IdentityValue:
|
||||
@ -394,23 +367,18 @@ func (f Filter) Compare(input string) bool {
|
||||
cmp = contains
|
||||
case TargetIn:
|
||||
cmp = in
|
||||
hasSlice = true
|
||||
case TargetPrefixes:
|
||||
cmp = prefixed
|
||||
case TargetSuffixes:
|
||||
cmp = suffixed
|
||||
case TargetPathPrefix:
|
||||
cmp = pathPrefix
|
||||
hasSlice = true
|
||||
case TargetPathContains:
|
||||
cmp = pathContains
|
||||
hasSlice = true
|
||||
case TargetPathSuffix:
|
||||
cmp = pathSuffix
|
||||
hasSlice = true
|
||||
case TargetPathEquals:
|
||||
cmp = pathEquals
|
||||
hasSlice = true
|
||||
case Passes:
|
||||
return true
|
||||
case Fails:
|
||||
@ -419,11 +387,11 @@ func (f Filter) Compare(input string) bool {
|
||||
|
||||
var (
|
||||
res bool
|
||||
targets = []string{f.Target}
|
||||
targets = f.NormalizedTargets
|
||||
)
|
||||
|
||||
if hasSlice {
|
||||
targets = f.NormalizedTargets
|
||||
if len(targets) == 0 {
|
||||
targets = f.Targets
|
||||
}
|
||||
|
||||
for _, tgt := range targets {
|
||||
@ -535,35 +503,41 @@ func pathEquals(target, input string) bool {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// Printers and PII control
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// prefixString maps the comparators to string prefixes for printing.
|
||||
var prefixString = map[comparator]string{
|
||||
EqualTo: "eq:",
|
||||
GreaterThan: "gt:",
|
||||
LessThan: "lt:",
|
||||
TargetContains: "cont:",
|
||||
TargetIn: "in:",
|
||||
TargetPrefixes: "pfx:",
|
||||
TargetSuffixes: "sfx:",
|
||||
TargetPathPrefix: "pathPfx:",
|
||||
TargetPathContains: "pathCont:",
|
||||
TargetPathSuffix: "pathSfx:",
|
||||
TargetPathEquals: "pathEq:",
|
||||
var _ clues.PlainConcealer = &Filter{}
|
||||
|
||||
var safeFilterValues = map[string]struct{}{"*": {}}
|
||||
|
||||
func (f Filter) Conceal() string {
|
||||
fcs := f.Comparator.String()
|
||||
|
||||
switch f.Comparator {
|
||||
case Passes, Fails:
|
||||
return fcs
|
||||
}
|
||||
|
||||
concealed := pii.ConcealElements(f.Targets, safeFilterValues)
|
||||
|
||||
return fcs + ":" + strings.Join(concealed, ",")
|
||||
}
|
||||
|
||||
func (f Filter) Format(fs fmt.State, _ rune) {
|
||||
fmt.Fprint(fs, f.Conceal())
|
||||
}
|
||||
|
||||
func (f Filter) String() string {
|
||||
switch f.Comparator {
|
||||
case Passes:
|
||||
return "pass"
|
||||
case Fails:
|
||||
return "fail"
|
||||
}
|
||||
|
||||
if len(f.Targets) > 0 {
|
||||
return prefixString[f.Comparator] + strings.Join(f.Targets, ",")
|
||||
}
|
||||
|
||||
return prefixString[f.Comparator] + f.Target
|
||||
return f.Conceal()
|
||||
}
|
||||
|
||||
func (f Filter) PlainString() string {
|
||||
fcs := f.Comparator.String()
|
||||
|
||||
switch f.Comparator {
|
||||
case Passes, Fails:
|
||||
return fcs
|
||||
}
|
||||
|
||||
return fcs + ":" + strings.Join(f.Targets, ",")
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package filters_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -18,9 +21,29 @@ func TestFiltersSuite(t *testing.T) {
|
||||
suite.Run(t, &FiltersSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
// set the clues hashing to mask for the span of this suite
|
||||
func (suite *FiltersSuite) SetupSuite() {
|
||||
clues.SetHasher(clues.HashCfg{HashAlg: clues.Flatmask})
|
||||
}
|
||||
|
||||
// revert clues hashing to plaintext for all other tests
|
||||
func (suite *FiltersSuite) TeardownSuite() {
|
||||
clues.SetHasher(clues.NoHash())
|
||||
}
|
||||
|
||||
func sl(s ...string) []string {
|
||||
return append([]string{}, s...)
|
||||
}
|
||||
|
||||
var (
|
||||
foo = sl("foo")
|
||||
five = sl("5")
|
||||
smurfs = sl("smurfs")
|
||||
)
|
||||
|
||||
func (suite *FiltersSuite) TestEquals() {
|
||||
f := filters.Equal("foo")
|
||||
nf := filters.NotEqual("foo")
|
||||
f := filters.Equal(foo)
|
||||
nf := filters.NotEqual(foo)
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -41,8 +64,8 @@ func (suite *FiltersSuite) TestEquals() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestEquals_any() {
|
||||
f := filters.Equal("foo")
|
||||
nf := filters.NotEqual("foo")
|
||||
f := filters.Equal(foo)
|
||||
nf := filters.NotEqual(foo)
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
@ -64,8 +87,8 @@ func (suite *FiltersSuite) TestEquals_any() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestGreater() {
|
||||
f := filters.Greater("5")
|
||||
nf := filters.NotGreater("5")
|
||||
f := filters.Greater(five)
|
||||
nf := filters.NotGreater(five)
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -87,8 +110,8 @@ func (suite *FiltersSuite) TestGreater() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestLess() {
|
||||
f := filters.Less("5")
|
||||
nf := filters.NotLess("5")
|
||||
f := filters.Less(five)
|
||||
nf := filters.NotLess(five)
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -110,8 +133,8 @@ func (suite *FiltersSuite) TestLess() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestContains() {
|
||||
f := filters.Contains("smurfs")
|
||||
nf := filters.NotContains("smurfs")
|
||||
f := filters.Contains(smurfs)
|
||||
nf := filters.NotContains(smurfs)
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -131,32 +154,9 @@ func (suite *FiltersSuite) TestContains() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestContains_Joined() {
|
||||
f := filters.Contains("smarf,userid")
|
||||
nf := filters.NotContains("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.Run(test.input, func() {
|
||||
t := suite.T()
|
||||
|
||||
test.expectF(t, f.Compare(test.input), "filter")
|
||||
test.expectNF(t, nf.Compare(test.input), "negated filter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn() {
|
||||
f := filters.In([]string{"murf"})
|
||||
nf := filters.NotIn([]string{"murf"})
|
||||
f := filters.In(sl("murf"))
|
||||
nf := filters.NotIn(sl("murf"))
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -177,8 +177,8 @@ func (suite *FiltersSuite) TestIn() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn_MultipleTargets() {
|
||||
f := filters.In([]string{"murf", "foo"})
|
||||
nf := filters.NotIn([]string{"murf", "foo"})
|
||||
f := filters.In(sl("murf", "foo"))
|
||||
nf := filters.NotIn(sl("murf", "foo"))
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -201,8 +201,8 @@ func (suite *FiltersSuite) TestIn_MultipleTargets() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn_MultipleTargets_Joined() {
|
||||
f := filters.In([]string{"userid", "foo"})
|
||||
nf := filters.NotIn([]string{"userid", "foo"})
|
||||
f := filters.In(sl("userid", "foo"))
|
||||
nf := filters.NotIn(sl("userid", "foo"))
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -225,8 +225,8 @@ func (suite *FiltersSuite) TestIn_MultipleTargets_Joined() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestIn_Joined() {
|
||||
f := filters.In([]string{"userid"})
|
||||
nf := filters.NotIn([]string{"userid"})
|
||||
f := filters.In(sl("userid"))
|
||||
nf := filters.NotIn(sl("userid"))
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
@ -247,7 +247,7 @@ func (suite *FiltersSuite) TestIn_Joined() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestPrefixes() {
|
||||
target := "folderA"
|
||||
target := sl("folderA")
|
||||
f := filters.Prefix(target)
|
||||
nf := filters.NotPrefix(target)
|
||||
|
||||
@ -274,7 +274,7 @@ func (suite *FiltersSuite) TestPrefixes() {
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestSuffixes() {
|
||||
target := "folderB"
|
||||
target := sl("folderB")
|
||||
f := filters.Suffix(target)
|
||||
nf := filters.NotSuffix(target)
|
||||
|
||||
@ -613,3 +613,95 @@ func (suite *FiltersSuite) TestPathEquals_NormalizedTargets() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestFilter_pii() {
|
||||
targets := []string{"fnords", "smarf", "*"}
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
f filters.Filter
|
||||
}{
|
||||
{"equal", filters.Equal(targets)},
|
||||
{"contains", filters.Contains(targets)},
|
||||
{"greater", filters.Greater(targets)},
|
||||
{"less", filters.Less(targets)},
|
||||
{"prefix", filters.Prefix(targets)},
|
||||
{"suffix", filters.Suffix(targets)},
|
||||
{"pathprefix", filters.PathPrefix(targets)},
|
||||
{"pathsuffix", filters.PathSuffix(targets)},
|
||||
{"pathcontains", filters.PathContains(targets)},
|
||||
{"pathequals", filters.PathEquals(targets)},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
var (
|
||||
t = suite.T()
|
||||
expect = test.f.Comparator.String() + ":***,***,*"
|
||||
expectPlain = test.f.Comparator.String() + ":" + strings.Join(targets, ",")
|
||||
)
|
||||
|
||||
result := test.f.Conceal()
|
||||
assert.Equal(t, expect, result, "conceal")
|
||||
|
||||
result = test.f.String()
|
||||
assert.Equal(t, expect, result, "string")
|
||||
|
||||
result = test.f.PlainString()
|
||||
assert.Equal(t, expectPlain, result, "plainString")
|
||||
|
||||
result = fmt.Sprintf("%s", test.f)
|
||||
assert.Equal(t, expect, result, "fmt %%s")
|
||||
|
||||
result = fmt.Sprintf("%v", test.f)
|
||||
assert.Equal(t, expect, result, "fmt %%v")
|
||||
|
||||
result = fmt.Sprintf("%+v", test.f)
|
||||
assert.Equal(t, expect, result, "fmt %%+v")
|
||||
})
|
||||
}
|
||||
|
||||
table2 := []struct {
|
||||
name string
|
||||
f filters.Filter
|
||||
expect string
|
||||
expectPlain string
|
||||
}{
|
||||
{"pass", filters.Pass(), "Pass", "Pass"},
|
||||
{"fail", filters.Fail(), "Fail", "Fail"},
|
||||
{
|
||||
"identity",
|
||||
filters.Identity("id"),
|
||||
filters.IdentityValue.String() + ":***",
|
||||
filters.IdentityValue.String() + ":id",
|
||||
},
|
||||
{
|
||||
"identity",
|
||||
filters.Identity("*"),
|
||||
filters.IdentityValue.String() + ":*",
|
||||
filters.IdentityValue.String() + ":*",
|
||||
},
|
||||
}
|
||||
for _, test := range table2 {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result := test.f.Conceal()
|
||||
assert.Equal(t, test.expect, result, "conceal")
|
||||
|
||||
result = test.f.String()
|
||||
assert.Equal(t, test.expect, result, "string")
|
||||
|
||||
result = test.f.PlainString()
|
||||
assert.Equal(t, test.expectPlain, result, "plainString")
|
||||
|
||||
result = fmt.Sprintf("%s", test.f)
|
||||
assert.Equal(t, test.expect, result, "fmt %%s")
|
||||
|
||||
result = fmt.Sprintf("%v", test.f)
|
||||
assert.Equal(t, test.expect, result, "fmt %%v")
|
||||
|
||||
result = fmt.Sprintf("%+v", test.f)
|
||||
assert.Equal(t, test.expect, result, "fmt %%+v")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package selectors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
@ -121,6 +122,15 @@ func (s exchange) PathCategories() selectorPathCategories {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (s ExchangeScope) Conceal() string { return conceal(s) }
|
||||
func (s ExchangeScope) Format(fs fmt.State, r rune) { format(s, fs, r) }
|
||||
func (s ExchangeScope) String() string { return conceal(s) }
|
||||
func (s ExchangeScope) PlainString() string { return plainString(s) }
|
||||
|
||||
// -------------------
|
||||
// Exclude/Includes
|
||||
|
||||
@ -336,7 +346,7 @@ func (sr *ExchangeRestore) ContactName(senderID string) []ExchangeScope {
|
||||
ExchangeContact,
|
||||
ExchangeInfoContactName,
|
||||
[]string{senderID},
|
||||
wrapSliceFilter(filters.In)),
|
||||
filters.In),
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,7 +361,7 @@ func (sr *ExchangeRestore) EventOrganizer(organizer string) []ExchangeScope {
|
||||
ExchangeEvent,
|
||||
ExchangeInfoEventOrganizer,
|
||||
[]string{organizer},
|
||||
wrapSliceFilter(filters.In)),
|
||||
filters.In),
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,7 +376,7 @@ func (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope {
|
||||
ExchangeEvent,
|
||||
ExchangeInfoEventRecurs,
|
||||
[]string{recurs},
|
||||
wrapFilter(filters.Equal)),
|
||||
filters.Equal),
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,7 +390,7 @@ func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope
|
||||
ExchangeEvent,
|
||||
ExchangeInfoEventStartsAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,7 +404,7 @@ func (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope
|
||||
ExchangeEvent,
|
||||
ExchangeInfoEventStartsBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,7 +419,7 @@ func (sr *ExchangeRestore) EventSubject(subject string) []ExchangeScope {
|
||||
ExchangeEvent,
|
||||
ExchangeInfoEventSubject,
|
||||
[]string{subject},
|
||||
wrapSliceFilter(filters.In)),
|
||||
filters.In),
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,7 +433,7 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope
|
||||
ExchangeMail,
|
||||
ExchangeInfoMailReceivedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,7 +447,7 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScop
|
||||
ExchangeMail,
|
||||
ExchangeInfoMailReceivedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,7 +462,7 @@ func (sr *ExchangeRestore) MailSender(sender string) []ExchangeScope {
|
||||
ExchangeMail,
|
||||
ExchangeInfoMailSender,
|
||||
[]string{sender},
|
||||
wrapSliceFilter(filters.In)),
|
||||
filters.In),
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,7 +477,7 @@ func (sr *ExchangeRestore) MailSubject(subject string) []ExchangeScope {
|
||||
ExchangeMail,
|
||||
ExchangeInfoMailSubject,
|
||||
[]string{subject},
|
||||
wrapSliceFilter(filters.In)),
|
||||
filters.In),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,9 +78,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Contacts() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeContactFolder: folder,
|
||||
ExchangeContact: join(c1, c2),
|
||||
map[categorizer][]string{
|
||||
ExchangeContactFolder: {folder},
|
||||
ExchangeContact: {c1, c2},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -103,9 +103,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Contacts() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeContactFolder: folder,
|
||||
ExchangeContact: join(c1, c2),
|
||||
map[categorizer][]string{
|
||||
ExchangeContactFolder: {folder},
|
||||
ExchangeContact: {c1, c2},
|
||||
},
|
||||
)
|
||||
|
||||
@ -129,9 +129,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_ContactFolders(
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeContactFolder: join(f1, f2),
|
||||
ExchangeContact: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeContactFolder: {f1, f2},
|
||||
ExchangeContact: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -153,9 +153,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_ContactFolders(
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeContactFolder: join(f1, f2),
|
||||
ExchangeContact: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeContactFolder: {f1, f2},
|
||||
ExchangeContact: Any(),
|
||||
},
|
||||
)
|
||||
|
||||
@ -180,9 +180,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeEventCalendar: c1,
|
||||
ExchangeEvent: join(e1, e2),
|
||||
map[categorizer][]string{
|
||||
ExchangeEventCalendar: {c1},
|
||||
ExchangeEvent: {e1, e2},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -204,9 +204,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_EventCalendars(
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeEventCalendar: join(c1, c2),
|
||||
ExchangeEvent: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeEventCalendar: {c1, c2},
|
||||
ExchangeEvent: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -229,9 +229,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeEventCalendar: c1,
|
||||
ExchangeEvent: join(e1, e2),
|
||||
map[categorizer][]string{
|
||||
ExchangeEventCalendar: {c1},
|
||||
ExchangeEvent: {e1, e2},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -253,9 +253,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_EventCalendars(
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeEventCalendar: join(c1, c2),
|
||||
ExchangeEvent: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeEventCalendar: {c1, c2},
|
||||
ExchangeEvent: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -278,9 +278,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeMailFolder: folder,
|
||||
ExchangeMail: join(m1, m2),
|
||||
map[categorizer][]string{
|
||||
ExchangeMailFolder: {folder},
|
||||
ExchangeMail: {m1, m2},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -303,9 +303,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Mails() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeMailFolder: folder,
|
||||
ExchangeMail: join(m1, m2),
|
||||
map[categorizer][]string{
|
||||
ExchangeMailFolder: {folder},
|
||||
ExchangeMail: {m1, m2},
|
||||
},
|
||||
)
|
||||
|
||||
@ -329,9 +329,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_MailFolders() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeMailFolder: join(f1, f2),
|
||||
ExchangeMail: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeMailFolder: {f1, f2},
|
||||
ExchangeMail: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -353,9 +353,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_MailFolders() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(scopes[0]),
|
||||
map[categorizer]string{
|
||||
ExchangeMailFolder: join(f1, f2),
|
||||
ExchangeMail: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeMailFolder: {f1, f2},
|
||||
ExchangeMail: Any(),
|
||||
},
|
||||
)
|
||||
|
||||
@ -380,9 +380,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeContact: AnyTgt,
|
||||
ExchangeContactFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeContact: Any(),
|
||||
ExchangeContactFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -391,8 +391,8 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeEvent: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeEvent: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -401,9 +401,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeMail: AnyTgt,
|
||||
ExchangeMailFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeMail: Any(),
|
||||
ExchangeMailFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -428,9 +428,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeContact: AnyTgt,
|
||||
ExchangeContactFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeContact: Any(),
|
||||
ExchangeContactFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -439,8 +439,8 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeEvent: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeEvent: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -449,9 +449,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
ExchangeScope(sc),
|
||||
map[categorizer]string{
|
||||
ExchangeMail: AnyTgt,
|
||||
ExchangeMailFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
ExchangeMail: Any(),
|
||||
ExchangeMailFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package selectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -95,8 +96,8 @@ type mockScope scope
|
||||
|
||||
var _ scoper = &mockScope{}
|
||||
|
||||
func (ms mockScope) categorizer() categorizer {
|
||||
switch ms[scopeKeyCategory].Target {
|
||||
func (s mockScope) categorizer() categorizer {
|
||||
switch s[scopeKeyCategory].Identity {
|
||||
case rootCatStub.String():
|
||||
return rootCatStub
|
||||
case leafCatStub.String():
|
||||
@ -106,11 +107,11 @@ func (ms mockScope) categorizer() categorizer {
|
||||
return unknownCatStub
|
||||
}
|
||||
|
||||
func (ms mockScope) matchesInfo(dii details.ItemInfo) bool {
|
||||
return ms[shouldMatch].Target == "true"
|
||||
func (s mockScope) matchesInfo(dii details.ItemInfo) bool {
|
||||
return s[shouldMatch].Target == "true"
|
||||
}
|
||||
|
||||
func (ms mockScope) setDefaults() {}
|
||||
func (s mockScope) setDefaults() {}
|
||||
|
||||
const (
|
||||
shouldMatch = "should-match-entry"
|
||||
@ -144,6 +145,15 @@ func stubInfoScope(match string) mockScope {
|
||||
return sc
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (s mockScope) Conceal() string { return conceal(s) }
|
||||
func (s mockScope) Format(fs fmt.State, r rune) { format(s, fs, r) }
|
||||
func (s mockScope) String() string { return conceal(s) }
|
||||
func (s mockScope) PlainString() string { return plainString(s) }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// selectors
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -155,7 +165,7 @@ type mockSel struct {
|
||||
func stubSelector(resourceOwners []string) mockSel {
|
||||
return mockSel{
|
||||
Selector: Selector{
|
||||
ResourceOwners: filterize(scopeConfig{}, resourceOwners...),
|
||||
ResourceOwners: filterFor(scopeConfig{}, resourceOwners...),
|
||||
Service: ServiceExchange,
|
||||
Excludes: []scope{scope(stubScope(""))},
|
||||
Filters: []scope{scope(stubScope(""))},
|
||||
@ -177,10 +187,10 @@ func setScopesToDefault[T scopeT](ts []T) []T {
|
||||
}
|
||||
|
||||
// calls assert.Equal(t, v, getCatValue(sc, k)[0]) 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 {
|
||||
func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer][]string) {
|
||||
for k, vs := range m {
|
||||
t.Run(k.String(), func(t *testing.T) {
|
||||
assert.Equal(t, split(v), getCatValue(sc, k), "Key: %s", k)
|
||||
assert.Equal(t, vs, getCatValue(sc, k), "Key: %s", k)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package selectors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
@ -120,6 +121,15 @@ func (s oneDrive) PathCategories() selectorPathCategories {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (s OneDriveScope) Conceal() string { return conceal(s) }
|
||||
func (s OneDriveScope) Format(fs fmt.State, r rune) { format(s, fs, r) }
|
||||
func (s OneDriveScope) String() string { return conceal(s) }
|
||||
func (s OneDriveScope) PlainString() string { return plainString(s) }
|
||||
|
||||
// -------------------
|
||||
// Scope Factories
|
||||
|
||||
@ -249,7 +259,7 @@ func (s *oneDrive) CreatedAfter(timeStrings string) []OneDriveScope {
|
||||
OneDriveItem,
|
||||
FileInfoCreatedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +273,7 @@ func (s *oneDrive) CreatedBefore(timeStrings string) []OneDriveScope {
|
||||
OneDriveItem,
|
||||
FileInfoCreatedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,7 +287,7 @@ func (s *oneDrive) ModifiedAfter(timeStrings string) []OneDriveScope {
|
||||
OneDriveItem,
|
||||
FileInfoModifiedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +301,7 @@ func (s *oneDrive) ModifiedBefore(timeStrings string) []OneDriveScope {
|
||||
OneDriveItem,
|
||||
FileInfoModifiedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,12 +498,6 @@ func (s OneDriveScope) setDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
// DiscreteCopy makes a clone of the scope, then replaces the clone's user comparison
|
||||
// with only the provided user.
|
||||
func (s OneDriveScope) DiscreteCopy(user string) OneDriveScope {
|
||||
return discreteCopy(s, user)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Backup Details Filtering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -74,9 +74,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
OneDriveScope(scope),
|
||||
map[categorizer]string{
|
||||
OneDriveItem: AnyTgt,
|
||||
OneDriveFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
OneDriveItem: Any(),
|
||||
OneDriveFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -106,9 +106,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Include_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
OneDriveScope(sc),
|
||||
map[categorizer]string{
|
||||
OneDriveItem: AnyTgt,
|
||||
OneDriveFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
OneDriveItem: Any(),
|
||||
OneDriveFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -136,9 +136,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Exclude_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
OneDriveScope(sc),
|
||||
map[categorizer]string{
|
||||
OneDriveItem: AnyTgt,
|
||||
OneDriveFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
OneDriveItem: Any(),
|
||||
OneDriveFolder: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,9 +2,10 @@ package selectors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
@ -152,6 +153,9 @@ type (
|
||||
// Primarily to ensure that root- or mid-tier scopes (such as folders)
|
||||
// cascade 'Any' matching to more granular categories.
|
||||
setDefaults()
|
||||
|
||||
// Scopes need to comply with PII printing controls.
|
||||
clues.PlainConcealer
|
||||
}
|
||||
// scopeT is the generic type interface of a scoper.
|
||||
scopeT interface {
|
||||
@ -163,7 +167,7 @@ type (
|
||||
// makeScope produces a well formatted, typed scope that ensures all base values are populated.
|
||||
func makeScope[T scopeT](
|
||||
cat categorizer,
|
||||
vs []string,
|
||||
tgts []string,
|
||||
opts ...option,
|
||||
) T {
|
||||
sc := &scopeConfig{}
|
||||
@ -172,7 +176,7 @@ func makeScope[T scopeT](
|
||||
s := T{
|
||||
scopeKeyCategory: filters.Identity(cat.String()),
|
||||
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
||||
cat.String(): filterize(*sc, vs...),
|
||||
cat.String(): filterFor(*sc, tgts...),
|
||||
}
|
||||
|
||||
return s
|
||||
@ -182,17 +186,61 @@ func makeScope[T scopeT](
|
||||
// towards identifying filter-type scopes, that ensures all base values are populated.
|
||||
func makeInfoScope[T scopeT](
|
||||
cat, infoCat categorizer,
|
||||
vs []string,
|
||||
f func([]string) filters.Filter,
|
||||
tgts []string,
|
||||
ff filterFunc,
|
||||
opts ...option,
|
||||
) T {
|
||||
sc := &scopeConfig{}
|
||||
sc.populate(opts...)
|
||||
|
||||
return T{
|
||||
scopeKeyCategory: filters.Identity(cat.String()),
|
||||
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
||||
scopeKeyInfoCategory: filters.Identity(infoCat.String()),
|
||||
infoCat.String(): f(clean(vs)),
|
||||
infoCat.String(): filterize(*sc, ff, tgts...),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// loggableMSS transforms the scope into a map by stringifying each filter.
|
||||
func loggableMSS[T scopeT](s T, plain bool) map[string]string {
|
||||
m := map[string]string{}
|
||||
|
||||
for k, filt := range s {
|
||||
if plain {
|
||||
m[k] = filt.PlainString()
|
||||
} else {
|
||||
m[k] = filt.Conceal()
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func conceal[T scopeT](s T) string {
|
||||
return marshalScope(loggableMSS(s, false))
|
||||
}
|
||||
|
||||
func format[T scopeT](s T, fs fmt.State, _ rune) {
|
||||
fmt.Fprint(fs, conceal(s))
|
||||
}
|
||||
|
||||
func plainString[T scopeT](s T) string {
|
||||
return marshalScope(loggableMSS(s, true))
|
||||
}
|
||||
|
||||
func marshalScope(mss map[string]string) string {
|
||||
bs, err := json.Marshal(mss)
|
||||
if err != nil {
|
||||
return "error-marshalling-selector"
|
||||
}
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// scope funcs
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -229,17 +277,16 @@ func matchesAny[T scopeT, C categoryT](s T, cat C, inpts []string) bool {
|
||||
// getCategory returns the scope's category value.
|
||||
// if s is an info-type scope, returns the info category.
|
||||
func getCategory[T scopeT](s T) string {
|
||||
return s[scopeKeyCategory].Target
|
||||
return s[scopeKeyCategory].Identity
|
||||
}
|
||||
|
||||
// getInfoCategory returns the scope's infoFilter category value.
|
||||
func getInfoCategory[T scopeT](s T) string {
|
||||
return s[scopeKeyInfoCategory].Target
|
||||
return s[scopeKeyInfoCategory].Identity
|
||||
}
|
||||
|
||||
// getCatValue takes the value of s[cat], split it by the standard
|
||||
// delimiter, and returns the slice. If s[cat] is nil, returns
|
||||
// None().
|
||||
// getCatValue takes the value of s[cat] and returns the slice.
|
||||
// If s[cat] is nil, returns None().
|
||||
func getCatValue[T scopeT](s T, cat categorizer) []string {
|
||||
filt, ok := s[cat.String()]
|
||||
if !ok {
|
||||
@ -250,7 +297,7 @@ func getCatValue[T scopeT](s T, cat categorizer) []string {
|
||||
return filt.Targets
|
||||
}
|
||||
|
||||
return split(filt.Target)
|
||||
return filt.Targets
|
||||
}
|
||||
|
||||
// set sets a value by category to the scope. Only intended for internal
|
||||
@ -259,22 +306,11 @@ func set[T scopeT](s T, cat categorizer, v []string, opts ...option) T {
|
||||
sc := &scopeConfig{}
|
||||
sc.populate(opts...)
|
||||
|
||||
s[cat.String()] = filterize(*sc, v...)
|
||||
s[cat.String()] = filterFor(*sc, v...)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// discreteCopy makes a shallow clone of the scocpe, and sets the resource
|
||||
// owner filter target in the clone to the provided string.
|
||||
func discreteCopy[T scopeT](s T, resourceOwner string) T {
|
||||
clone := maps.Clone(s)
|
||||
|
||||
return set(
|
||||
clone,
|
||||
clone.categorizer().rootCat(),
|
||||
[]string{resourceOwner})
|
||||
}
|
||||
|
||||
// returns true if the category is included in the scope's category type,
|
||||
// and the value is set to None().
|
||||
func isNoneTarget[T scopeT, C categoryT](s T, cat C) bool {
|
||||
@ -316,7 +352,7 @@ func reduce[T scopeT, C categoryT](
|
||||
// if a DiscreteOwner is specified, only match details for that owner.
|
||||
matchesResourceOwner := s.ResourceOwners
|
||||
if len(s.DiscreteOwner) > 0 {
|
||||
matchesResourceOwner = filterize(scopeConfig{}, s.DiscreteOwner)
|
||||
matchesResourceOwner = filterFor(scopeConfig{}, s.DiscreteOwner)
|
||||
}
|
||||
|
||||
// aggregate each scope type by category for easier isolation in future processing.
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package selectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
@ -57,7 +60,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "blank value",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filters.Equal("")
|
||||
stub[rootCatStub.String()] = filters.Equal([]string{""})
|
||||
return stub
|
||||
},
|
||||
check: rootCatStub.String(),
|
||||
@ -67,7 +70,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "blank target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, "fnords")
|
||||
stub[rootCatStub.String()] = filterFor(scopeConfig{}, "fnords")
|
||||
return stub
|
||||
},
|
||||
check: "",
|
||||
@ -77,7 +80,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "matching target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
|
||||
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||
return stub
|
||||
},
|
||||
check: rootCatStub.String(),
|
||||
@ -87,7 +90,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
name: "non-matching target",
|
||||
scope: func() mockScope {
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
|
||||
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||
return stub
|
||||
},
|
||||
check: "smarf",
|
||||
@ -109,7 +112,7 @@ func (suite *SelectorScopesSuite) TestGetCatValue() {
|
||||
t := suite.T()
|
||||
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
|
||||
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{rootCatStub.String()},
|
||||
@ -342,7 +345,7 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||
t := suite.T()
|
||||
s1 := stubScope("")
|
||||
s2 := stubScope("")
|
||||
s2[scopeKeyCategory] = filterize(scopeConfig{}, unknownCatStub.String())
|
||||
s2[scopeKeyCategory] = filterFor(scopeConfig{}, unknownCatStub.String())
|
||||
result := scopesByCategory[mockScope](
|
||||
[]scope{scope(s1), scope(s2)},
|
||||
map[path.CategoryType]mockCategorizer{
|
||||
@ -449,8 +452,8 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
|
||||
pvs[leafCatStub] = append(pvs[leafCatStub], test.shortRef)
|
||||
|
||||
sc := stubScope("")
|
||||
sc[rootCatStub.String()] = filterize(scopeConfig{}, test.rootVal)
|
||||
sc[leafCatStub.String()] = filterize(scopeConfig{}, test.leafVal)
|
||||
sc[rootCatStub.String()] = filterFor(scopeConfig{}, test.rootVal)
|
||||
sc[leafCatStub.String()] = filterFor(scopeConfig{}, test.leafVal)
|
||||
|
||||
test.expect(t, matchesPathValues(sc, cat, pvs))
|
||||
})
|
||||
@ -504,47 +507,6 @@ func (suite *SelectorScopesSuite) TestClean() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestWrapFilter() {
|
||||
table := []struct {
|
||||
name string
|
||||
filter filterFunc
|
||||
input []string
|
||||
comparator int
|
||||
target string
|
||||
}{
|
||||
{
|
||||
name: "any",
|
||||
filter: filters.Contains,
|
||||
input: Any(),
|
||||
comparator: int(filters.Passes),
|
||||
target: AnyTgt,
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
filter: filters.Greater,
|
||||
input: None(),
|
||||
comparator: int(filters.Fails),
|
||||
target: NoneTgt,
|
||||
},
|
||||
{
|
||||
name: "something",
|
||||
filter: filters.Equal,
|
||||
input: []string{"userid"},
|
||||
comparator: int(filters.EqualTo),
|
||||
target: "userid",
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
ff := wrapFilter(test.filter)(test.input)
|
||||
assert.Equal(t, int(ff.Comparator), test.comparator)
|
||||
assert.Equal(t, ff.Target, test.target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestScopeConfig() {
|
||||
input := "input"
|
||||
|
||||
@ -568,25 +530,79 @@ func (suite *SelectorScopesSuite) TestScopeConfig() {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result := filterize(test.config, input)
|
||||
result := filterFor(test.config, input)
|
||||
assert.Equal(t, test.expect, int(result.Comparator))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestDiscreteCopy() {
|
||||
var (
|
||||
t = suite.T()
|
||||
orig = stubScope(AnyTgt)
|
||||
clone = discreteCopy(orig, "fnords")
|
||||
)
|
||||
var _ fmt.State = &mockFMTState{}
|
||||
|
||||
for k, v := range orig {
|
||||
if k != rootCatStub.String() {
|
||||
assert.Equal(t, v.Target, clone[k].Target)
|
||||
} else {
|
||||
assert.Equal(t, AnyTgt, v.Target)
|
||||
assert.Equal(t, "fnords", clone[k].Target)
|
||||
}
|
||||
type mockFMTState struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (ms mockFMTState) Write(bs []byte) (int, error) { return ms.w.Write(bs) }
|
||||
func (ms mockFMTState) Width() (int, bool) { return 0, false }
|
||||
func (ms mockFMTState) Precision() (int, bool) { return 0, false }
|
||||
func (ms mockFMTState) Flag(int) bool { return false }
|
||||
|
||||
func (suite *SelectorScopesSuite) TestScopesPII() {
|
||||
table := []struct {
|
||||
name string
|
||||
s mockScope
|
||||
contains []string
|
||||
containsPlain []string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: mockScope{},
|
||||
contains: []string{`{}`},
|
||||
containsPlain: []string{`{}`},
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
s: mockScope{
|
||||
"pass": filterFor(scopeConfig{}, "*"),
|
||||
"fail": filterFor(scopeConfig{}, ""),
|
||||
"foo": filterFor(scopeConfig{}, "bar"),
|
||||
"qux": filterFor(scopeConfig{}, "fnords", "smarf"),
|
||||
},
|
||||
contains: []string{
|
||||
`"pass":"Pass"`,
|
||||
`"fail":"Fail"`,
|
||||
`"foo":"EQ:bar"`,
|
||||
`"qux":"Cont:fnords,smarf"`,
|
||||
},
|
||||
containsPlain: []string{
|
||||
`"pass":"Pass"`,
|
||||
`"fail":"Fail"`,
|
||||
`"foo":"EQ:bar"`,
|
||||
`"qux":"Cont:fnords,smarf"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result := conceal(test.s)
|
||||
for _, c := range test.contains {
|
||||
assert.Contains(t, result, c, "conceal")
|
||||
}
|
||||
|
||||
result = plainString(test.s)
|
||||
for _, c := range test.containsPlain {
|
||||
assert.Contains(t, result, c, "plainString")
|
||||
}
|
||||
|
||||
sb := &strings.Builder{}
|
||||
fs := mockFMTState{sb}
|
||||
|
||||
format(test.s, &fs, 0)
|
||||
for _, c := range test.contains {
|
||||
assert.Contains(t, sb.String(), c, "conceal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
@ -60,9 +60,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
delimiter = string('\x1F')
|
||||
passAny = filters.Pass()
|
||||
failAny = filters.Fail()
|
||||
passAny = filters.Pass()
|
||||
failAny = filters.Fail()
|
||||
)
|
||||
|
||||
// All is the resource name that gets output when the resource is AnyTgt.
|
||||
@ -134,7 +133,7 @@ func newSelector(s service, resourceOwners []string) Selector {
|
||||
|
||||
return Selector{
|
||||
Service: s,
|
||||
ResourceOwners: filterize(scopeConfig{}, resourceOwners...),
|
||||
ResourceOwners: filterFor(scopeConfig{}, resourceOwners...),
|
||||
DiscreteOwner: owner,
|
||||
Excludes: []scope{},
|
||||
Includes: []scope{},
|
||||
@ -145,7 +144,7 @@ func newSelector(s service, resourceOwners []string) Selector {
|
||||
// in the selector.
|
||||
// TODO(rkeepers): remove in favor of split and s.DiscreteOwner
|
||||
func (s Selector) DiscreteResourceOwners() []string {
|
||||
return split(s.ResourceOwners.Target)
|
||||
return s.ResourceOwners.Targets
|
||||
}
|
||||
|
||||
// SetDiscreteOwnerIDName ensures the selector has the correct discrete owner
|
||||
@ -220,7 +219,7 @@ func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string,
|
||||
targets := allOwners
|
||||
|
||||
if !isAnyResourceOwner(s) {
|
||||
targets = split(s.ResourceOwners.Target)
|
||||
targets = s.ResourceOwners.Targets
|
||||
}
|
||||
|
||||
ss := make([]Selector, 0, len(targets))
|
||||
@ -234,15 +233,6 @@ func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string,
|
||||
return ss
|
||||
}
|
||||
|
||||
func (s Selector) String() string {
|
||||
bs, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return "error"
|
||||
}
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// appendScopes iterates through each scope in the list of scope slices,
|
||||
// calling setDefaults() to ensure it is completely populated, and appends
|
||||
// those scopes to the `to` slice.
|
||||
@ -329,6 +319,84 @@ func selectorAsIface[T any](s Selector) (T, error) {
|
||||
return t, err
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var _ clues.PlainConcealer = &Selector{}
|
||||
|
||||
type loggableSelector struct {
|
||||
Service service `json:"service,omitempty"`
|
||||
ResourceOwners string `json:"resourceOwners,omitempty"`
|
||||
DiscreteOwner string `json:"discreteOwner,omitempty"`
|
||||
Excludes []map[string]string `json:"exclusions,omitempty"`
|
||||
Filters []map[string]string `json:"filters,omitempty"`
|
||||
Includes []map[string]string `json:"includes,omitempty"`
|
||||
}
|
||||
|
||||
func (s Selector) Conceal() string {
|
||||
ls := loggableSelector{
|
||||
Service: s.Service,
|
||||
ResourceOwners: s.ResourceOwners.Conceal(),
|
||||
DiscreteOwner: clues.Conceal(s.DiscreteOwner),
|
||||
Excludes: toMSS(s.Excludes, false),
|
||||
Filters: toMSS(s.Filters, false),
|
||||
Includes: toMSS(s.Includes, false),
|
||||
}
|
||||
|
||||
return ls.marshal()
|
||||
}
|
||||
|
||||
func (s Selector) Format(fs fmt.State, _ rune) {
|
||||
fmt.Fprint(fs, s.Conceal())
|
||||
}
|
||||
|
||||
func (s Selector) String() string {
|
||||
return s.Conceal()
|
||||
}
|
||||
|
||||
func (s Selector) PlainString() string {
|
||||
ls := loggableSelector{
|
||||
Service: s.Service,
|
||||
ResourceOwners: s.ResourceOwners.PlainString(),
|
||||
DiscreteOwner: s.DiscreteOwner,
|
||||
Excludes: toMSS(s.Excludes, true),
|
||||
Filters: toMSS(s.Filters, true),
|
||||
Includes: toMSS(s.Includes, true),
|
||||
}
|
||||
|
||||
return ls.marshal()
|
||||
}
|
||||
|
||||
func toMSS(scs []scope, plain bool) []map[string]string {
|
||||
mss := make([]map[string]string, 0, len(scs))
|
||||
|
||||
for _, s := range scs {
|
||||
m := map[string]string{}
|
||||
|
||||
for k, filt := range s {
|
||||
if plain {
|
||||
m[k] = filt.PlainString()
|
||||
} else {
|
||||
m[k] = filt.Conceal()
|
||||
}
|
||||
}
|
||||
|
||||
mss = append(mss, m)
|
||||
}
|
||||
|
||||
return mss
|
||||
}
|
||||
|
||||
func (ls loggableSelector) marshal() string {
|
||||
bs, err := json.Marshal(ls)
|
||||
if err != nil {
|
||||
return "error-marshalling-selector"
|
||||
}
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -339,7 +407,7 @@ func resourceOwnersIn(s []scope, rootCat string) []string {
|
||||
rm := map[string]struct{}{}
|
||||
|
||||
for _, sc := range s {
|
||||
for _, v := range split(sc[rootCat].Target) {
|
||||
for _, v := range sc[rootCat].Targets {
|
||||
rm[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
@ -357,7 +425,7 @@ func resourceOwnersIn(s []scope, rootCat string) []string {
|
||||
|
||||
// produces the discrete set of path categories in the slice of scopes.
|
||||
func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
||||
rm := map[path.CategoryType]struct{}{}
|
||||
m := map[path.CategoryType]struct{}{}
|
||||
|
||||
for _, s := range ss {
|
||||
t := T(s)
|
||||
@ -367,16 +435,10 @@ func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
||||
continue
|
||||
}
|
||||
|
||||
rm[lc.PathType()] = struct{}{}
|
||||
m[lc.PathType()] = struct{}{}
|
||||
}
|
||||
|
||||
rs := []path.CategoryType{}
|
||||
|
||||
for k := range rm {
|
||||
rs = append(rs, k)
|
||||
}
|
||||
|
||||
return rs
|
||||
return maps.Keys(m)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -438,14 +500,6 @@ func badCastErr(cast, is service) error {
|
||||
return clues.Stack(ErrorBadSelectorCast, clues.New(fmt.Sprintf("%s is not %s", cast, is)))
|
||||
}
|
||||
|
||||
func join(s ...string) string {
|
||||
return strings.Join(s, delimiter)
|
||||
}
|
||||
|
||||
func split(s string) []string {
|
||||
return strings.Split(s, delimiter)
|
||||
}
|
||||
|
||||
// if the provided slice contains Any, returns [Any]
|
||||
// if the slice contains None, returns [None]
|
||||
// if the slice contains Any and None, returns the first
|
||||
@ -469,68 +523,84 @@ func clean(s []string) []string {
|
||||
return s
|
||||
}
|
||||
|
||||
type filterFunc func([]string) filters.Filter
|
||||
|
||||
// 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 scopeConfig specifies a filter, use that filter.
|
||||
// if the input is len(1), returns an Equals filter.
|
||||
// otherwise returns a Contains filter.
|
||||
func filterize(sc scopeConfig, s ...string) filters.Filter {
|
||||
s = clean(s)
|
||||
func filterFor(sc scopeConfig, targets ...string) filters.Filter {
|
||||
return filterize(sc, nil, targets...)
|
||||
}
|
||||
|
||||
if len(s) == 0 || s[0] == NoneTgt {
|
||||
// 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 scopeConfig specifies a filter, use that filter.
|
||||
// if defaultFilter is non-nil, returns that filter.
|
||||
// if the input is len(1), returns an Equals filter.
|
||||
// otherwise returns a Contains filter.
|
||||
func filterize(
|
||||
sc scopeConfig,
|
||||
defaultFilter filterFunc,
|
||||
targets ...string,
|
||||
) filters.Filter {
|
||||
targets = clean(targets)
|
||||
|
||||
if len(targets) == 0 || targets[0] == NoneTgt {
|
||||
return failAny
|
||||
}
|
||||
|
||||
if s[0] == AnyTgt {
|
||||
if targets[0] == AnyTgt {
|
||||
return passAny
|
||||
}
|
||||
|
||||
if sc.usePathFilter {
|
||||
if sc.useEqualsFilter {
|
||||
return filters.PathEquals(s)
|
||||
return filters.PathEquals(targets)
|
||||
}
|
||||
|
||||
if sc.usePrefixFilter {
|
||||
return filters.PathPrefix(s)
|
||||
return filters.PathPrefix(targets)
|
||||
}
|
||||
|
||||
if sc.useSuffixFilter {
|
||||
return filters.PathSuffix(s)
|
||||
return filters.PathSuffix(targets)
|
||||
}
|
||||
|
||||
return filters.PathContains(s)
|
||||
return filters.PathContains(targets)
|
||||
}
|
||||
|
||||
if sc.usePrefixFilter {
|
||||
return filters.Prefix(join(s...))
|
||||
return filters.Prefix(targets)
|
||||
}
|
||||
|
||||
if sc.useSuffixFilter {
|
||||
return filters.Suffix(join(s...))
|
||||
return filters.Suffix(targets)
|
||||
}
|
||||
|
||||
if len(s) == 1 {
|
||||
return filters.Equal(s[0])
|
||||
if defaultFilter != nil {
|
||||
return defaultFilter(targets)
|
||||
}
|
||||
|
||||
return filters.Contains(join(s...))
|
||||
if len(targets) == 1 {
|
||||
return filters.Equal(targets)
|
||||
}
|
||||
|
||||
return filters.Contains(targets)
|
||||
}
|
||||
|
||||
type (
|
||||
filterFunc func(string) filters.Filter
|
||||
sliceFilterFunc func([]string) filters.Filter
|
||||
)
|
||||
|
||||
// pathFilterFactory returns the appropriate path filter
|
||||
// (contains, prefix, or suffix) for the provided options.
|
||||
// If multiple options are flagged, Prefix takes priority.
|
||||
// If no options are provided, returns PathContains.
|
||||
func pathFilterFactory(opts ...option) sliceFilterFunc {
|
||||
func pathFilterFactory(opts ...option) filterFunc {
|
||||
sc := &scopeConfig{}
|
||||
sc.populate(opts...)
|
||||
|
||||
var ff sliceFilterFunc
|
||||
var ff filterFunc
|
||||
|
||||
switch true {
|
||||
case sc.usePrefixFilter:
|
||||
@ -546,7 +616,7 @@ func pathFilterFactory(opts ...option) sliceFilterFunc {
|
||||
return wrapSliceFilter(ff)
|
||||
}
|
||||
|
||||
func wrapSliceFilter(ff sliceFilterFunc) sliceFilterFunc {
|
||||
func wrapSliceFilter(ff filterFunc) filterFunc {
|
||||
return func(s []string) filters.Filter {
|
||||
s = clean(s)
|
||||
|
||||
@ -558,23 +628,6 @@ func wrapSliceFilter(ff sliceFilterFunc) sliceFilterFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) sliceFilterFunc {
|
||||
return func(s []string) filters.Filter {
|
||||
s = clean(s)
|
||||
|
||||
if f, ok := isAnyOrNone(s); ok {
|
||||
return f
|
||||
}
|
||||
|
||||
return ff(join(s...))
|
||||
}
|
||||
}
|
||||
|
||||
// returns (<filter>, true) if s is len==1 and s[0] is
|
||||
// anyTgt or noneTgt, implying that the caller should use
|
||||
// the returned filter. On (<filter>, false), the caller
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package selectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
@ -28,6 +29,16 @@ func (suite *SelectorSuite) TestNewSelector() {
|
||||
assert.NotNil(t, s.Includes)
|
||||
}
|
||||
|
||||
// set the clues hashing to mask for the span of this suite
|
||||
func (suite *SelectorSuite) SetupSuite() {
|
||||
clues.SetHasher(clues.HashCfg{HashAlg: clues.Flatmask})
|
||||
}
|
||||
|
||||
// revert clues hashing to plaintext for all other tests
|
||||
func (suite *SelectorSuite) TeardownSuite() {
|
||||
clues.SetHasher(clues.NoHash())
|
||||
}
|
||||
|
||||
func (suite *SelectorSuite) TestBadCastErr() {
|
||||
err := badCastErr(ServiceUnknown, ServiceExchange)
|
||||
assert.Error(suite.T(), err, clues.ToCore(err))
|
||||
@ -56,36 +67,21 @@ func (suite *SelectorSuite) TestResourceOwnersIn() {
|
||||
input: []scope{{rootCat: filters.Identity("foo")}},
|
||||
expect: []string{"foo"},
|
||||
},
|
||||
{
|
||||
name: "multiple values",
|
||||
input: []scope{{rootCat: filters.Identity(join("foo", "bar"))}},
|
||||
expect: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "with any",
|
||||
input: []scope{{rootCat: filters.Identity(join("foo", "bar", AnyTgt))}},
|
||||
expect: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "with none",
|
||||
input: []scope{{rootCat: filters.Identity(join("foo", "bar", NoneTgt))}},
|
||||
expect: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "multiple scopes",
|
||||
input: []scope{
|
||||
{rootCat: filters.Identity(join("foo", "bar"))},
|
||||
{rootCat: filters.Identity(join("baz"))},
|
||||
{rootCat: filters.Identity("foo,bar")},
|
||||
{rootCat: filters.Identity("baz")},
|
||||
},
|
||||
expect: []string{"foo", "bar", "baz"},
|
||||
expect: []string{"foo,bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "multiple scopes with duplicates",
|
||||
input: []scope{
|
||||
{rootCat: filters.Identity(join("foo", "bar"))},
|
||||
{rootCat: filters.Identity(join("baz", "foo"))},
|
||||
{rootCat: filters.Identity("foo")},
|
||||
{rootCat: filters.Identity("foo")},
|
||||
},
|
||||
expect: []string{"foo", "bar", "baz"},
|
||||
expect: []string{"foo"},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
@ -138,9 +134,9 @@ func (suite *SelectorSuite) TestContains() {
|
||||
key := rootCatStub
|
||||
target := "fnords"
|
||||
does := stubScope("")
|
||||
does[key.String()] = filterize(scopeConfig{}, target)
|
||||
does[key.String()] = filterFor(scopeConfig{}, target)
|
||||
doesNot := stubScope("")
|
||||
doesNot[key.String()] = filterize(scopeConfig{}, "smarf")
|
||||
doesNot[key.String()] = filterFor(scopeConfig{}, "smarf")
|
||||
|
||||
assert.True(t, matches(does, key, target), "does contain")
|
||||
assert.False(t, matches(doesNot, key, target), "does not contain")
|
||||
@ -420,3 +416,81 @@ func (suite *SelectorSuite) TestPathCategories_includes() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SelectorSuite) TestSelector_pii() {
|
||||
table := []struct {
|
||||
name string
|
||||
sel func() Selector
|
||||
expect string
|
||||
expectPlain string
|
||||
}{
|
||||
{
|
||||
name: "empty selector",
|
||||
sel: func() Selector { return Selector{} },
|
||||
expect: `{"resourceOwners":"UnknownComparison:"}`,
|
||||
expectPlain: `{"resourceOwners":"UnknownComparison:"}`,
|
||||
},
|
||||
{
|
||||
name: "no scopes",
|
||||
sel: func() Selector {
|
||||
return Selector{
|
||||
Service: ServiceUnknown,
|
||||
DiscreteOwner: "owner",
|
||||
ResourceOwners: filterFor(scopeConfig{}, "owner_1", "owner_2"),
|
||||
}
|
||||
},
|
||||
expect: `{"resourceOwners":"Cont:***,***","discreteOwner":"***"}`,
|
||||
expectPlain: `{"resourceOwners":"Cont:owner_1,owner_2","discreteOwner":"owner"}`,
|
||||
},
|
||||
{
|
||||
name: "one scope each type",
|
||||
sel: func() Selector {
|
||||
s := NewExchangeBackup([]string{"owner_1", "owner_2"})
|
||||
s.DiscreteOwner = "owner"
|
||||
|
||||
s.Exclude(s.MailFolders([]string{"e"}))
|
||||
s.Filter(s.MailFolders([]string{"f"}, SuffixMatch()))
|
||||
s.Include(s.MailFolders([]string{"i"}, PrefixMatch()))
|
||||
|
||||
return s.Selector
|
||||
},
|
||||
//nolint:lll
|
||||
expect: `{"service":1,` +
|
||||
`"resourceOwners":"Cont:***,***",` +
|
||||
`"discreteOwner":"***",` +
|
||||
`"exclusions":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathCont:***","category":"Identity:***","type":"Identity:***"}],` +
|
||||
`"filters":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathSfx:***","category":"Identity:***","type":"Identity:***"}],` +
|
||||
`"includes":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathPfx:***","category":"Identity:***","type":"Identity:***"}]}`,
|
||||
//nolint:lll
|
||||
expectPlain: `{"service":1,` +
|
||||
`"resourceOwners":"Cont:owner_1,owner_2",` +
|
||||
`"discreteOwner":"owner",` +
|
||||
`"exclusions":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathCont:e","category":"Identity:ExchangeMailFolder","type":"Identity:ExchangeMail"}],` +
|
||||
`"filters":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathSfx:f","category":"Identity:ExchangeMailFolder","type":"Identity:ExchangeMail"}],` +
|
||||
`"includes":[{"ExchangeMail":"Pass","ExchangeMailFolder":"PathPfx:i","category":"Identity:ExchangeMailFolder","type":"Identity:ExchangeMail"}]}`,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result := test.sel().Conceal()
|
||||
assert.Equal(t, test.expect, result, "conceal")
|
||||
|
||||
result = test.sel().String()
|
||||
assert.Equal(t, test.expect, result, "string")
|
||||
|
||||
result = test.sel().PlainString()
|
||||
assert.Equal(t, test.expectPlain, result, "plainString")
|
||||
|
||||
result = fmt.Sprintf("%s", test.sel())
|
||||
assert.Equal(t, test.expect, result, "fmt %%s")
|
||||
|
||||
result = fmt.Sprintf("%v", test.sel())
|
||||
assert.Equal(t, test.expect, result, "fmt %%v")
|
||||
|
||||
result = fmt.Sprintf("%+v", test.sel())
|
||||
assert.Equal(t, test.expect, result, "fmt %%+v")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package selectors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
@ -120,6 +121,15 @@ func (s sharePoint) PathCategories() selectorPathCategories {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stringers and Concealers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (s SharePointScope) Conceal() string { return conceal(s) }
|
||||
func (s SharePointScope) Format(fs fmt.State, r rune) { format(s, fs, r) }
|
||||
func (s SharePointScope) String() string { return conceal(s) }
|
||||
func (s SharePointScope) PlainString() string { return plainString(s) }
|
||||
|
||||
// -------------------
|
||||
// Scope Factories
|
||||
|
||||
@ -286,7 +296,7 @@ func (s *sharePoint) Library(library string) []SharePointScope {
|
||||
SharePointLibraryItem,
|
||||
SharePointInfoLibraryDrive,
|
||||
[]string{library},
|
||||
wrapFilter(filters.Equal)),
|
||||
filters.Equal),
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,7 +376,7 @@ func (s *sharePoint) CreatedAfter(timeStrings string) []SharePointScope {
|
||||
SharePointLibraryItem,
|
||||
SharePointInfoCreatedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,7 +386,7 @@ func (s *sharePoint) CreatedBefore(timeStrings string) []SharePointScope {
|
||||
SharePointLibraryItem,
|
||||
SharePointInfoCreatedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,7 +396,7 @@ func (s *sharePoint) ModifiedAfter(timeStrings string) []SharePointScope {
|
||||
SharePointLibraryItem,
|
||||
SharePointInfoModifiedAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
filters.Less),
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +406,7 @@ func (s *sharePoint) ModifiedBefore(timeStrings string) []SharePointScope {
|
||||
SharePointLibraryItem,
|
||||
SharePointInfoModifiedBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
filters.Greater),
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,12 +658,6 @@ func (s SharePointScope) setDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
// DiscreteCopy makes a shallow clone of the scope, then replaces the clone's
|
||||
// site comparison with only the provided site.
|
||||
func (s SharePointScope) DiscreteCopy(site string) SharePointScope {
|
||||
return discreteCopy(s, site)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Backup Details Filtering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -81,18 +81,18 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_AllData() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
spsc,
|
||||
map[categorizer]string{
|
||||
SharePointLibraryItem: AnyTgt,
|
||||
SharePointLibraryFolder: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
SharePointLibraryItem: Any(),
|
||||
SharePointLibraryFolder: Any(),
|
||||
},
|
||||
)
|
||||
case SharePointListItem:
|
||||
scopeMustHave(
|
||||
t,
|
||||
spsc,
|
||||
map[categorizer]string{
|
||||
SharePointListItem: AnyTgt,
|
||||
SharePointList: AnyTgt,
|
||||
map[categorizer][]string{
|
||||
SharePointListItem: Any(),
|
||||
SharePointList: Any(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -109,8 +109,10 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
||||
s2 = "s2"
|
||||
)
|
||||
|
||||
sel := NewSharePointRestore([]string{s1, s2})
|
||||
sel.Include(sel.WebURL([]string{s1, s2}))
|
||||
s12 := []string{s1, s2}
|
||||
|
||||
sel := NewSharePointRestore(s12)
|
||||
sel.Include(sel.WebURL(s12))
|
||||
scopes := sel.Includes
|
||||
require.Len(t, scopes, 3)
|
||||
|
||||
@ -118,7 +120,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
SharePointScope(sc),
|
||||
map[categorizer]string{SharePointWebURL: join(s1, s2)},
|
||||
map[categorizer][]string{SharePointWebURL: s12},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -127,17 +129,17 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any
|
||||
table := []struct {
|
||||
name string
|
||||
in []string
|
||||
expect string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "any",
|
||||
in: []string{AnyTgt},
|
||||
expect: AnyTgt,
|
||||
expect: Any(),
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
in: []string{NoneTgt},
|
||||
expect: NoneTgt,
|
||||
expect: None(),
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
@ -153,7 +155,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any
|
||||
scopeMustHave(
|
||||
t,
|
||||
SharePointScope(sc),
|
||||
map[categorizer]string{SharePointWebURL: test.expect},
|
||||
map[categorizer][]string{SharePointWebURL: test.expect},
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -168,8 +170,10 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() {
|
||||
s2 = "s2"
|
||||
)
|
||||
|
||||
sel := NewSharePointRestore([]string{s1, s2})
|
||||
sel.Exclude(sel.WebURL([]string{s1, s2}))
|
||||
s12 := []string{s1, s2}
|
||||
|
||||
sel := NewSharePointRestore(s12)
|
||||
sel.Exclude(sel.WebURL(s12))
|
||||
scopes := sel.Excludes
|
||||
require.Len(t, scopes, 3)
|
||||
|
||||
@ -177,7 +181,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() {
|
||||
scopeMustHave(
|
||||
t,
|
||||
SharePointScope(sc),
|
||||
map[categorizer]string{SharePointWebURL: join(s1, s2)},
|
||||
map[categorizer][]string{SharePointWebURL: s12},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user