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 {
|
func makeExchangeUserScope(user, granularity string, cat exchangeCategory, vs []string) ExchangeScope {
|
||||||
es := makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user)
|
es := makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user)
|
||||||
es[scopeKeyResource] = user
|
es[scopeKeyResource] = user
|
||||||
es[scopeKeyDataType] = cat.dataType()
|
es[scopeKeyDataType] = cat.leafType().String()
|
||||||
return es
|
return es
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +301,7 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
|
|||||||
scopeKeyCategory: cat.String(),
|
scopeKeyCategory: cat.String(),
|
||||||
scopeKeyInfoFilter: filterCat.String(),
|
scopeKeyInfoFilter: filterCat.String(),
|
||||||
scopeKeyResource: Filter,
|
scopeKeyResource: Filter,
|
||||||
scopeKeyDataType: cat.dataType(),
|
scopeKeyDataType: cat.leafType().String(),
|
||||||
filterCat.String(): join(vs...),
|
filterCat.String(): join(vs...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,17 +383,15 @@ func (d ExchangeDestination) Set(cat exchangeCategory, dest string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Scopes
|
// Categories
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type (
|
// exchangeCategory enumerates the type of the lowest level
|
||||||
// ExchangeScope specifies the data available
|
// of data specified by the scope.
|
||||||
// when interfacing with the Exchange service.
|
type exchangeCategory int
|
||||||
ExchangeScope scope
|
|
||||||
// exchangeCategory enumerates the type of the lowest level
|
// interface compliance checks
|
||||||
// of data () in a scope.
|
var _ categorizer = ExchangeCategoryUnknown
|
||||||
exchangeCategory int
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate stringer -type=exchangeCategory
|
//go:generate stringer -type=exchangeCategory
|
||||||
const (
|
const (
|
||||||
@ -441,35 +439,139 @@ func exchangeCatAtoI(s string) exchangeCategory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exchangeDataType returns the human-readable name of the core data type.
|
// exchangePathSet describes the category type keys used in Exchange paths.
|
||||||
// Ex: ExchangeContactFolder.dataType() => ExchangeContact.String()
|
// The order of each slice is important, and should match the order in which
|
||||||
// Ex: ExchangeEvent.dataType() => ExchangeEvent.String().
|
// these types appear in the canonical Path for each type.
|
||||||
func (ec exchangeCategory) dataType() string {
|
var categoryPathSet = map[categorizer][]categorizer{
|
||||||
switch ec {
|
ExchangeContact: {ExchangeUser, ExchangeContactFolder, ExchangeContact},
|
||||||
case ExchangeContact, ExchangeContactFolder:
|
ExchangeEvent: {ExchangeUser, ExchangeEvent},
|
||||||
return ExchangeContact.String()
|
ExchangeMail: {ExchangeUser, ExchangeMailFolder, ExchangeMail},
|
||||||
case ExchangeMail, ExchangeMailFolder:
|
ExchangeUser: {ExchangeUser}, // the root category must be represented
|
||||||
return ExchangeMail.String()
|
|
||||||
}
|
|
||||||
return ec.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Granularity describes the granularity (directory || item)
|
// leafType returns the leaf category of the receiver.
|
||||||
// of the data in scope
|
// If the receiver category has multiple leaves (ex: User) or no leaves,
|
||||||
func (s ExchangeScope) Granularity() string {
|
// (ex: Unknown), the receiver itself is returned.
|
||||||
return s[scopeKeyGranularity]
|
// 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.
|
// Category describes the type of the data in scope.
|
||||||
func (s ExchangeScope) Category() exchangeCategory {
|
func (s ExchangeScope) Category() exchangeCategory {
|
||||||
return exchangeCatAtoI(s[scopeKeyCategory])
|
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.
|
// Filer describes the specific filter, and its target values.
|
||||||
func (s ExchangeScope) Filter() exchangeCategory {
|
func (s ExchangeScope) Filter() exchangeCategory {
|
||||||
return exchangeCatAtoI(s[scopeKeyInfoFilter])
|
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
|
// IncludeCategory checks whether the scope includes a
|
||||||
// certain category of data.
|
// certain category of data.
|
||||||
// Ex: to check if the scope includes mail 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
|
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.
|
// 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 {
|
func (s ExchangeScope) matches(cat exchangeCategory, path []string, info *details.ExchangeInfo) bool {
|
||||||
return s.matchesPath(cat, path) || s.matchesInfo(cat, info)
|
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
|
// matchesPath handles the standard behavior when comparing a scope and a path
|
||||||
// returns true if the scope and path match for the provided category.
|
// returns true if the scope and path match for the provided category.
|
||||||
func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
||||||
pathIDs := exchangeIDPath(cat, path)
|
pathIDs := cat.pathValues(path)
|
||||||
for _, c := range categoryPathSet[cat] {
|
for _, c := range categoryPathSet[cat] {
|
||||||
target := s.Get(c)
|
target := s.Get(c.(exchangeCategory))
|
||||||
// the scope must define the targets to match on
|
// the scope must define the targets to match on
|
||||||
if len(target) == 0 {
|
if len(target) == 0 {
|
||||||
return false
|
return false
|
||||||
@ -617,47 +712,6 @@ func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
|||||||
// Restore Point Filtering
|
// 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
|
// Reduce reduces the entries in a backupDetails struct to only
|
||||||
// those that match the inclusions, filters, and exclusions in the selector.
|
// those that match the inclusions, filters, and exclusions in the selector.
|
||||||
func (sr *ExchangeRestore) Reduce(deets *details.Details) *details.Details {
|
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