fullfill categorizer for exchange selectors (#602)
This commit is contained in:
parent
4bf6e92098
commit
daa939f5f8
@ -179,7 +179,7 @@ func makeExchangeScope(granularity string, cat exchangeCategory, vs []string) Ex
|
||||
func makeExchangeUserScope(user, granularity string, cat exchangeCategory, vs []string) ExchangeScope {
|
||||
es := makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user)
|
||||
es[scopeKeyResource] = user
|
||||
es[scopeKeyDataType] = cat.dataType()
|
||||
es[scopeKeyDataType] = cat.leafType().String()
|
||||
return es
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
|
||||
scopeKeyCategory: cat.String(),
|
||||
scopeKeyInfoFilter: filterCat.String(),
|
||||
scopeKeyResource: Filter,
|
||||
scopeKeyDataType: cat.dataType(),
|
||||
scopeKeyDataType: cat.leafType().String(),
|
||||
filterCat.String(): join(vs...),
|
||||
}
|
||||
}
|
||||
@ -383,17 +383,15 @@ func (d ExchangeDestination) Set(cat exchangeCategory, dest string) error {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scopes
|
||||
// Categories
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type (
|
||||
// ExchangeScope specifies the data available
|
||||
// when interfacing with the Exchange service.
|
||||
ExchangeScope scope
|
||||
// exchangeCategory enumerates the type of the lowest level
|
||||
// of data () in a scope.
|
||||
exchangeCategory int
|
||||
)
|
||||
// of data specified by the scope.
|
||||
type exchangeCategory int
|
||||
|
||||
// interface compliance checks
|
||||
var _ categorizer = ExchangeCategoryUnknown
|
||||
|
||||
//go:generate stringer -type=exchangeCategory
|
||||
const (
|
||||
@ -441,35 +439,139 @@ func exchangeCatAtoI(s string) exchangeCategory {
|
||||
}
|
||||
}
|
||||
|
||||
// exchangeDataType returns the human-readable name of the core data type.
|
||||
// Ex: ExchangeContactFolder.dataType() => ExchangeContact.String()
|
||||
// Ex: ExchangeEvent.dataType() => ExchangeEvent.String().
|
||||
func (ec exchangeCategory) dataType() string {
|
||||
switch ec {
|
||||
case ExchangeContact, ExchangeContactFolder:
|
||||
return ExchangeContact.String()
|
||||
case ExchangeMail, ExchangeMailFolder:
|
||||
return ExchangeMail.String()
|
||||
}
|
||||
return ec.String()
|
||||
// exchangePathSet describes the category type keys used in Exchange paths.
|
||||
// The order of each slice is important, and should match the order in which
|
||||
// these types appear in the canonical Path for each type.
|
||||
var categoryPathSet = map[categorizer][]categorizer{
|
||||
ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact},
|
||||
ExchangeEvent: {ExchangeUser, ExchangeEvent},
|
||||
ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail},
|
||||
ExchangeUser: {ExchangeUser}, // the root category must be represented
|
||||
}
|
||||
|
||||
// Granularity describes the granularity (directory || item)
|
||||
// of the data in scope
|
||||
func (s ExchangeScope) Granularity() string {
|
||||
return s[scopeKeyGranularity]
|
||||
// leafType returns the leaf category of the receiver.
|
||||
// If the receiver category has multiple leaves (ex: User) or no leaves,
|
||||
// (ex: Unknown), the receiver itself is returned.
|
||||
// Ex: ExchangeContactFolder.leafType() => ExchangeContact
|
||||
// Ex: ExchangeEvent.leafType() => ExchangeEvent
|
||||
// Ex: ExchangeUser.leafType() => ExchangeUser
|
||||
func (ec exchangeCategory) leafType() exchangeCategory {
|
||||
switch ec {
|
||||
case ExchangeContact, ExchangeContactFolder:
|
||||
return ExchangeContact
|
||||
case ExchangeMail, ExchangeMailFolder:
|
||||
return ExchangeMail
|
||||
}
|
||||
return ec
|
||||
}
|
||||
|
||||
// isType checks if either the receiver is a supertype of the parameter,
|
||||
// or if the parameter is a supertype of the receiver.
|
||||
// if either value is an unknown types, the comparison is always false.
|
||||
// if either value is the root type (user), the comparison is always true.
|
||||
func (ec exchangeCategory) isType(cat exchangeCategory) bool {
|
||||
if cat == ExchangeCategoryUnknown || ec == ExchangeCategoryUnknown {
|
||||
return false
|
||||
}
|
||||
if cat == ExchangeUser || ec == ExchangeUser {
|
||||
return true
|
||||
}
|
||||
return ec.leafType() == cat.leafType()
|
||||
}
|
||||
|
||||
// includesType returns true if it matches the isType check for
|
||||
// the receiver's service category.
|
||||
func (ec exchangeCategory) includesType(cat categorizer) bool {
|
||||
c, ok := cat.(exchangeCategory)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return ec.isType(c)
|
||||
}
|
||||
|
||||
// transforms a path to a map of identified properties.
|
||||
// TODO: this should use service-specific funcs in the Paths pkg. Instead of
|
||||
// peeking at the path directly, the caller should compare against values like
|
||||
// path.UserID() and path.Folders().
|
||||
//
|
||||
// Malformed (ie, short len) paths will return incomplete results.
|
||||
// Example:
|
||||
// [tenantID, userID, "mail", mailFolder, mailID]
|
||||
// => {exchUser: userID, exchMailFolder: mailFolder, exchMail: mailID}
|
||||
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
|
||||
m := map[categorizer]string{}
|
||||
if len(path) < 4 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeUser] = path[1]
|
||||
/*
|
||||
TODO/Notice:
|
||||
Mail and Contacts contain folder structures, identified
|
||||
in this code as being at index 3. This assumes a single
|
||||
folder, while in reality users can express subfolder
|
||||
hierarchies of arbirary depth. Subfolder handling is coming
|
||||
at a later time.
|
||||
*/
|
||||
switch ec {
|
||||
case ExchangeContact:
|
||||
if len(path) < 5 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeContactFolder] = path[3]
|
||||
m[ExchangeContact] = path[4]
|
||||
case ExchangeEvent:
|
||||
if len(path) < 4 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeEvent] = path[3]
|
||||
case ExchangeMail:
|
||||
if len(path) < 5 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeMailFolder] = path[3]
|
||||
m[ExchangeMail] = path[4]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// pathKeys returns a map of the path types, keyed by their leaf type,
|
||||
func (ec exchangeCategory) pathKeys() []categorizer {
|
||||
return categoryPathSet[ec.leafType()]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scopes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ExchangeScope specifies the data available
|
||||
// when interfacing with the Exchange service.
|
||||
type ExchangeScope scope
|
||||
|
||||
// interface compliance checks
|
||||
// var _ scoper = &ExchangeScope{}
|
||||
|
||||
// Category describes the type of the data in scope.
|
||||
func (s ExchangeScope) Category() exchangeCategory {
|
||||
return exchangeCatAtoI(s[scopeKeyCategory])
|
||||
}
|
||||
|
||||
// categorizer type is a generic wrapper around Category.
|
||||
// Primarily used by scopes.go to for abstract comparisons.
|
||||
func (s ExchangeScope) categorizer() categorizer {
|
||||
return s.Category()
|
||||
}
|
||||
|
||||
// Filer describes the specific filter, and its target values.
|
||||
func (s ExchangeScope) Filter() exchangeCategory {
|
||||
return exchangeCatAtoI(s[scopeKeyInfoFilter])
|
||||
}
|
||||
|
||||
// Granularity describes the granularity (directory || item)
|
||||
// of the data in scope
|
||||
func (s ExchangeScope) Granularity() string {
|
||||
return s[scopeKeyGranularity]
|
||||
}
|
||||
|
||||
// IncludeCategory checks whether the scope includes a
|
||||
// certain category of data.
|
||||
// Ex: to check if the scope includes mail data:
|
||||
@ -528,13 +630,6 @@ func (s ExchangeScope) set(cat exchangeCategory, v string) ExchangeScope {
|
||||
return s
|
||||
}
|
||||
|
||||
var categoryPathSet = map[exchangeCategory][]exchangeCategory{
|
||||
ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact},
|
||||
ExchangeEvent: {ExchangeUser, ExchangeEvent},
|
||||
ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail},
|
||||
ExchangeUser: {ExchangeUser},
|
||||
}
|
||||
|
||||
// matches returns true if either the path or the info matches the scope details.
|
||||
func (s ExchangeScope) matches(cat exchangeCategory, path []string, info *details.ExchangeInfo) bool {
|
||||
return s.matchesPath(cat, path) || s.matchesInfo(cat, info)
|
||||
@ -586,9 +681,9 @@ func (s ExchangeScope) matchesInfo(cat exchangeCategory, info *details.ExchangeI
|
||||
// matchesPath handles the standard behavior when comparing a scope and a path
|
||||
// returns true if the scope and path match for the provided category.
|
||||
func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
||||
pathIDs := exchangeIDPath(cat, path)
|
||||
pathIDs := cat.pathValues(path)
|
||||
for _, c := range categoryPathSet[cat] {
|
||||
target := s.Get(c)
|
||||
target := s.Get(c.(exchangeCategory))
|
||||
// the scope must define the targets to match on
|
||||
if len(target) == 0 {
|
||||
return false
|
||||
@ -617,47 +712,6 @@ func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
||||
// Restore Point Filtering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// transforms a path to a map of identified properties.
|
||||
// Malformed (ie, short len) paths will return incomplete results.
|
||||
// Example:
|
||||
// [tenantID, userID, "mail", mailFolder, mailID]
|
||||
// => {exchUser: userID, exchMailFolder: mailFolder, exchMail: mailID}
|
||||
func exchangeIDPath(cat exchangeCategory, path []string) map[exchangeCategory]string {
|
||||
m := map[exchangeCategory]string{}
|
||||
if len(path) == 0 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeUser] = path[1]
|
||||
/*
|
||||
TODO/Notice:
|
||||
Mail and Contacts contain folder structures, identified
|
||||
in this code as being at index 3. This assumes a single
|
||||
folder, while in reality users can express subfolder
|
||||
hierarchies of arbirary depth. Subfolder handling is coming
|
||||
at a later time.
|
||||
*/
|
||||
switch cat {
|
||||
case ExchangeContact:
|
||||
if len(path) < 5 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeContactFolder] = path[3]
|
||||
m[ExchangeContact] = path[4]
|
||||
case ExchangeEvent:
|
||||
if len(path) < 4 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeEvent] = path[3]
|
||||
case ExchangeMail:
|
||||
if len(path) < 5 {
|
||||
return m
|
||||
}
|
||||
m[ExchangeMailFolder] = path[3]
|
||||
m[ExchangeMail] = path[4]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Reduce reduces the entries in a backupDetails struct to only
|
||||
// those that match the inclusions, filters, and exclusions in the selector.
|
||||
func (sr *ExchangeRestore) Reduce(deets *details.Details) *details.Details {
|
||||
|
||||
@ -942,3 +942,144 @@ func (suite *ExchangeSourceSuite) TestIsAny() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestExchangeCategory_LeafType() {
|
||||
table := []struct {
|
||||
cat exchangeCategory
|
||||
expect exchangeCategory
|
||||
}{
|
||||
{exchangeCategory(-1), exchangeCategory(-1)},
|
||||
{ExchangeCategoryUnknown, ExchangeCategoryUnknown},
|
||||
{ExchangeUser, ExchangeUser},
|
||||
{ExchangeMailFolder, ExchangeMail},
|
||||
{ExchangeMail, ExchangeMail},
|
||||
{ExchangeContactFolder, ExchangeContact},
|
||||
{ExchangeEvent, ExchangeEvent},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
||||
assert.Equal(t, test.expect, test.cat.leafType(), test.cat.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestExchangeCategory_IsType() {
|
||||
table := []struct {
|
||||
cat exchangeCategory
|
||||
input exchangeCategory
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
// technically this should be false, but we're not reducing fabricated
|
||||
// exchange category values to unknown. Since the type is unexported,
|
||||
// that transformation would be unnecessary.
|
||||
{exchangeCategory(-1), exchangeCategory(-1), assert.True},
|
||||
{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.False},
|
||||
{ExchangeUser, ExchangeCategoryUnknown, assert.False},
|
||||
{ExchangeCategoryUnknown, ExchangeUser, assert.False},
|
||||
{ExchangeUser, ExchangeUser, assert.True},
|
||||
{ExchangeMailFolder, ExchangeMail, assert.True},
|
||||
{ExchangeMailFolder, ExchangeContact, assert.False},
|
||||
{ExchangeContactFolder, ExchangeMail, assert.False},
|
||||
{ExchangeMail, ExchangeMail, assert.True},
|
||||
{ExchangeContactFolder, ExchangeContact, assert.True},
|
||||
{ExchangeContactFolder, ExchangeEvent, assert.False},
|
||||
{ExchangeEvent, ExchangeContact, assert.False},
|
||||
{ExchangeEvent, ExchangeEvent, assert.True},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
||||
test.expect(t, test.cat.isType(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestExchangeCategory_IncludesType() {
|
||||
table := []struct {
|
||||
cat categorizer
|
||||
input categorizer
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
// technically this should be false, but we're not reducing fabricated
|
||||
// exchange category values to unknown. Since the type is unexported,
|
||||
// that transformation would be unnecessary.
|
||||
{exchangeCategory(-1), exchangeCategory(-1), assert.True},
|
||||
{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.False},
|
||||
{ExchangeUser, ExchangeCategoryUnknown, assert.False},
|
||||
{ExchangeCategoryUnknown, ExchangeUser, assert.False},
|
||||
{ExchangeUser, ExchangeUser, assert.True},
|
||||
{ExchangeMailFolder, ExchangeMail, assert.True},
|
||||
{ExchangeMailFolder, ExchangeContact, assert.False},
|
||||
{ExchangeContactFolder, ExchangeMail, assert.False},
|
||||
{ExchangeMail, ExchangeMail, assert.True},
|
||||
{ExchangeContactFolder, ExchangeContact, assert.True},
|
||||
{ExchangeContactFolder, ExchangeEvent, assert.False},
|
||||
{ExchangeEvent, ExchangeContact, assert.False},
|
||||
{ExchangeEvent, ExchangeEvent, assert.True},
|
||||
{ExchangeUser, rootCatStub, assert.False},
|
||||
{rootCatStub, ExchangeUser, assert.False},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
||||
test.expect(t, test.cat.includesType(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestExchangeCategory_PathValues() {
|
||||
contactPath := []string{"ten", "user", "contact", "cfolder", "contactitem"}
|
||||
contactMap := map[categorizer]string{
|
||||
ExchangeUser: contactPath[1],
|
||||
ExchangeContactFolder: contactPath[3],
|
||||
ExchangeContact: contactPath[4],
|
||||
}
|
||||
eventPath := []string{"ten", "user", "event", "eventitem"}
|
||||
eventMap := map[categorizer]string{
|
||||
ExchangeUser: eventPath[1],
|
||||
ExchangeEvent: eventPath[3],
|
||||
}
|
||||
mailPath := []string{"ten", "user", "mail", "mfolder", "mailitem"}
|
||||
mailMap := map[categorizer]string{
|
||||
ExchangeUser: contactPath[1],
|
||||
ExchangeMailFolder: mailPath[3],
|
||||
ExchangeMail: mailPath[4],
|
||||
}
|
||||
table := []struct {
|
||||
cat exchangeCategory
|
||||
path []string
|
||||
expect map[categorizer]string
|
||||
}{
|
||||
{ExchangeCategoryUnknown, nil, map[categorizer]string{}},
|
||||
{ExchangeCategoryUnknown, []string{"a", "b", "c"}, map[categorizer]string{}},
|
||||
{ExchangeContact, contactPath, contactMap},
|
||||
{ExchangeEvent, eventPath, eventMap},
|
||||
{ExchangeMail, mailPath, mailMap},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
||||
assert.Equal(t, test.cat.pathValues(test.path), test.expect)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestExchangeCategory_PathKeys() {
|
||||
contact := []categorizer{ExchangeUser, ExchangeContactFolder, ExchangeContact}
|
||||
event := []categorizer{ExchangeUser, ExchangeEvent}
|
||||
mail := []categorizer{ExchangeUser, ExchangeMailFolder, ExchangeMail}
|
||||
user := []categorizer{ExchangeUser}
|
||||
var empty []categorizer
|
||||
table := []struct {
|
||||
cat exchangeCategory
|
||||
expect []categorizer
|
||||
}{
|
||||
{ExchangeCategoryUnknown, empty},
|
||||
{ExchangeContact, contact},
|
||||
{ExchangeEvent, event},
|
||||
{ExchangeMail, mail},
|
||||
{ExchangeUser, user},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.cat.String(), func(t *testing.T) {
|
||||
assert.Equal(t, test.cat.pathKeys(), test.expect)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user