corso/src/pkg/selectors/exchange.go
Keepers c3f94fd7f7
move drive pagers to pager pattern (#4316)
Drive pager usage currently showcases strong coupling between two layers: drive collection logic processing and drive api.  This PR separates that coupling by moving the full item enumeration process into the
API, and letting the collection logic process the
results. This acs as both a simplification of complex code, and a clearer separation of ownership between the two layers.

A detrimental side effect of this change is that drive item enumeration has moved from page-streaming
(ie: each page is fully processed before moving on to the next) and onto batch processing (ie: all items are stored in memory and processed in a single pass). Acknowledging that this is an unacceptable regression, a follow-up PR will appear shortly with better handling for stream-processing enumeration from the API layer as a standard part of the pattern for all pager
implementations.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2023-09-28 21:23:26 +00:00

819 lines
28 KiB
Go

package selectors
import (
"context"
"fmt"
"strconv"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/backup/identity"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
// ---------------------------------------------------------------------------
// Selectors
// ---------------------------------------------------------------------------
type (
// exchange provides an api for selecting
// data scopes applicable to the Exchange service.
exchange struct {
Selector
}
// ExchangeBackup provides an api for selecting
// data scopes applicable to the Exchange service,
// plus backup-specific methods.
ExchangeBackup struct {
exchange
}
// ExchangeRestore provides an api for selecting
// data scopes applicable to the Exchange service,
// plus restore-specific methods.
ExchangeRestore struct {
exchange
}
)
var (
_ Reducer = &ExchangeRestore{}
_ pathCategorier = &ExchangeRestore{}
_ reasoner = &ExchangeRestore{}
)
// NewExchange produces a new Selector with the service set to ServiceExchange.
func NewExchangeBackup(users []string) *ExchangeBackup {
src := ExchangeBackup{
exchange{
newSelector(ServiceExchange, users),
},
}
return &src
}
// ToExchangeBackup transforms the generic selector into an ExchangeBackup.
// Errors if the service defined by the selector is not ServiceExchange.
func (s Selector) ToExchangeBackup() (*ExchangeBackup, error) {
if s.Service != ServiceExchange {
return nil, badCastErr(ServiceExchange, s.Service)
}
src := ExchangeBackup{exchange{s}}
return &src, nil
}
func (s ExchangeBackup) SplitByResourceOwner(users []string) []ExchangeBackup {
sels := splitByProtectedResource[ExchangeScope](s.Selector, users, ExchangeUser)
ss := make([]ExchangeBackup, 0, len(sels))
for _, sel := range sels {
ss = append(ss, ExchangeBackup{exchange{sel}})
}
return ss
}
// NewExchangeRestore produces a new Selector with the service set to ServiceExchange.
func NewExchangeRestore(users []string) *ExchangeRestore {
src := ExchangeRestore{
exchange{
newSelector(ServiceExchange, users),
},
}
return &src
}
// ToExchangeRestore transforms the generic selector into an ExchangeRestore.
// Errors if the service defined by the selector is not ServiceExchange.
func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) {
if s.Service != ServiceExchange {
return nil, badCastErr(ServiceExchange, s.Service)
}
src := ExchangeRestore{exchange{s}}
return &src, nil
}
func (sr ExchangeRestore) SplitByResourceOwner(users []string) []ExchangeRestore {
sels := splitByProtectedResource[ExchangeScope](sr.Selector, users, ExchangeUser)
ss := make([]ExchangeRestore, 0, len(sels))
for _, sel := range sels {
ss = append(ss, ExchangeRestore{exchange{sel}})
}
return ss
}
// PathCategories produces the aggregation of discrete users described by each type of scope.
func (s exchange) PathCategories() selectorPathCategories {
return selectorPathCategories{
Excludes: pathCategoriesIn[ExchangeScope, exchangeCategory](s.Excludes),
Filters: pathCategoriesIn[ExchangeScope, exchangeCategory](s.Filters),
Includes: pathCategoriesIn[ExchangeScope, exchangeCategory](s.Includes),
}
}
// Reasons returns a deduplicated set of the backup reasons produced
// using the selector's discrete owner and each scopes' service and
// category types.
func (s exchange) Reasons(tenantID string, useOwnerNameForID bool) []identity.Reasoner {
return reasonsFor(s, tenantID, useOwnerNameForID)
}
// ---------------------------------------------------------------------------
// 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 appends the provided scopes to the selector's exclusion set.
// Every Exclusion scope applies globally, affecting all inclusion scopes.
// Data is excluded if it matches ANY exclusion (of the same data category).
//
// All parts of the scope must match for data to be exclucded.
// Ex: Mail(u1, f1, m1) => only excludes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only excludes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Exclude(scopes ...[]ExchangeScope) {
s.Excludes = appendScopes(s.Excludes, scopes...)
}
// Filter appends the provided scopes to the selector's filters set.
// A selector with >0 filters and 0 inclusions will include any data
// that passes all filters.
// A selector with >0 filters and >0 inclusions will reduce the
// inclusion set to only the data that passes all filters.
// Data is retained if it passes ALL filters (of the same data category).
//
// All parts of the scope must match for data to pass the filter.
// Ex: Mail(u1, f1, m1) => only passes mail that is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only passes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Filter(scopes ...[]ExchangeScope) {
s.Filters = appendScopes(s.Filters, scopes...)
}
// Include appends the provided scopes to the selector's inclusion set.
// Data is included if it matches ANY inclusion.
// The inclusion set is later filtered (all included data must pass ALL
// filters) and excluded (all included data must not match ANY exclusion).
// Data is included if it matches ANY inclusion (of the same data category).
//
// All parts of the scope must match for data to be included.
// Ex: Mail(u1, f1, m1) => only includes mail if it is owned by user u1,
// located in folder f1, and ID'd as m1. MailSender(foo) => only includes
// mail whose sender is foo. Use selectors.Any() to wildcard a scope value.
// No value will match if selectors.None() is provided.
//
// Group-level scopes will automatically apply the Any() wildcard to
// child properties.
// ex: User(u1) automatically cascades to all mail, events, and contacts,
// therefore it is the same as selecting all of the following:
// Mail(u1, Any(), Any()), Event(u1, Any()), Contacts(u1, Any(), Any())
func (s *exchange) Include(scopes ...[]ExchangeScope) {
s.Includes = appendScopes(s.Includes, scopes...)
}
// Scopes retrieves the list of exchangeScopes in the selector.
func (s *exchange) Scopes() []ExchangeScope {
return scopes[ExchangeScope](s.Selector)
}
type ExchangeItemScopeConstructor func([]string, []string, ...option) []ExchangeScope
// -------------------
// Scope Factories
// Contacts produces one or more exchange contact scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) Contacts(folders, contacts []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{}
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeContact, contacts, defaultItemOptions(s.Cfg)...).
set(ExchangeContactFolder, folders, opts...))
return scopes
}
// Contactfolders produces one or more exchange contact folder scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) ContactFolders(folders []string, opts ...option) []ExchangeScope {
var (
scopes = []ExchangeScope{}
os = append([]option{pathComparator()}, opts...)
)
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeContactFolder, folders, os...))
return scopes
}
// Events produces one or more exchange event scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) Events(calendars, events []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{}
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeEvent, events, defaultItemOptions(s.Cfg)...).
set(ExchangeEventCalendar, calendars, opts...))
return scopes
}
// EventCalendars produces one or more exchange event calendar scopes.
// Calendars act as folders to contain Events
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) EventCalendars(events []string, opts ...option) []ExchangeScope {
var (
scopes = []ExchangeScope{}
os = append([]option{pathComparator()}, opts...)
)
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeEventCalendar, events, os...))
return scopes
}
// Produces one or more mail scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) Mails(folders, mails []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{}
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeMail, mails, defaultItemOptions(s.Cfg)...).
set(ExchangeMailFolder, folders, opts...))
return scopes
}
// Produces one or more exchange mail folder scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the folder scopes.
func (s *exchange) MailFolders(folders []string, opts ...option) []ExchangeScope {
var (
scopes = []ExchangeScope{}
os = append([]option{pathComparator()}, opts...)
)
scopes = append(
scopes,
makeScope[ExchangeScope](ExchangeMailFolder, folders, os...))
return scopes
}
// Retrieves all exchange data.
// Each user id generates three scopes, one for each data type: contact, event, and mail.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *exchange) AllData() []ExchangeScope {
scopes := []ExchangeScope{}
scopes = append(scopes,
makeScope[ExchangeScope](ExchangeContactFolder, Any()),
makeScope[ExchangeScope](ExchangeEventCalendar, Any()),
makeScope[ExchangeScope](ExchangeMailFolder, Any()))
return scopes
}
// -------------------
// ItemInfo Factories
// ContactName produces one or more exchange contact name info scopes.
// Matches any contact whose name contains the provided string.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) ContactName(senderID string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeContact,
ExchangeInfoContactName,
[]string{senderID},
filters.In),
}
}
// EventOrganizer produces one or more exchange event subject info scopes.
// Matches any event where the event subject contains one of the provided strings.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) EventOrganizer(organizer string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeEvent,
ExchangeInfoEventOrganizer,
[]string{organizer},
filters.In),
}
}
// EventRecurs produces one or more exchange event recurrence info scopes.
// Matches any event if the comparator flag matches the event recurrence flag.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeEvent,
ExchangeInfoEventRecurs,
[]string{recurs},
filters.Equal),
}
}
// EventStartsAfter produces an exchange event starts-after info scope.
// Matches any event where the start time is after the timestring.
// If the input equals selectors.Any, the scope will match all times.
// If the input is empty or selectors.None, the scope will always fail comparisons.
func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeEvent,
ExchangeInfoEventStartsAfter,
[]string{timeStrings},
filters.Less),
}
}
// EventStartsBefore produces an exchange event starts-before info scope.
// Matches any event where the start time is before the timestring.
// If the input equals selectors.Any, the scope will match all times.
// If the input is empty or selectors.None, the scope will always fail comparisons.
func (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeEvent,
ExchangeInfoEventStartsBefore,
[]string{timeStrings},
filters.Greater),
}
}
// EventSubject produces one or more exchange event subject info scopes.
// Matches any event where the event subject contains one of the provided strings.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) EventSubject(subject string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeEvent,
ExchangeInfoEventSubject,
[]string{subject},
filters.In),
}
}
// MailReceivedAfter produces an exchange mail received-after info scope.
// Matches any mail which was received after the timestring.
// If the input equals selectors.Any, the scope will match all times.
// If the input is empty or selectors.None, the scope will always fail comparisons.
func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeMail,
ExchangeInfoMailReceivedAfter,
[]string{timeStrings},
filters.Less),
}
}
// MailReceivedBefore produces an exchange mail received-before info scope.
// Matches any mail which was received before the timestring.
// If the input equals selectors.Any, the scope will match all times.
// If the input is empty or selectors.None, the scope will always fail comparisons.
func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeMail,
ExchangeInfoMailReceivedBefore,
[]string{timeStrings},
filters.Greater),
}
}
// MailSender produces one or more exchange mail sender info scopes.
// Matches any mail whose sender contains one of the provided strings.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailSender(sender string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeMail,
ExchangeInfoMailSender,
[]string{sender},
filters.In),
}
}
// MailSubject produces one or more exchange mail subject line info scopes.
// Matches any mail whose subject contains one of the provided strings.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *ExchangeRestore) MailSubject(subject string) []ExchangeScope {
return []ExchangeScope{
makeInfoScope[ExchangeScope](
ExchangeMail,
ExchangeInfoMailSubject,
[]string{subject},
filters.In),
}
}
// ---------------------------------------------------------------------------
// Categories
// ---------------------------------------------------------------------------
// exchangeCategory enumerates the type of the lowest level
// of data specified by the scope.
type exchangeCategory string
// interface compliance checks
var _ categorizer = ExchangeCategoryUnknown
const (
ExchangeCategoryUnknown exchangeCategory = ""
// types of data identified by exchange
ExchangeContact exchangeCategory = "ExchangeContact"
ExchangeContactFolder exchangeCategory = "ExchangeContactFolder"
ExchangeEvent exchangeCategory = "ExchangeEvent"
ExchangeEventCalendar exchangeCategory = "ExchangeEventCalendar"
ExchangeMail exchangeCategory = "ExchangeMail"
ExchangeMailFolder exchangeCategory = "ExchangeMailFolder"
ExchangeUser exchangeCategory = "ExchangeUser"
// data contained within details.ItemInfo
ExchangeInfoMailSender exchangeCategory = "ExchangeInfoMailSender"
ExchangeInfoMailSubject exchangeCategory = "ExchangeInfoMailSubject"
ExchangeInfoMailReceivedAfter exchangeCategory = "ExchangeInfoMailReceivedAfter"
ExchangeInfoMailReceivedBefore exchangeCategory = "ExchangeInfoMailReceivedBefore"
ExchangeInfoContactName exchangeCategory = "ExchangeInfoContactName"
ExchangeInfoEventOrganizer exchangeCategory = "ExchangeInfoEventOrganizer"
ExchangeInfoEventRecurs exchangeCategory = "ExchangeInfoEventRecurs"
ExchangeInfoEventStartsAfter exchangeCategory = "ExchangeInfoEventStartsAfter"
ExchangeInfoEventStartsBefore exchangeCategory = "ExchangeInfoEventStartsBefore"
ExchangeInfoEventSubject exchangeCategory = "ExchangeInfoEventSubject"
)
// exchangeLeafProperties describes common metadata of the leaf categories
var exchangeLeafProperties = map[categorizer]leafProperty{
ExchangeContact: {
pathKeys: []categorizer{ExchangeContactFolder, ExchangeContact},
pathType: path.ContactsCategory,
},
ExchangeEvent: {
pathKeys: []categorizer{ExchangeEventCalendar, ExchangeEvent},
pathType: path.EventsCategory,
},
ExchangeMail: {
pathKeys: []categorizer{ExchangeMailFolder, ExchangeMail},
pathType: path.EmailCategory,
},
ExchangeUser: { // the root category must be represented, even though it isn't a leaf
pathKeys: []categorizer{ExchangeUser},
pathType: path.UnknownCategory,
},
}
func (ec exchangeCategory) String() string {
return string(ec)
}
// leafCat 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.
// If the receiver category is an info type (ex: ExchangeInfoMailSubject),
// returns the category covered by the info.
// Ex: ExchangeContactFolder.leafCat() => ExchangeContact
// Ex: ExchangeEvent.leafCat() => ExchangeEvent
// Ex: ExchangeUser.leafCat() => ExchangeUser
func (ec exchangeCategory) leafCat() categorizer {
switch ec {
case ExchangeContact, ExchangeContactFolder, ExchangeInfoContactName:
return ExchangeContact
case ExchangeEvent, ExchangeEventCalendar, ExchangeInfoEventOrganizer, ExchangeInfoEventRecurs,
ExchangeInfoEventStartsAfter, ExchangeInfoEventStartsBefore, ExchangeInfoEventSubject:
return ExchangeEvent
case ExchangeMail, ExchangeMailFolder, ExchangeInfoMailReceivedAfter,
ExchangeInfoMailReceivedBefore, ExchangeInfoMailSender, ExchangeInfoMailSubject:
return ExchangeMail
}
return ec
}
// rootCat returns the root category type.
func (ec exchangeCategory) rootCat() categorizer {
return ExchangeUser
}
// unknownCat returns the unknown category type.
func (ec exchangeCategory) unknownCat() categorizer {
return ExchangeCategoryUnknown
}
// isUnion returns true if c is a user
func (ec exchangeCategory) isUnion() bool {
return ec == ec.rootCat()
}
// isLeaf is true if the category is a mail, event, or contact category.
func (ec exchangeCategory) isLeaf() bool {
return ec == ec.leafCat()
}
// pathValues transforms the two paths to maps of identified properties.
//
// Example:
// [tenantID, service, userPN, category, mailFolder, mailID]
// => {exchMailFolder: mailFolder, exchMail: mailID}
func (ec exchangeCategory) pathValues(
repo path.Path,
ent details.Entry,
cfg Config,
) (map[categorizer][]string, error) {
var folderCat, itemCat categorizer
switch ec {
case ExchangeContact:
folderCat, itemCat = ExchangeContactFolder, ExchangeContact
case ExchangeEvent:
folderCat, itemCat = ExchangeEventCalendar, ExchangeEvent
case ExchangeMail:
folderCat, itemCat = ExchangeMailFolder, ExchangeMail
default:
return nil, clues.New("bad exchanageCategory").With("category", ec)
}
item := ent.ItemRef
if len(item) == 0 {
item = repo.Item()
}
items := []string{ent.ShortRef, item}
// only include the item ID when the user is NOT matching
// item names. Exchange data does not contain an item name,
// only an ID, and we don't want to mix up the two.
if cfg.OnlyMatchItemNames {
items = []string{ent.ShortRef}
}
// Will hit the if-condition when we're at a top-level folder, but we'll get
// the same result when we extract from the RepoRef.
folder := ent.LocationRef
if len(folder) == 0 {
folder = repo.Folder(true)
}
result := map[categorizer][]string{
folderCat: {folder},
itemCat: items,
}
return result, nil
}
// pathKeys returns the path keys recognized by the receiver's leaf type.
func (ec exchangeCategory) pathKeys() []categorizer {
return exchangeLeafProperties[ec.leafCat()].pathKeys
}
// PathType converts the category's leaf type into the matching path.CategoryType.
func (ec exchangeCategory) PathType() path.CategoryType {
return exchangeLeafProperties[ec.leafCat()].pathType
}
// ---------------------------------------------------------------------------
// 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 exchangeCategory(getCategory(s))
}
// 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()
}
// Matches returns true if the category is included in the scope's
// data type, and the target string matches that category's comparator.
func (s ExchangeScope) Matches(cat exchangeCategory, target string) bool {
return matches(s, cat, target)
}
// InfoCategory returns the category enum of the scope info.
// If the scope is not an info type, returns ExchangeUnknownCategory.
func (s ExchangeScope) InfoCategory() exchangeCategory {
return exchangeCategory(getInfoCategory(s))
}
// IncludeCategory checks whether the scope includes a certain category of data.
// Ex: to check if the scope includes mail data:
// s.IncludesCategory(selector.ExchangeMail)
func (s ExchangeScope) IncludesCategory(cat exchangeCategory) bool {
return categoryMatches(s.Category(), cat)
}
// returns true if the category is included in the scope's data type,
// and the value is set to Any().
func (s ExchangeScope) IsAny(cat exchangeCategory) bool {
return IsAnyTarget(s, cat)
}
// Get returns the data category in the scope. If the scope
// contains all data types for a user, it'll return the
// ExchangeUser category.
func (s ExchangeScope) Get(cat exchangeCategory) []string {
return getCatValue(s, cat)
}
// sets a value by category to the scope. Only intended for internal use.
func (s ExchangeScope) set(cat exchangeCategory, v []string, opts ...option) ExchangeScope {
os := []option{}
if cat == ExchangeContactFolder || cat == ExchangeEventCalendar || cat == ExchangeMailFolder {
os = append(os, pathComparator())
}
return set(s, cat, v, append(os, opts...)...)
}
// setDefaults ensures that contact folder, mail folder, and user category
// scopes all express `AnyTgt` for their child category types.
func (s ExchangeScope) setDefaults() {
switch s.Category() {
case ExchangeContactFolder:
s[ExchangeContact.String()] = passAny
case ExchangeEventCalendar:
s[ExchangeEvent.String()] = passAny
case ExchangeMailFolder:
s[ExchangeMail.String()] = passAny
case ExchangeUser:
s[ExchangeContactFolder.String()] = passAny
s[ExchangeContact.String()] = passAny
s[ExchangeEvent.String()] = passAny
s[ExchangeMailFolder.String()] = passAny
s[ExchangeMail.String()] = passAny
}
}
// ---------------------------------------------------------------------------
// Backup Details Filtering
// ---------------------------------------------------------------------------
// Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector.
func (s exchange) Reduce(
ctx context.Context,
deets *details.Details,
errs *fault.Bus,
) *details.Details {
return reduce[ExchangeScope](
ctx,
deets,
s.Selector,
map[path.CategoryType]exchangeCategory{
path.ContactsCategory: ExchangeContact,
path.EventsCategory: ExchangeEvent,
path.EmailCategory: ExchangeMail,
},
errs)
}
// matchesInfo handles the standard behavior when comparing a scope and an ExchangeInfo
// returns true if the scope and info match for the provided category.
func (s ExchangeScope) matchesInfo(dii details.ItemInfo) bool {
info := dii.Exchange
if info == nil {
return false
}
infoCat := s.InfoCategory()
cfpc := categoryFromItemType(info.ItemType)
if !typeAndCategoryMatches(infoCat, cfpc) {
return false
}
i := ""
switch infoCat {
case ExchangeInfoContactName:
i = info.ContactName
case ExchangeInfoEventOrganizer:
i = info.Organizer
case ExchangeInfoEventRecurs:
i = strconv.FormatBool(info.EventRecurs)
case ExchangeInfoEventStartsAfter, ExchangeInfoEventStartsBefore:
i = dttm.Format(info.EventStart)
case ExchangeInfoEventSubject:
i = info.Subject
case ExchangeInfoMailSender:
i = info.Sender
case ExchangeInfoMailSubject:
i = info.Subject
case ExchangeInfoMailReceivedAfter, ExchangeInfoMailReceivedBefore:
i = dttm.Format(info.Received)
}
return s.Matches(infoCat, i)
}
// categoryFromItemType interprets the category represented by the ExchangeInfo
// struct. Since every ExchangeInfo can hold all exchange data info, the exact
// type that the struct represents must be compared using its ItemType prop.
func categoryFromItemType(pct details.ItemType) exchangeCategory {
switch pct {
case details.ExchangeContact:
return ExchangeContact
case details.ExchangeMail:
return ExchangeMail
case details.ExchangeEvent:
return ExchangeEvent
}
return ExchangeCategoryUnknown
}