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[BackupOpSchema-1]
|
||||||
_ = x[RestoreOpSchema-2]
|
_ = x[RestoreOpSchema-2]
|
||||||
_ = x[BackupSchema-3]
|
_ = 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 {
|
func (i Schema) String() string {
|
||||||
if i < 0 || i >= Schema(len(_Schema_index)-1) {
|
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
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"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"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
type comparator int
|
type comparator int
|
||||||
|
|
||||||
|
//go:generate stringer -type=comparator -linecomment
|
||||||
const (
|
const (
|
||||||
UnknownComparator comparator = iota
|
UnknownComparator comparator = iota // UnknownComparison
|
||||||
// a == b
|
// a == b
|
||||||
EqualTo
|
EqualTo // EQ
|
||||||
// a > b
|
// a > b
|
||||||
GreaterThan
|
GreaterThan // GT
|
||||||
// a < b
|
// a < b
|
||||||
LessThan
|
LessThan // LT
|
||||||
// "foo,bar,baz" contains "foo"
|
// "foo,bar,baz" contains "foo"
|
||||||
TargetContains
|
TargetContains // Cont
|
||||||
// "foo" is found in "foo,bar,baz"
|
// "foo" is found in "foo,bar,baz"
|
||||||
TargetIn
|
TargetIn // IN
|
||||||
// always passes
|
// always passes
|
||||||
Passes
|
Passes // Pass
|
||||||
// always fails
|
// always fails
|
||||||
Fails
|
Fails // Fail
|
||||||
// passthrough for the target
|
// passthrough for the target
|
||||||
IdentityValue
|
IdentityValue // Identity
|
||||||
// "foo" is a prefix of "foobarbaz"
|
// "foo" is a prefix of "foobarbaz"
|
||||||
TargetPrefixes
|
TargetPrefixes // Pfx
|
||||||
// "baz" is a suffix of "foobarbaz"
|
// "baz" is a suffix of "foobarbaz"
|
||||||
TargetSuffixes
|
TargetSuffixes // Sfx
|
||||||
// "foo" equals any complete element prefix of "foo/bar/baz"
|
// "foo" equals any complete element prefix of "foo/bar/baz"
|
||||||
TargetPathPrefix
|
TargetPathPrefix // PathPfx
|
||||||
// "foo" equals any complete element in "foo/bar/baz"
|
// "foo" equals any complete element in "foo/bar/baz"
|
||||||
TargetPathContains
|
TargetPathContains // PathCont
|
||||||
// "baz" equals any complete element suffix of "foo/bar/baz"
|
// "baz" equals any complete element suffix of "foo/bar/baz"
|
||||||
TargetPathSuffix
|
TargetPathSuffix // PathSfx
|
||||||
// "foo/bar/baz" equals the complete path "foo/bar/baz"
|
// "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 {
|
func norm(s string) string {
|
||||||
return strings.ToLower(s)
|
return strings.ToLower(s)
|
||||||
}
|
}
|
||||||
@ -69,213 +84,198 @@ func normPathElem(s string) string {
|
|||||||
// true if Filter.Comparer(filter.target, v) is true.
|
// true if Filter.Comparer(filter.target, v) is true.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
Comparator comparator `json:"comparator"`
|
Comparator comparator `json:"comparator"`
|
||||||
Target string `json:"target"` // the value to compare against
|
|
||||||
Targets []string `json:"targets"` // the set of values to compare
|
Targets []string `json:"targets"` // the set of values to compare
|
||||||
NormalizedTargets []string `json:"normalizedTargets"` // the set of comparable values post normalization
|
NormalizedTargets []string `json:"normalizedTargets"` // the set of comparable values post normalization
|
||||||
Negate bool `json:"negate"` // when true, negate the comparator result
|
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
|
// 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
|
// target == v
|
||||||
func Equal(target string) Filter {
|
func Equal(target []string) Filter {
|
||||||
return newFilter(EqualTo, target, false)
|
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
|
// target != v
|
||||||
func NotEqual(target string) Filter {
|
func NotEqual(target []string) Filter {
|
||||||
return newFilter(EqualTo, target, true)
|
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
|
// target > v
|
||||||
func Greater(target string) Filter {
|
func Greater(target []string) Filter {
|
||||||
return newFilter(GreaterThan, target, false)
|
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
|
// target <= v
|
||||||
func NotGreater(target string) Filter {
|
func NotGreater(target []string) Filter {
|
||||||
return newFilter(GreaterThan, target, true)
|
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
|
// target < v
|
||||||
func Less(target string) Filter {
|
func Less(target []string) Filter {
|
||||||
return newFilter(LessThan, target, false)
|
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
|
// target >= v
|
||||||
func NotLess(target string) Filter {
|
func NotLess(target []string) Filter {
|
||||||
return newFilter(LessThan, target, true)
|
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)
|
// target.Contains(v)
|
||||||
func Contains(target string) Filter {
|
func Contains(target []string) Filter {
|
||||||
return newFilter(TargetContains, target, false)
|
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)
|
// !target.Contains(v)
|
||||||
func NotContains(target string) Filter {
|
func NotContains(target []string) Filter {
|
||||||
return newFilter(TargetContains, target, true)
|
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)
|
// v.Contains(target)
|
||||||
func In(targets []string) Filter {
|
func In(target []string) Filter {
|
||||||
return newSliceFilter(TargetIn, targets, targets, false)
|
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)
|
// !v.Contains(target)
|
||||||
func NotIn(targets []string) Filter {
|
func NotIn(target []string) Filter {
|
||||||
return newSliceFilter(TargetIn, targets, targets, true)
|
return newFilter(TargetIn, target, normAll(target), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass creates a filter where Compare(v) always returns true
|
// Pass creates a filter where Compare(v) always returns true
|
||||||
func Pass() Filter {
|
func Pass() Filter {
|
||||||
return newFilter(Passes, "*", false)
|
return newFilter(Passes, []string{"*"}, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail creates a filter where Compare(v) always returns false
|
// Fail creates a filter where Compare(v) always returns false
|
||||||
func Fail() Filter {
|
func Fail() Filter {
|
||||||
return newFilter(Fails, "", false)
|
return newFilter(Fails, []string{""}, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identity creates a filter intended to hold values, rather than
|
// Prefix creates a filter where Compare(v) is true if, for any target string,
|
||||||
// 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
|
|
||||||
// target.Prefix(v)
|
// target.Prefix(v)
|
||||||
func Prefix(target string) Filter {
|
func Prefix(target []string) Filter {
|
||||||
return newFilter(TargetPrefixes, target, false)
|
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)
|
// !target.Prefix(v)
|
||||||
func NotPrefix(target string) Filter {
|
func NotPrefix(target []string) Filter {
|
||||||
return newFilter(TargetPrefixes, target, true)
|
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)
|
// target.Suffix(v)
|
||||||
func Suffix(target string) Filter {
|
func Suffix(target []string) Filter {
|
||||||
return newFilter(TargetSuffixes, target, false)
|
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)
|
// !target.Suffix(v)
|
||||||
func NotSuffix(target string) Filter {
|
func NotSuffix(target []string) Filter {
|
||||||
return newFilter(TargetSuffixes, target, true)
|
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) &&
|
// target.Prefix(v) &&
|
||||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
// 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",
|
// ex: target "/foo/bar" returns true for input "/foo/bar/baz",
|
||||||
// but false for "/foo/barbaz"
|
// 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 {
|
func PathPrefix(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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) ||
|
// !target.Prefix(v) ||
|
||||||
// !split(target)[i].Equals(split(v)[i]) for _any_ i in 0..len(target)-1
|
// !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",
|
// ex: target "/foo/bar" returns false for input "/foo/bar/baz",
|
||||||
// but true for "/foo/barbaz"
|
// 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 {
|
func NotPathPrefix(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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_ elem e in split(v), target.Equals(e) ||
|
||||||
// for _any_ sequence of elems in split(v), target.Equals(path.Join(e[n:m]))
|
// 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",
|
// ex: target "foo" returns true for input "/baz/foo/bar",
|
||||||
// but false for "/baz/foobar"
|
// but false for "/baz/foobar"
|
||||||
// ex: target "baz/foo" returns true for input "/baz/foo/bar",
|
// ex: target "baz/foo" returns true for input "/baz/foo/bar",
|
||||||
// but false for "/baz/foobar"
|
// 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 {
|
func PathContains(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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_ elem e in split(v), !target.Equals(e) ||
|
||||||
// for _every_ sequence of elems in split(v), !target.Equals(path.Join(e[n:m]))
|
// 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",
|
// ex: target "foo" returns false for input "/baz/foo/bar",
|
||||||
// but true for "/baz/foobar"
|
// but true for "/baz/foobar"
|
||||||
// ex: target "baz/foo" returns false for input "/baz/foo/bar",
|
// ex: target "baz/foo" returns false for input "/baz/foo/bar",
|
||||||
// but true for "/baz/foobar"
|
// 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 {
|
func NotPathContains(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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) &&
|
// target.Suffix(v) &&
|
||||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
// 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",
|
// ex: target "/bar/baz" returns true for input "/foo/bar/baz",
|
||||||
// but false for "/foobar/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 {
|
func PathSuffix(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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
|
// 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
|
// !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",
|
// ex: target "/bar/baz" returns false for input "/foo/bar/baz",
|
||||||
// but true for "/foobar/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 {
|
func NotPathSuffix(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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) &&
|
// target.Equals(v) &&
|
||||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
// 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/"
|
// ex: target "foo" returns true for inputs "/foo/", "/foo", and "foo/"
|
||||||
// but false for "/foo/bar", "bar/foo/", and "/foobar/"
|
// 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 {
|
func PathEquals(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
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) ||
|
// !target.Equals(v) ||
|
||||||
// !split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
// !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/"
|
// ex: target "foo" returns true "/foo/bar", "bar/foo/", and "/foobar/"
|
||||||
// but false for for inputs "/foo/", "/foo", and "foo/"
|
// 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 {
|
func NotPathEquals(targets []string) Filter {
|
||||||
tgts := make([]string, len(targets))
|
tgts := make([]string, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
tgts[i] = normPathElem(targets[i])
|
tgts[i] = normPathElem(targets[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSliceFilter(TargetPathEquals, targets, tgts, true)
|
return newFilter(TargetPathEquals, targets, tgts, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFilter is the standard filter constructor.
|
// newFilter constructs filters that contain multiple targets
|
||||||
func newFilter(c comparator, target string, negate bool) Filter {
|
func newFilter(c comparator, targets, normTargets []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 {
|
|
||||||
return Filter{
|
return Filter{
|
||||||
Comparator: c,
|
Comparator: c,
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
@ -378,10 +354,7 @@ func (f Filter) CompareAny(inputs ...string) bool {
|
|||||||
|
|
||||||
// Compare checks whether the input passes the filter.
|
// Compare checks whether the input passes the filter.
|
||||||
func (f Filter) Compare(input string) bool {
|
func (f Filter) Compare(input string) bool {
|
||||||
var (
|
var cmp func(string, string) bool
|
||||||
cmp func(string, string) bool
|
|
||||||
hasSlice bool
|
|
||||||
)
|
|
||||||
|
|
||||||
switch f.Comparator {
|
switch f.Comparator {
|
||||||
case EqualTo, IdentityValue:
|
case EqualTo, IdentityValue:
|
||||||
@ -394,23 +367,18 @@ func (f Filter) Compare(input string) bool {
|
|||||||
cmp = contains
|
cmp = contains
|
||||||
case TargetIn:
|
case TargetIn:
|
||||||
cmp = in
|
cmp = in
|
||||||
hasSlice = true
|
|
||||||
case TargetPrefixes:
|
case TargetPrefixes:
|
||||||
cmp = prefixed
|
cmp = prefixed
|
||||||
case TargetSuffixes:
|
case TargetSuffixes:
|
||||||
cmp = suffixed
|
cmp = suffixed
|
||||||
case TargetPathPrefix:
|
case TargetPathPrefix:
|
||||||
cmp = pathPrefix
|
cmp = pathPrefix
|
||||||
hasSlice = true
|
|
||||||
case TargetPathContains:
|
case TargetPathContains:
|
||||||
cmp = pathContains
|
cmp = pathContains
|
||||||
hasSlice = true
|
|
||||||
case TargetPathSuffix:
|
case TargetPathSuffix:
|
||||||
cmp = pathSuffix
|
cmp = pathSuffix
|
||||||
hasSlice = true
|
|
||||||
case TargetPathEquals:
|
case TargetPathEquals:
|
||||||
cmp = pathEquals
|
cmp = pathEquals
|
||||||
hasSlice = true
|
|
||||||
case Passes:
|
case Passes:
|
||||||
return true
|
return true
|
||||||
case Fails:
|
case Fails:
|
||||||
@ -419,11 +387,11 @@ func (f Filter) Compare(input string) bool {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
res bool
|
res bool
|
||||||
targets = []string{f.Target}
|
targets = f.NormalizedTargets
|
||||||
)
|
)
|
||||||
|
|
||||||
if hasSlice {
|
if len(targets) == 0 {
|
||||||
targets = f.NormalizedTargets
|
targets = f.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tgt := range 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 _ clues.PlainConcealer = &Filter{}
|
||||||
var prefixString = map[comparator]string{
|
|
||||||
EqualTo: "eq:",
|
var safeFilterValues = map[string]struct{}{"*": {}}
|
||||||
GreaterThan: "gt:",
|
|
||||||
LessThan: "lt:",
|
func (f Filter) Conceal() string {
|
||||||
TargetContains: "cont:",
|
fcs := f.Comparator.String()
|
||||||
TargetIn: "in:",
|
|
||||||
TargetPrefixes: "pfx:",
|
switch f.Comparator {
|
||||||
TargetSuffixes: "sfx:",
|
case Passes, Fails:
|
||||||
TargetPathPrefix: "pathPfx:",
|
return fcs
|
||||||
TargetPathContains: "pathCont:",
|
}
|
||||||
TargetPathSuffix: "pathSfx:",
|
|
||||||
TargetPathEquals: "pathEq:",
|
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 {
|
func (f Filter) String() string {
|
||||||
switch f.Comparator {
|
return f.Conceal()
|
||||||
case Passes:
|
}
|
||||||
return "pass"
|
|
||||||
case Fails:
|
func (f Filter) PlainString() string {
|
||||||
return "fail"
|
fcs := f.Comparator.String()
|
||||||
}
|
|
||||||
|
switch f.Comparator {
|
||||||
if len(f.Targets) > 0 {
|
case Passes, Fails:
|
||||||
return prefixString[f.Comparator] + strings.Join(f.Targets, ",")
|
return fcs
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefixString[f.Comparator] + f.Target
|
return fcs + ":" + strings.Join(f.Targets, ",")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
package filters_test
|
package filters_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -18,9 +21,29 @@ func TestFiltersSuite(t *testing.T) {
|
|||||||
suite.Run(t, &FiltersSuite{Suite: tester.NewUnitSuite(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() {
|
func (suite *FiltersSuite) TestEquals() {
|
||||||
f := filters.Equal("foo")
|
f := filters.Equal(foo)
|
||||||
nf := filters.NotEqual("foo")
|
nf := filters.NotEqual(foo)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -41,8 +64,8 @@ func (suite *FiltersSuite) TestEquals() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestEquals_any() {
|
func (suite *FiltersSuite) TestEquals_any() {
|
||||||
f := filters.Equal("foo")
|
f := filters.Equal(foo)
|
||||||
nf := filters.NotEqual("foo")
|
nf := filters.NotEqual(foo)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
@ -64,8 +87,8 @@ func (suite *FiltersSuite) TestEquals_any() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestGreater() {
|
func (suite *FiltersSuite) TestGreater() {
|
||||||
f := filters.Greater("5")
|
f := filters.Greater(five)
|
||||||
nf := filters.NotGreater("5")
|
nf := filters.NotGreater(five)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -87,8 +110,8 @@ func (suite *FiltersSuite) TestGreater() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestLess() {
|
func (suite *FiltersSuite) TestLess() {
|
||||||
f := filters.Less("5")
|
f := filters.Less(five)
|
||||||
nf := filters.NotLess("5")
|
nf := filters.NotLess(five)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -110,8 +133,8 @@ func (suite *FiltersSuite) TestLess() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestContains() {
|
func (suite *FiltersSuite) TestContains() {
|
||||||
f := filters.Contains("smurfs")
|
f := filters.Contains(smurfs)
|
||||||
nf := filters.NotContains("smurfs")
|
nf := filters.NotContains(smurfs)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
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() {
|
func (suite *FiltersSuite) TestIn() {
|
||||||
f := filters.In([]string{"murf"})
|
f := filters.In(sl("murf"))
|
||||||
nf := filters.NotIn([]string{"murf"})
|
nf := filters.NotIn(sl("murf"))
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -177,8 +177,8 @@ func (suite *FiltersSuite) TestIn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestIn_MultipleTargets() {
|
func (suite *FiltersSuite) TestIn_MultipleTargets() {
|
||||||
f := filters.In([]string{"murf", "foo"})
|
f := filters.In(sl("murf", "foo"))
|
||||||
nf := filters.NotIn([]string{"murf", "foo"})
|
nf := filters.NotIn(sl("murf", "foo"))
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -201,8 +201,8 @@ func (suite *FiltersSuite) TestIn_MultipleTargets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestIn_MultipleTargets_Joined() {
|
func (suite *FiltersSuite) TestIn_MultipleTargets_Joined() {
|
||||||
f := filters.In([]string{"userid", "foo"})
|
f := filters.In(sl("userid", "foo"))
|
||||||
nf := filters.NotIn([]string{"userid", "foo"})
|
nf := filters.NotIn(sl("userid", "foo"))
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -225,8 +225,8 @@ func (suite *FiltersSuite) TestIn_MultipleTargets_Joined() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestIn_Joined() {
|
func (suite *FiltersSuite) TestIn_Joined() {
|
||||||
f := filters.In([]string{"userid"})
|
f := filters.In(sl("userid"))
|
||||||
nf := filters.NotIn([]string{"userid"})
|
nf := filters.NotIn(sl("userid"))
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
input string
|
input string
|
||||||
@ -247,7 +247,7 @@ func (suite *FiltersSuite) TestIn_Joined() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestPrefixes() {
|
func (suite *FiltersSuite) TestPrefixes() {
|
||||||
target := "folderA"
|
target := sl("folderA")
|
||||||
f := filters.Prefix(target)
|
f := filters.Prefix(target)
|
||||||
nf := filters.NotPrefix(target)
|
nf := filters.NotPrefix(target)
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ func (suite *FiltersSuite) TestPrefixes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FiltersSuite) TestSuffixes() {
|
func (suite *FiltersSuite) TestSuffixes() {
|
||||||
target := "folderB"
|
target := sl("folderB")
|
||||||
f := filters.Suffix(target)
|
f := filters.Suffix(target)
|
||||||
nf := filters.NotSuffix(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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"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
|
// Exclude/Includes
|
||||||
|
|
||||||
@ -336,7 +346,7 @@ func (sr *ExchangeRestore) ContactName(senderID string) []ExchangeScope {
|
|||||||
ExchangeContact,
|
ExchangeContact,
|
||||||
ExchangeInfoContactName,
|
ExchangeInfoContactName,
|
||||||
[]string{senderID},
|
[]string{senderID},
|
||||||
wrapSliceFilter(filters.In)),
|
filters.In),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +361,7 @@ func (sr *ExchangeRestore) EventOrganizer(organizer string) []ExchangeScope {
|
|||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
ExchangeInfoEventOrganizer,
|
ExchangeInfoEventOrganizer,
|
||||||
[]string{organizer},
|
[]string{organizer},
|
||||||
wrapSliceFilter(filters.In)),
|
filters.In),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +376,7 @@ func (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope {
|
|||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
ExchangeInfoEventRecurs,
|
ExchangeInfoEventRecurs,
|
||||||
[]string{recurs},
|
[]string{recurs},
|
||||||
wrapFilter(filters.Equal)),
|
filters.Equal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +390,7 @@ func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope
|
|||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
ExchangeInfoEventStartsAfter,
|
ExchangeInfoEventStartsAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +404,7 @@ func (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope
|
|||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
ExchangeInfoEventStartsBefore,
|
ExchangeInfoEventStartsBefore,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Greater)),
|
filters.Greater),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +419,7 @@ func (sr *ExchangeRestore) EventSubject(subject string) []ExchangeScope {
|
|||||||
ExchangeEvent,
|
ExchangeEvent,
|
||||||
ExchangeInfoEventSubject,
|
ExchangeInfoEventSubject,
|
||||||
[]string{subject},
|
[]string{subject},
|
||||||
wrapSliceFilter(filters.In)),
|
filters.In),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +433,7 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope
|
|||||||
ExchangeMail,
|
ExchangeMail,
|
||||||
ExchangeInfoMailReceivedAfter,
|
ExchangeInfoMailReceivedAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,7 +447,7 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScop
|
|||||||
ExchangeMail,
|
ExchangeMail,
|
||||||
ExchangeInfoMailReceivedBefore,
|
ExchangeInfoMailReceivedBefore,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Greater)),
|
filters.Greater),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +462,7 @@ func (sr *ExchangeRestore) MailSender(sender string) []ExchangeScope {
|
|||||||
ExchangeMail,
|
ExchangeMail,
|
||||||
ExchangeInfoMailSender,
|
ExchangeInfoMailSender,
|
||||||
[]string{sender},
|
[]string{sender},
|
||||||
wrapSliceFilter(filters.In)),
|
filters.In),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +477,7 @@ func (sr *ExchangeRestore) MailSubject(subject string) []ExchangeScope {
|
|||||||
ExchangeMail,
|
ExchangeMail,
|
||||||
ExchangeInfoMailSubject,
|
ExchangeInfoMailSubject,
|
||||||
[]string{subject},
|
[]string{subject},
|
||||||
wrapSliceFilter(filters.In)),
|
filters.In),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,9 +78,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Contacts() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContactFolder: folder,
|
ExchangeContactFolder: {folder},
|
||||||
ExchangeContact: join(c1, c2),
|
ExchangeContact: {c1, c2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -103,9 +103,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Contacts() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContactFolder: folder,
|
ExchangeContactFolder: {folder},
|
||||||
ExchangeContact: join(c1, c2),
|
ExchangeContact: {c1, c2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,9 +129,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_ContactFolders(
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContactFolder: join(f1, f2),
|
ExchangeContactFolder: {f1, f2},
|
||||||
ExchangeContact: AnyTgt,
|
ExchangeContact: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -153,9 +153,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_ContactFolders(
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContactFolder: join(f1, f2),
|
ExchangeContactFolder: {f1, f2},
|
||||||
ExchangeContact: AnyTgt,
|
ExchangeContact: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -180,9 +180,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEventCalendar: c1,
|
ExchangeEventCalendar: {c1},
|
||||||
ExchangeEvent: join(e1, e2),
|
ExchangeEvent: {e1, e2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -204,9 +204,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_EventCalendars(
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEventCalendar: join(c1, c2),
|
ExchangeEventCalendar: {c1, c2},
|
||||||
ExchangeEvent: AnyTgt,
|
ExchangeEvent: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -229,9 +229,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEventCalendar: c1,
|
ExchangeEventCalendar: {c1},
|
||||||
ExchangeEvent: join(e1, e2),
|
ExchangeEvent: {e1, e2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -253,9 +253,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_EventCalendars(
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEventCalendar: join(c1, c2),
|
ExchangeEventCalendar: {c1, c2},
|
||||||
ExchangeEvent: AnyTgt,
|
ExchangeEvent: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -278,9 +278,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMailFolder: folder,
|
ExchangeMailFolder: {folder},
|
||||||
ExchangeMail: join(m1, m2),
|
ExchangeMail: {m1, m2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -303,9 +303,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Mails() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMailFolder: folder,
|
ExchangeMailFolder: {folder},
|
||||||
ExchangeMail: join(m1, m2),
|
ExchangeMail: {m1, m2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -329,9 +329,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_MailFolders() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMailFolder: join(f1, f2),
|
ExchangeMailFolder: {f1, f2},
|
||||||
ExchangeMail: AnyTgt,
|
ExchangeMail: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -353,9 +353,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_MailFolders() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(scopes[0]),
|
ExchangeScope(scopes[0]),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMailFolder: join(f1, f2),
|
ExchangeMailFolder: {f1, f2},
|
||||||
ExchangeMail: AnyTgt,
|
ExchangeMail: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -380,9 +380,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContact: AnyTgt,
|
ExchangeContact: Any(),
|
||||||
ExchangeContactFolder: AnyTgt,
|
ExchangeContactFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -391,8 +391,8 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEvent: AnyTgt,
|
ExchangeEvent: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -401,9 +401,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMail: AnyTgt,
|
ExchangeMail: Any(),
|
||||||
ExchangeMailFolder: AnyTgt,
|
ExchangeMailFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -428,9 +428,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeContact: AnyTgt,
|
ExchangeContact: Any(),
|
||||||
ExchangeContactFolder: AnyTgt,
|
ExchangeContactFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -439,8 +439,8 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeEvent: AnyTgt,
|
ExchangeEvent: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -449,9 +449,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
ExchangeScope(sc),
|
ExchangeScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
ExchangeMail: AnyTgt,
|
ExchangeMail: Any(),
|
||||||
ExchangeMailFolder: AnyTgt,
|
ExchangeMailFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -95,8 +96,8 @@ type mockScope scope
|
|||||||
|
|
||||||
var _ scoper = &mockScope{}
|
var _ scoper = &mockScope{}
|
||||||
|
|
||||||
func (ms mockScope) categorizer() categorizer {
|
func (s mockScope) categorizer() categorizer {
|
||||||
switch ms[scopeKeyCategory].Target {
|
switch s[scopeKeyCategory].Identity {
|
||||||
case rootCatStub.String():
|
case rootCatStub.String():
|
||||||
return rootCatStub
|
return rootCatStub
|
||||||
case leafCatStub.String():
|
case leafCatStub.String():
|
||||||
@ -106,11 +107,11 @@ func (ms mockScope) categorizer() categorizer {
|
|||||||
return unknownCatStub
|
return unknownCatStub
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms mockScope) matchesInfo(dii details.ItemInfo) bool {
|
func (s mockScope) matchesInfo(dii details.ItemInfo) bool {
|
||||||
return ms[shouldMatch].Target == "true"
|
return s[shouldMatch].Target == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms mockScope) setDefaults() {}
|
func (s mockScope) setDefaults() {}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shouldMatch = "should-match-entry"
|
shouldMatch = "should-match-entry"
|
||||||
@ -144,6 +145,15 @@ func stubInfoScope(match string) mockScope {
|
|||||||
return sc
|
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
|
// selectors
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -155,7 +165,7 @@ type mockSel struct {
|
|||||||
func stubSelector(resourceOwners []string) mockSel {
|
func stubSelector(resourceOwners []string) mockSel {
|
||||||
return mockSel{
|
return mockSel{
|
||||||
Selector: Selector{
|
Selector: Selector{
|
||||||
ResourceOwners: filterize(scopeConfig{}, resourceOwners...),
|
ResourceOwners: filterFor(scopeConfig{}, resourceOwners...),
|
||||||
Service: ServiceExchange,
|
Service: ServiceExchange,
|
||||||
Excludes: []scope{scope(stubScope(""))},
|
Excludes: []scope{scope(stubScope(""))},
|
||||||
Filters: []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
|
// 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) {
|
func scopeMustHave[T scopeT](t *testing.T, sc T, m map[categorizer][]string) {
|
||||||
for k, v := range m {
|
for k, vs := range m {
|
||||||
t.Run(k.String(), func(t *testing.T) {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"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
|
// Scope Factories
|
||||||
|
|
||||||
@ -249,7 +259,7 @@ func (s *oneDrive) CreatedAfter(timeStrings string) []OneDriveScope {
|
|||||||
OneDriveItem,
|
OneDriveItem,
|
||||||
FileInfoCreatedAfter,
|
FileInfoCreatedAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +273,7 @@ func (s *oneDrive) CreatedBefore(timeStrings string) []OneDriveScope {
|
|||||||
OneDriveItem,
|
OneDriveItem,
|
||||||
FileInfoCreatedBefore,
|
FileInfoCreatedBefore,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Greater)),
|
filters.Greater),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +287,7 @@ func (s *oneDrive) ModifiedAfter(timeStrings string) []OneDriveScope {
|
|||||||
OneDriveItem,
|
OneDriveItem,
|
||||||
FileInfoModifiedAfter,
|
FileInfoModifiedAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +301,7 @@ func (s *oneDrive) ModifiedBefore(timeStrings string) []OneDriveScope {
|
|||||||
OneDriveItem,
|
OneDriveItem,
|
||||||
FileInfoModifiedBefore,
|
FileInfoModifiedBefore,
|
||||||
[]string{timeStrings},
|
[]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
|
// Backup Details Filtering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -74,9 +74,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
OneDriveScope(scope),
|
OneDriveScope(scope),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
OneDriveItem: AnyTgt,
|
OneDriveItem: Any(),
|
||||||
OneDriveFolder: AnyTgt,
|
OneDriveFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -106,9 +106,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Include_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
OneDriveScope(sc),
|
OneDriveScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
OneDriveItem: AnyTgt,
|
OneDriveItem: Any(),
|
||||||
OneDriveFolder: AnyTgt,
|
OneDriveFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -136,9 +136,9 @@ func (suite *OneDriveSelectorSuite) TestOneDriveSelector_Exclude_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
OneDriveScope(sc),
|
OneDriveScope(sc),
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
OneDriveItem: AnyTgt,
|
OneDriveItem: Any(),
|
||||||
OneDriveFolder: AnyTgt,
|
OneDriveFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,10 @@ package selectors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/diagnostics"
|
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"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)
|
// Primarily to ensure that root- or mid-tier scopes (such as folders)
|
||||||
// cascade 'Any' matching to more granular categories.
|
// cascade 'Any' matching to more granular categories.
|
||||||
setDefaults()
|
setDefaults()
|
||||||
|
|
||||||
|
// Scopes need to comply with PII printing controls.
|
||||||
|
clues.PlainConcealer
|
||||||
}
|
}
|
||||||
// scopeT is the generic type interface of a scoper.
|
// scopeT is the generic type interface of a scoper.
|
||||||
scopeT interface {
|
scopeT interface {
|
||||||
@ -163,7 +167,7 @@ type (
|
|||||||
// 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](
|
||||||
cat categorizer,
|
cat categorizer,
|
||||||
vs []string,
|
tgts []string,
|
||||||
opts ...option,
|
opts ...option,
|
||||||
) T {
|
) T {
|
||||||
sc := &scopeConfig{}
|
sc := &scopeConfig{}
|
||||||
@ -172,7 +176,7 @@ func makeScope[T scopeT](
|
|||||||
s := T{
|
s := T{
|
||||||
scopeKeyCategory: filters.Identity(cat.String()),
|
scopeKeyCategory: filters.Identity(cat.String()),
|
||||||
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
||||||
cat.String(): filterize(*sc, vs...),
|
cat.String(): filterFor(*sc, tgts...),
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@ -182,17 +186,61 @@ func makeScope[T scopeT](
|
|||||||
// towards identifying filter-type scopes, that ensures all base values are populated.
|
// towards identifying filter-type scopes, that ensures all base values are populated.
|
||||||
func makeInfoScope[T scopeT](
|
func makeInfoScope[T scopeT](
|
||||||
cat, infoCat categorizer,
|
cat, infoCat categorizer,
|
||||||
vs []string,
|
tgts []string,
|
||||||
f func([]string) filters.Filter,
|
ff filterFunc,
|
||||||
|
opts ...option,
|
||||||
) T {
|
) T {
|
||||||
|
sc := &scopeConfig{}
|
||||||
|
sc.populate(opts...)
|
||||||
|
|
||||||
return T{
|
return T{
|
||||||
scopeKeyCategory: filters.Identity(cat.String()),
|
scopeKeyCategory: filters.Identity(cat.String()),
|
||||||
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
scopeKeyDataType: filters.Identity(cat.leafCat().String()),
|
||||||
scopeKeyInfoCategory: filters.Identity(infoCat.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
|
// 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.
|
// getCategory returns the scope's category value.
|
||||||
// if s is an info-type scope, returns the info category.
|
// if s is an info-type scope, returns the info category.
|
||||||
func getCategory[T scopeT](s T) string {
|
func getCategory[T scopeT](s T) string {
|
||||||
return s[scopeKeyCategory].Target
|
return s[scopeKeyCategory].Identity
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInfoCategory returns the scope's infoFilter category value.
|
// getInfoCategory returns the scope's infoFilter category value.
|
||||||
func getInfoCategory[T scopeT](s T) string {
|
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
|
// getCatValue takes the value of s[cat] and returns the slice.
|
||||||
// delimiter, and returns the slice. If s[cat] is nil, returns
|
// If s[cat] is nil, returns None().
|
||||||
// None().
|
|
||||||
func getCatValue[T scopeT](s T, cat categorizer) []string {
|
func getCatValue[T scopeT](s T, cat categorizer) []string {
|
||||||
filt, ok := s[cat.String()]
|
filt, ok := s[cat.String()]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -250,7 +297,7 @@ func getCatValue[T scopeT](s T, cat categorizer) []string {
|
|||||||
return filt.Targets
|
return filt.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
return split(filt.Target)
|
return filt.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -259,22 +306,11 @@ func set[T scopeT](s T, cat categorizer, v []string, opts ...option) T {
|
|||||||
sc := &scopeConfig{}
|
sc := &scopeConfig{}
|
||||||
sc.populate(opts...)
|
sc.populate(opts...)
|
||||||
|
|
||||||
s[cat.String()] = filterize(*sc, v...)
|
s[cat.String()] = filterFor(*sc, v...)
|
||||||
|
|
||||||
return s
|
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,
|
// returns true if the category is included in the scope's category type,
|
||||||
// and the value is set to None().
|
// and the value is set to None().
|
||||||
func isNoneTarget[T scopeT, C categoryT](s T, cat C) bool {
|
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.
|
// if a DiscreteOwner is specified, only match details for that owner.
|
||||||
matchesResourceOwner := s.ResourceOwners
|
matchesResourceOwner := s.ResourceOwners
|
||||||
if len(s.DiscreteOwner) > 0 {
|
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.
|
// aggregate each scope type by category for easier isolation in future processing.
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -57,7 +60,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
|||||||
name: "blank value",
|
name: "blank value",
|
||||||
scope: func() mockScope {
|
scope: func() mockScope {
|
||||||
stub := stubScope("")
|
stub := stubScope("")
|
||||||
stub[rootCatStub.String()] = filters.Equal("")
|
stub[rootCatStub.String()] = filters.Equal([]string{""})
|
||||||
return stub
|
return stub
|
||||||
},
|
},
|
||||||
check: rootCatStub.String(),
|
check: rootCatStub.String(),
|
||||||
@ -67,7 +70,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
|||||||
name: "blank target",
|
name: "blank target",
|
||||||
scope: func() mockScope {
|
scope: func() mockScope {
|
||||||
stub := stubScope("")
|
stub := stubScope("")
|
||||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, "fnords")
|
stub[rootCatStub.String()] = filterFor(scopeConfig{}, "fnords")
|
||||||
return stub
|
return stub
|
||||||
},
|
},
|
||||||
check: "",
|
check: "",
|
||||||
@ -77,7 +80,7 @@ func (suite *SelectorScopesSuite) TestContains() {
|
|||||||
name: "matching target",
|
name: "matching target",
|
||||||
scope: func() mockScope {
|
scope: func() mockScope {
|
||||||
stub := stubScope("")
|
stub := stubScope("")
|
||||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
|
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||||
return stub
|
return stub
|
||||||
},
|
},
|
||||||
check: rootCatStub.String(),
|
check: rootCatStub.String(),
|
||||||
@ -87,7 +90,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()] = filterize(scopeConfig{}, rootCatStub.String())
|
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||||
return stub
|
return stub
|
||||||
},
|
},
|
||||||
check: "smarf",
|
check: "smarf",
|
||||||
@ -109,7 +112,7 @@ func (suite *SelectorScopesSuite) TestGetCatValue() {
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
stub := stubScope("")
|
stub := stubScope("")
|
||||||
stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
|
stub[rootCatStub.String()] = filterFor(scopeConfig{}, rootCatStub.String())
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
[]string{rootCatStub.String()},
|
[]string{rootCatStub.String()},
|
||||||
@ -342,7 +345,7 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
|||||||
t := suite.T()
|
t := suite.T()
|
||||||
s1 := stubScope("")
|
s1 := stubScope("")
|
||||||
s2 := stubScope("")
|
s2 := stubScope("")
|
||||||
s2[scopeKeyCategory] = filterize(scopeConfig{}, unknownCatStub.String())
|
s2[scopeKeyCategory] = filterFor(scopeConfig{}, unknownCatStub.String())
|
||||||
result := scopesByCategory[mockScope](
|
result := scopesByCategory[mockScope](
|
||||||
[]scope{scope(s1), scope(s2)},
|
[]scope{scope(s1), scope(s2)},
|
||||||
map[path.CategoryType]mockCategorizer{
|
map[path.CategoryType]mockCategorizer{
|
||||||
@ -449,8 +452,8 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
|
|||||||
pvs[leafCatStub] = append(pvs[leafCatStub], test.shortRef)
|
pvs[leafCatStub] = append(pvs[leafCatStub], test.shortRef)
|
||||||
|
|
||||||
sc := stubScope("")
|
sc := stubScope("")
|
||||||
sc[rootCatStub.String()] = filterize(scopeConfig{}, test.rootVal)
|
sc[rootCatStub.String()] = filterFor(scopeConfig{}, test.rootVal)
|
||||||
sc[leafCatStub.String()] = filterize(scopeConfig{}, test.leafVal)
|
sc[leafCatStub.String()] = filterFor(scopeConfig{}, test.leafVal)
|
||||||
|
|
||||||
test.expect(t, matchesPathValues(sc, cat, pvs))
|
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() {
|
func (suite *SelectorScopesSuite) TestScopeConfig() {
|
||||||
input := "input"
|
input := "input"
|
||||||
|
|
||||||
@ -568,25 +530,79 @@ func (suite *SelectorScopesSuite) TestScopeConfig() {
|
|||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
result := filterize(test.config, input)
|
result := filterFor(test.config, input)
|
||||||
assert.Equal(t, test.expect, int(result.Comparator))
|
assert.Equal(t, test.expect, int(result.Comparator))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SelectorScopesSuite) TestDiscreteCopy() {
|
var _ fmt.State = &mockFMTState{}
|
||||||
var (
|
|
||||||
t = suite.T()
|
|
||||||
orig = stubScope(AnyTgt)
|
|
||||||
clone = discreteCopy(orig, "fnords")
|
|
||||||
)
|
|
||||||
|
|
||||||
for k, v := range orig {
|
type mockFMTState struct {
|
||||||
if k != rootCatStub.String() {
|
w io.Writer
|
||||||
assert.Equal(t, v.Target, clone[k].Target)
|
}
|
||||||
} else {
|
|
||||||
assert.Equal(t, AnyTgt, v.Target)
|
func (ms mockFMTState) Write(bs []byte) (int, error) { return ms.w.Write(bs) }
|
||||||
assert.Equal(t, "fnords", clone[k].Target)
|
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"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
@ -60,9 +60,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
delimiter = string('\x1F')
|
passAny = filters.Pass()
|
||||||
passAny = filters.Pass()
|
failAny = filters.Fail()
|
||||||
failAny = filters.Fail()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.
|
||||||
@ -134,7 +133,7 @@ func newSelector(s service, resourceOwners []string) Selector {
|
|||||||
|
|
||||||
return Selector{
|
return Selector{
|
||||||
Service: s,
|
Service: s,
|
||||||
ResourceOwners: filterize(scopeConfig{}, resourceOwners...),
|
ResourceOwners: filterFor(scopeConfig{}, resourceOwners...),
|
||||||
DiscreteOwner: owner,
|
DiscreteOwner: owner,
|
||||||
Excludes: []scope{},
|
Excludes: []scope{},
|
||||||
Includes: []scope{},
|
Includes: []scope{},
|
||||||
@ -145,7 +144,7 @@ func newSelector(s service, resourceOwners []string) Selector {
|
|||||||
// in the selector.
|
// in the selector.
|
||||||
// TODO(rkeepers): remove in favor of split and s.DiscreteOwner
|
// TODO(rkeepers): remove in favor of split and s.DiscreteOwner
|
||||||
func (s Selector) DiscreteResourceOwners() []string {
|
func (s Selector) DiscreteResourceOwners() []string {
|
||||||
return split(s.ResourceOwners.Target)
|
return s.ResourceOwners.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDiscreteOwnerIDName ensures the selector has the correct discrete owner
|
// 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
|
targets := allOwners
|
||||||
|
|
||||||
if !isAnyResourceOwner(s) {
|
if !isAnyResourceOwner(s) {
|
||||||
targets = split(s.ResourceOwners.Target)
|
targets = s.ResourceOwners.Targets
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := make([]Selector, 0, len(targets))
|
ss := make([]Selector, 0, len(targets))
|
||||||
@ -234,15 +233,6 @@ func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string,
|
|||||||
return ss
|
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,
|
// appendScopes iterates through each scope in the list of scope slices,
|
||||||
// calling setDefaults() to ensure it is completely populated, and appends
|
// calling setDefaults() to ensure it is completely populated, and appends
|
||||||
// those scopes to the `to` slice.
|
// those scopes to the `to` slice.
|
||||||
@ -329,6 +319,84 @@ func selectorAsIface[T any](s Selector) (T, error) {
|
|||||||
return t, err
|
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
|
// helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -339,7 +407,7 @@ func resourceOwnersIn(s []scope, rootCat string) []string {
|
|||||||
rm := map[string]struct{}{}
|
rm := map[string]struct{}{}
|
||||||
|
|
||||||
for _, sc := range s {
|
for _, sc := range s {
|
||||||
for _, v := range split(sc[rootCat].Target) {
|
for _, v := range sc[rootCat].Targets {
|
||||||
rm[v] = struct{}{}
|
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.
|
// produces the discrete set of path categories in the slice of scopes.
|
||||||
func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
||||||
rm := map[path.CategoryType]struct{}{}
|
m := map[path.CategoryType]struct{}{}
|
||||||
|
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
t := T(s)
|
t := T(s)
|
||||||
@ -367,16 +435,10 @@ func pathCategoriesIn[T scopeT, C categoryT](ss []scope) []path.CategoryType {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rm[lc.PathType()] = struct{}{}
|
m[lc.PathType()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := []path.CategoryType{}
|
return maps.Keys(m)
|
||||||
|
|
||||||
for k := range rm {
|
|
||||||
rs = append(rs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -438,14 +500,6 @@ func badCastErr(cast, is service) error {
|
|||||||
return clues.Stack(ErrorBadSelectorCast, clues.New(fmt.Sprintf("%s is not %s", cast, is)))
|
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 provided slice contains Any, returns [Any]
|
||||||
// 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
|
||||||
@ -469,68 +523,84 @@ func clean(s []string) []string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filterFunc func([]string) filters.Filter
|
||||||
|
|
||||||
// filterize turns the slice into a filter.
|
// filterize turns the slice into a filter.
|
||||||
// if the input is Any(), returns a passAny filter.
|
// if the input is Any(), returns a passAny filter.
|
||||||
// if the input is None(), returns a failAny filter.
|
// if the input is None(), returns a failAny filter.
|
||||||
// if the scopeConfig specifies a filter, use that filter.
|
// if the scopeConfig specifies a filter, use that filter.
|
||||||
// if the input is len(1), returns an Equals filter.
|
// if the input is len(1), returns an Equals filter.
|
||||||
// otherwise returns a Contains filter.
|
// otherwise returns a Contains filter.
|
||||||
func filterize(sc scopeConfig, s ...string) filters.Filter {
|
func filterFor(sc scopeConfig, targets ...string) filters.Filter {
|
||||||
s = clean(s)
|
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
|
return failAny
|
||||||
}
|
}
|
||||||
|
|
||||||
if s[0] == AnyTgt {
|
if targets[0] == AnyTgt {
|
||||||
return passAny
|
return passAny
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.usePathFilter {
|
if sc.usePathFilter {
|
||||||
if sc.useEqualsFilter {
|
if sc.useEqualsFilter {
|
||||||
return filters.PathEquals(s)
|
return filters.PathEquals(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.usePrefixFilter {
|
if sc.usePrefixFilter {
|
||||||
return filters.PathPrefix(s)
|
return filters.PathPrefix(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.useSuffixFilter {
|
if sc.useSuffixFilter {
|
||||||
return filters.PathSuffix(s)
|
return filters.PathSuffix(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filters.PathContains(s)
|
return filters.PathContains(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.usePrefixFilter {
|
if sc.usePrefixFilter {
|
||||||
return filters.Prefix(join(s...))
|
return filters.Prefix(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.useSuffixFilter {
|
if sc.useSuffixFilter {
|
||||||
return filters.Suffix(join(s...))
|
return filters.Suffix(targets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s) == 1 {
|
if defaultFilter != nil {
|
||||||
return filters.Equal(s[0])
|
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
|
// pathFilterFactory returns the appropriate path filter
|
||||||
// (contains, prefix, or suffix) for the provided options.
|
// (contains, prefix, or suffix) for the provided options.
|
||||||
// If multiple options are flagged, Prefix takes priority.
|
// If multiple options are flagged, Prefix takes priority.
|
||||||
// If no options are provided, returns PathContains.
|
// If no options are provided, returns PathContains.
|
||||||
func pathFilterFactory(opts ...option) sliceFilterFunc {
|
func pathFilterFactory(opts ...option) filterFunc {
|
||||||
sc := &scopeConfig{}
|
sc := &scopeConfig{}
|
||||||
sc.populate(opts...)
|
sc.populate(opts...)
|
||||||
|
|
||||||
var ff sliceFilterFunc
|
var ff filterFunc
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
case sc.usePrefixFilter:
|
case sc.usePrefixFilter:
|
||||||
@ -546,7 +616,7 @@ func pathFilterFactory(opts ...option) sliceFilterFunc {
|
|||||||
return wrapSliceFilter(ff)
|
return wrapSliceFilter(ff)
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapSliceFilter(ff sliceFilterFunc) sliceFilterFunc {
|
func wrapSliceFilter(ff filterFunc) filterFunc {
|
||||||
return func(s []string) filters.Filter {
|
return func(s []string) filters.Filter {
|
||||||
s = clean(s)
|
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
|
// returns (<filter>, true) if s is len==1 and s[0] is
|
||||||
// anyTgt or noneTgt, implying that the caller should use
|
// anyTgt or noneTgt, implying that the caller should use
|
||||||
// the returned filter. On (<filter>, false), the caller
|
// the returned filter. On (<filter>, false), the caller
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -28,6 +29,16 @@ func (suite *SelectorSuite) TestNewSelector() {
|
|||||||
assert.NotNil(t, s.Includes)
|
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() {
|
func (suite *SelectorSuite) TestBadCastErr() {
|
||||||
err := badCastErr(ServiceUnknown, ServiceExchange)
|
err := badCastErr(ServiceUnknown, ServiceExchange)
|
||||||
assert.Error(suite.T(), err, clues.ToCore(err))
|
assert.Error(suite.T(), err, clues.ToCore(err))
|
||||||
@ -56,36 +67,21 @@ func (suite *SelectorSuite) TestResourceOwnersIn() {
|
|||||||
input: []scope{{rootCat: filters.Identity("foo")}},
|
input: []scope{{rootCat: filters.Identity("foo")}},
|
||||||
expect: []string{"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",
|
name: "multiple scopes",
|
||||||
input: []scope{
|
input: []scope{
|
||||||
{rootCat: filters.Identity(join("foo", "bar"))},
|
{rootCat: filters.Identity("foo,bar")},
|
||||||
{rootCat: filters.Identity(join("baz"))},
|
{rootCat: filters.Identity("baz")},
|
||||||
},
|
},
|
||||||
expect: []string{"foo", "bar", "baz"},
|
expect: []string{"foo,bar", "baz"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple scopes with duplicates",
|
name: "multiple scopes with duplicates",
|
||||||
input: []scope{
|
input: []scope{
|
||||||
{rootCat: filters.Identity(join("foo", "bar"))},
|
{rootCat: filters.Identity("foo")},
|
||||||
{rootCat: filters.Identity(join("baz", "foo"))},
|
{rootCat: filters.Identity("foo")},
|
||||||
},
|
},
|
||||||
expect: []string{"foo", "bar", "baz"},
|
expect: []string{"foo"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -138,9 +134,9 @@ func (suite *SelectorSuite) TestContains() {
|
|||||||
key := rootCatStub
|
key := rootCatStub
|
||||||
target := "fnords"
|
target := "fnords"
|
||||||
does := stubScope("")
|
does := stubScope("")
|
||||||
does[key.String()] = filterize(scopeConfig{}, target)
|
does[key.String()] = filterFor(scopeConfig{}, target)
|
||||||
doesNot := stubScope("")
|
doesNot := stubScope("")
|
||||||
doesNot[key.String()] = filterize(scopeConfig{}, "smarf")
|
doesNot[key.String()] = filterFor(scopeConfig{}, "smarf")
|
||||||
|
|
||||||
assert.True(t, matches(does, key, target), "does contain")
|
assert.True(t, matches(does, key, target), "does contain")
|
||||||
assert.False(t, matches(doesNot, key, target), "does not 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"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
|
// Scope Factories
|
||||||
|
|
||||||
@ -286,7 +296,7 @@ func (s *sharePoint) Library(library string) []SharePointScope {
|
|||||||
SharePointLibraryItem,
|
SharePointLibraryItem,
|
||||||
SharePointInfoLibraryDrive,
|
SharePointInfoLibraryDrive,
|
||||||
[]string{library},
|
[]string{library},
|
||||||
wrapFilter(filters.Equal)),
|
filters.Equal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +376,7 @@ func (s *sharePoint) CreatedAfter(timeStrings string) []SharePointScope {
|
|||||||
SharePointLibraryItem,
|
SharePointLibraryItem,
|
||||||
SharePointInfoCreatedAfter,
|
SharePointInfoCreatedAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +386,7 @@ func (s *sharePoint) CreatedBefore(timeStrings string) []SharePointScope {
|
|||||||
SharePointLibraryItem,
|
SharePointLibraryItem,
|
||||||
SharePointInfoCreatedBefore,
|
SharePointInfoCreatedBefore,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Greater)),
|
filters.Greater),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +396,7 @@ func (s *sharePoint) ModifiedAfter(timeStrings string) []SharePointScope {
|
|||||||
SharePointLibraryItem,
|
SharePointLibraryItem,
|
||||||
SharePointInfoModifiedAfter,
|
SharePointInfoModifiedAfter,
|
||||||
[]string{timeStrings},
|
[]string{timeStrings},
|
||||||
wrapFilter(filters.Less)),
|
filters.Less),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +406,7 @@ func (s *sharePoint) ModifiedBefore(timeStrings string) []SharePointScope {
|
|||||||
SharePointLibraryItem,
|
SharePointLibraryItem,
|
||||||
SharePointInfoModifiedBefore,
|
SharePointInfoModifiedBefore,
|
||||||
[]string{timeStrings},
|
[]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
|
// Backup Details Filtering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -81,18 +81,18 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_AllData() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
spsc,
|
spsc,
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
SharePointLibraryItem: AnyTgt,
|
SharePointLibraryItem: Any(),
|
||||||
SharePointLibraryFolder: AnyTgt,
|
SharePointLibraryFolder: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
case SharePointListItem:
|
case SharePointListItem:
|
||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
spsc,
|
spsc,
|
||||||
map[categorizer]string{
|
map[categorizer][]string{
|
||||||
SharePointListItem: AnyTgt,
|
SharePointListItem: Any(),
|
||||||
SharePointList: AnyTgt,
|
SharePointList: Any(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -109,8 +109,10 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
|||||||
s2 = "s2"
|
s2 = "s2"
|
||||||
)
|
)
|
||||||
|
|
||||||
sel := NewSharePointRestore([]string{s1, s2})
|
s12 := []string{s1, s2}
|
||||||
sel.Include(sel.WebURL([]string{s1, s2}))
|
|
||||||
|
sel := NewSharePointRestore(s12)
|
||||||
|
sel.Include(sel.WebURL(s12))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Len(t, scopes, 3)
|
require.Len(t, scopes, 3)
|
||||||
|
|
||||||
@ -118,7 +120,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
SharePointScope(sc),
|
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 {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
in []string
|
in []string
|
||||||
expect string
|
expect []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "any",
|
name: "any",
|
||||||
in: []string{AnyTgt},
|
in: []string{AnyTgt},
|
||||||
expect: AnyTgt,
|
expect: Any(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "none",
|
name: "none",
|
||||||
in: []string{NoneTgt},
|
in: []string{NoneTgt},
|
||||||
expect: NoneTgt,
|
expect: None(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -153,7 +155,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
SharePointScope(sc),
|
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"
|
s2 = "s2"
|
||||||
)
|
)
|
||||||
|
|
||||||
sel := NewSharePointRestore([]string{s1, s2})
|
s12 := []string{s1, s2}
|
||||||
sel.Exclude(sel.WebURL([]string{s1, s2}))
|
|
||||||
|
sel := NewSharePointRestore(s12)
|
||||||
|
sel.Exclude(sel.WebURL(s12))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Len(t, scopes, 3)
|
require.Len(t, scopes, 3)
|
||||||
|
|
||||||
@ -177,7 +181,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() {
|
|||||||
scopeMustHave(
|
scopeMustHave(
|
||||||
t,
|
t,
|
||||||
SharePointScope(sc),
|
SharePointScope(sc),
|
||||||
map[categorizer]string{SharePointWebURL: join(s1, s2)},
|
map[categorizer][]string{SharePointWebURL: s12},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user