diff --git a/src/pkg/backup/backup.go b/src/pkg/backup/backup.go index e1aa1af52..f6f8589f3 100644 --- a/src/pkg/backup/backup.go +++ b/src/pkg/backup/backup.go @@ -92,7 +92,7 @@ func (b Backup) MinimumPrintable() any { StartedAt: b.StartedAt, Status: b.Status, Version: "0", - Selectors: b.Selectors.Printable(), + Selectors: b.Selectors.ToPrintable(), } } @@ -117,6 +117,6 @@ func (b Backup) Values() []string { common.FormatTime(b.StartedAt), string(b.ID), status, - b.Selectors.Printable().Resources(), + b.Selectors.ToPrintable().Resources(), } } diff --git a/src/pkg/backup/backup_test.go b/src/pkg/backup/backup_test.go index 2768235a1..4acf39c6c 100644 --- a/src/pkg/backup/backup_test.go +++ b/src/pkg/backup/backup_test.go @@ -90,7 +90,7 @@ func (suite *BackupSuite) TestBackup_MinimumPrintable() { assert.Equal(t, now, result.StartedAt, "started at") assert.Equal(t, b.Status, result.Status, "status") - bselp := b.Selectors.Printable() + bselp := b.Selectors.ToPrintable() assert.Equal(t, bselp, result.Selectors, "selectors") assert.Equal(t, bselp.Resources(), result.Selectors.Resources(), "selector resources") } diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 119c05194..35f717974 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -80,6 +80,11 @@ func (s Selector) ToExchangeRestore() (*ExchangeRestore, error) { return &src, nil } +// Printable creates the minimized display of a selector, formatted for human readability. +func (s exchange) Printable() Printable { + return toPrintable[ExchangeScope](s.Selector) +} + // ------------------- // Exclude/Includes @@ -169,7 +174,7 @@ func (s *exchange) Contacts(users, folders, contacts []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Item, ExchangeContact, users, contacts). + makeScope[ExchangeScope](ExchangeContact, users, contacts). set(ExchangeContactFolder, folders), ) @@ -185,7 +190,7 @@ func (s *exchange) ContactFolders(users, folders []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Group, ExchangeContactFolder, users, folders), + makeScope[ExchangeScope](ExchangeContactFolder, users, folders), ) return scopes @@ -200,7 +205,7 @@ func (s *exchange) Events(users, calendars, events []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Item, ExchangeEvent, users, events). + makeScope[ExchangeScope](ExchangeEvent, users, events). set(ExchangeEventCalendar, calendars), ) @@ -217,7 +222,7 @@ func (s *exchange) EventCalendars(users, events []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Group, ExchangeEventCalendar, users, events), + makeScope[ExchangeScope](ExchangeEventCalendar, users, events), ) return scopes @@ -232,7 +237,7 @@ func (s *exchange) Mails(users, folders, mails []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Item, ExchangeMail, users, mails). + makeScope[ExchangeScope](ExchangeMail, users, mails). set(ExchangeMailFolder, folders), ) @@ -248,7 +253,7 @@ func (s *exchange) MailFolders(users, folders []string) []ExchangeScope { scopes = append( scopes, - makeScope[ExchangeScope](Group, ExchangeMailFolder, users, folders), + makeScope[ExchangeScope](ExchangeMailFolder, users, folders), ) return scopes @@ -263,9 +268,9 @@ func (s *exchange) Users(users []string) []ExchangeScope { scopes := []ExchangeScope{} scopes = append(scopes, - makeScope[ExchangeScope](Group, ExchangeContactFolder, users, Any()), - makeScope[ExchangeScope](Item, ExchangeEventCalendar, users, Any()), - makeScope[ExchangeScope](Group, ExchangeMailFolder, users, Any()), + makeScope[ExchangeScope](ExchangeContactFolder, users, Any()), + makeScope[ExchangeScope](ExchangeEventCalendar, users, Any()), + makeScope[ExchangeScope](ExchangeMailFolder, users, Any()), ) return scopes @@ -618,12 +623,6 @@ func (s ExchangeScope) FilterCategory() exchangeCategory { return exchangeCategory(getFilterCategory(s)) } -// Granularity describes the granularity (directory || item) -// of the data in scope. -func (s ExchangeScope) Granularity() string { - return getGranularity(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) diff --git a/src/pkg/selectors/helpers_test.go b/src/pkg/selectors/helpers_test.go index cdc1198cf..bafffe791 100644 --- a/src/pkg/selectors/helpers_test.go +++ b/src/pkg/selectors/helpers_test.go @@ -88,8 +88,7 @@ func (ms mockScope) matchesEntry( func (ms mockScope) setDefaults() {} const ( - shouldMatch = "should-match-entry" - stubResource = "stubResource" + shouldMatch = "should-match-entry" ) // helper funcs @@ -102,8 +101,6 @@ func stubScope(match string) mockScope { return mockScope{ rootCatStub.String(): passAny, scopeKeyCategory: filters.Identity(rootCatStub.String()), - scopeKeyGranularity: filters.Identity(Item), - scopeKeyResource: filters.Identity(stubResource), scopeKeyDataType: filters.Identity(rootCatStub.String()), shouldMatch: filters.Identity(sm), } @@ -113,15 +110,25 @@ func stubScope(match string) mockScope { // selectors // --------------------------------------------------------------------------- -func stubSelector() Selector { - return Selector{ - Service: ServiceExchange, - Excludes: []scope{scope(stubScope(""))}, - Filters: []scope{scope(stubScope(""))}, - Includes: []scope{scope(stubScope(""))}, +type mockSel struct { + Selector +} + +func stubSelector() mockSel { + return mockSel{ + Selector: Selector{ + Service: ServiceExchange, + Excludes: []scope{scope(stubScope(""))}, + Filters: []scope{scope(stubScope(""))}, + Includes: []scope{scope(stubScope(""))}, + }, } } +func (s mockSel) Printable() Printable { + return toPrintable[mockScope](s.Selector) +} + // --------------------------------------------------------------------------- // helper funcs // --------------------------------------------------------------------------- diff --git a/src/pkg/selectors/onedrive.go b/src/pkg/selectors/onedrive.go index 820f81bfa..3cec025a6 100644 --- a/src/pkg/selectors/onedrive.go +++ b/src/pkg/selectors/onedrive.go @@ -46,6 +46,11 @@ func (s Selector) ToOneDriveBackup() (*OneDriveBackup, error) { return &src, nil } +// Printable creates the minimized display of a selector, formatted for human readability. +func (s oneDrive) Printable() Printable { + return toPrintable[OneDriveScope](s.Selector) +} + // ------------------- // Scope Factories @@ -113,7 +118,7 @@ func (s *oneDrive) Filter(scopes ...[]OneDriveScope) { func (s *oneDrive) Users(users []string) []OneDriveScope { scopes := []OneDriveScope{} - scopes = append(scopes, makeScope[OneDriveScope](Group, OneDriveUser, users, users)) + scopes = append(scopes, makeScope[OneDriveScope](OneDriveUser, users, users)) return scopes } @@ -238,12 +243,6 @@ func (s OneDriveScope) FilterCategory() oneDriveCategory { return oneDriveCategory(getFilterCategory(s)) } -// Granularity describes the granularity (directory || item) -// of the data in scope. -func (s OneDriveScope) Granularity() string { - return getGranularity(s) -} - // IncludeCategory checks whether the scope includes a // certain category of data. // Ex: to check if the scope includes file data: diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index 2f0a72236..7cdd5adb1 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -117,15 +117,12 @@ type ( // makeScope produces a well formatted, typed scope that ensures all base values are populated. func makeScope[T scopeT]( - granularity string, cat categorizer, resources, vs []string, ) T { s := T{ scopeKeyCategory: filters.Identity(cat.String()), scopeKeyDataType: filters.Identity(cat.leafCat().String()), - scopeKeyGranularity: filters.Identity(granularity), - scopeKeyResource: filters.Identity(join(resources...)), cat.String(): filterize(vs...), cat.rootCat().String(): filterize(resources...), } @@ -141,12 +138,10 @@ func makeFilterScope[T scopeT]( f func([]string) filters.Filter, ) T { return T{ - scopeKeyCategory: filters.Identity(cat.String()), - scopeKeyDataType: filters.Identity(cat.leafCat().String()), - scopeKeyGranularity: filters.Identity(Filter), - scopeKeyInfoFilter: filters.Identity(filterCat.String()), - scopeKeyResource: filters.Identity(Filter), - filterCat.String(): f(clean(vs)), + scopeKeyCategory: filters.Identity(cat.String()), + scopeKeyDataType: filters.Identity(cat.leafCat().String()), + scopeKeyInfoFilter: filters.Identity(filterCat.String()), + filterCat.String(): f(clean(vs)), } } @@ -179,11 +174,6 @@ func getFilterCategory[T scopeT](s T) string { return s[scopeKeyInfoFilter].Target } -// getGranularity returns the scope's granularity value. -func getGranularity[T scopeT](s T) string { - return s[scopeKeyGranularity].Target -} - // getCatValue takes the value of s[cat], split it by the standard // delimiter, and returns the slice. If s[cat] is nil, returns // None(). @@ -203,12 +193,6 @@ func set[T scopeT](s T, cat categorizer, v []string) T { return s } -// granularity describes the granularity (directory || item) -// of the data in scope. -func granularity[T scopeT](s T) string { - return s[scopeKeyGranularity].Target -} - // returns true if the category is included in the scope's category type, // and the value is set to Any(). func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool { diff --git a/src/pkg/selectors/scopes_test.go b/src/pkg/selectors/scopes_test.go index 242541185..8cfedf0da 100644 --- a/src/pkg/selectors/scopes_test.go +++ b/src/pkg/selectors/scopes_test.go @@ -110,12 +110,6 @@ func (suite *SelectorScopesSuite) TestGetCatValue() { assert.Equal(t, None(), getCatValue(stub, leafCatStub)) } -func (suite *SelectorScopesSuite) TestGranularity() { - t := suite.T() - stub := stubScope("") - assert.Equal(t, Item, granularity(stub)) -} - func (suite *SelectorScopesSuite) TestIsAnyTarget() { t := suite.T() stub := stubScope("") @@ -125,13 +119,13 @@ func (suite *SelectorScopesSuite) TestIsAnyTarget() { var reduceTestTable = []struct { name string - sel func() Selector + sel func() mockSel expectLen int expectPasses assert.BoolAssertionFunc }{ { name: "include all", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Filters = nil sel.Excludes = nil @@ -142,7 +136,7 @@ var reduceTestTable = []struct { }, { name: "include none", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Includes[0] = scope(stubScope("none")) sel.Filters = nil @@ -154,7 +148,7 @@ var reduceTestTable = []struct { }, { name: "filter and include all", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Excludes = nil return sel @@ -164,7 +158,7 @@ var reduceTestTable = []struct { }, { name: "include all filter none", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Filters[0] = scope(stubScope("none")) sel.Excludes = nil @@ -175,7 +169,7 @@ var reduceTestTable = []struct { }, { name: "include all exclude all", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Filters = nil return sel @@ -185,7 +179,7 @@ var reduceTestTable = []struct { }, { name: "include all exclude none", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Filters = nil sel.Excludes[0] = scope(stubScope("none")) @@ -196,7 +190,7 @@ var reduceTestTable = []struct { }, { name: "filter all exclude all", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Includes = nil return sel @@ -206,7 +200,7 @@ var reduceTestTable = []struct { }, { name: "filter all exclude none", - sel: func() Selector { + sel: func() mockSel { sel := stubSelector() sel.Includes = nil sel.Excludes[0] = scope(stubScope("none")) @@ -234,7 +228,7 @@ func (suite *SelectorScopesSuite) TestReduce() { for _, test := range reduceTestTable { suite.T().Run(test.name, func(t *testing.T) { ds := deets() - result := reduce[mockScope](&ds, test.sel(), dataCats) + result := reduce[mockScope](&ds, test.sel().Selector, dataCats) require.NotNil(t, result) assert.Len(t, result.Entries, test.expectLen) }) diff --git a/src/pkg/selectors/selectors.go b/src/pkg/selectors/selectors.go index 5643f4776..97022f3e1 100644 --- a/src/pkg/selectors/selectors.go +++ b/src/pkg/selectors/selectors.go @@ -22,20 +22,9 @@ const ( var ErrorBadSelectorCast = errors.New("wrong selector service type") const ( - scopeKeyCategory = "category" - scopeKeyGranularity = "granularity" - scopeKeyInfoFilter = "info_filter" - scopeKeyResource = "resource" - scopeKeyDataType = "type" -) - -// The granularity exprerssed by the scope. Groups imply non-item granularity, -// such as a directory. Items are individual files or objects. -// Filters are properties that search over service-specific info -const ( - Group = "group" - Item = "item" - Filter = "filter" + scopeKeyCategory = "category" + scopeKeyInfoFilter = "info_filter" + scopeKeyDataType = "type" ) // The granularity exprerssed by the scope. Groups imply non-item granularity, @@ -191,23 +180,43 @@ type Printable struct { Includes map[string][]string `json:"includes,omitempty"` } -// Printable is the minimized display of a selector, formatted for human readability. -// This transformer assumes that the scopeKeyResource and scopeKeyDataType have been -// added to all scopes as they were created. It is unable to infer resource or data -// type values from existing scope values. -func (s Selector) Printable() Printable { +// ToPrintable creates the minimized display of a selector, formatted for human readability. +func (s Selector) ToPrintable() Printable { + switch s.Service { + case ServiceExchange: + r, err := s.ToExchangeRestore() + if err != nil { + return Printable{} + } + + return r.Printable() + + case ServiceOneDrive: + r, err := s.ToOneDriveBackup() + if err != nil { + return Printable{} + } + + return r.Printable() + } + + return Printable{} +} + +// toPrintable creates the minimized display of a selector, formatted for human readability. +func toPrintable[T scopeT](s Selector) Printable { return Printable{ Service: s.Service.String(), - Excludes: toResourceTypeMap(s.Excludes), - Filters: toResourceTypeMap(s.Filters), - Includes: toResourceTypeMap(s.Includes), + Excludes: toResourceTypeMap[T](s.Excludes), + Filters: toResourceTypeMap[T](s.Filters), + Includes: toResourceTypeMap[T](s.Includes), } } // Resources generates a tabular-readable output of the resources in Printable. // Only the first (arbitrarily picked) resource is displayed. All others are // simply counted. If no inclusions exist, uses Filters. If no filters exist, -// defaults to "All". +// defaults to "None". // Resource refers to the top-level entity in the service. User for Exchange, // Site for sharepoint, etc. func (p Printable) Resources() string { @@ -217,7 +226,7 @@ func (p Printable) Resources() string { } if len(s) == 0 { - s = "All" + s = "None" } return s @@ -243,22 +252,23 @@ func resourcesShortFormat(m map[string][]string) string { // Transforms the slice to a single map. // Keys are each map's scopeKeyResource value. // Values are the set of all scopeKeyDataTypes for a given resource. -func toResourceTypeMap(ms []scope) map[string][]string { - if len(ms) == 0 { +func toResourceTypeMap[T scopeT](s []scope) map[string][]string { + if len(s) == 0 { return nil } r := make(map[string][]string) - for _, m := range ms { - res := m[scopeKeyResource] + for _, sc := range s { + t := T(sc) + res := sc[t.categorizer().rootCat().String()] k := res.Target if res.Target == AnyTgt { k = All } - r[k] = addToSet(r[k], split(m[scopeKeyDataType].Target)) + r[k] = addToSet(r[k], split(sc[scopeKeyDataType].Target)) } return r diff --git a/src/pkg/selectors/selectors_test.go b/src/pkg/selectors/selectors_test.go index 7475a5790..e1c25e3e6 100644 --- a/src/pkg/selectors/selectors_test.go +++ b/src/pkg/selectors/selectors_test.go @@ -53,13 +53,21 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() { p := sel.Printable() res := p.Resources() - assert.Equal(t, stubResource, res, "resource should state only the stub") + assert.Equal(t, "All", res, "stub starts out as an all-pass") + + stubWithResource := func(resource string) scope { + ss := stubScope("") + ss[rootCatStub.String()] = filterize(resource) + + return scope(ss) + } sel.Includes = []scope{ - scope(stubScope("")), - {scopeKeyResource: filterize("smarf"), scopeKeyDataType: filterize(unknownCatStub.String())}, - {scopeKeyResource: filterize("smurf"), scopeKeyDataType: filterize(unknownCatStub.String())}, + stubWithResource("foo"), + stubWithResource("smarf"), + stubWithResource("fnords"), } + p = sel.Printable() res = p.Resources() @@ -68,12 +76,12 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() { p.Includes = nil res = p.Resources() - assert.Equal(t, stubResource, res, "resource on filters should state only the stub") + assert.Equal(t, "All", res, "filters is also an all-pass") p.Filters = nil res = p.Resources() - assert.Equal(t, "All", res, "resource with no Includes or Filters should state All") + assert.Equal(t, "None", res, "resource with no Includes or Filters should state None") } func (suite *SelectorSuite) TestToResourceTypeMap() { @@ -86,7 +94,7 @@ func (suite *SelectorSuite) TestToResourceTypeMap() { name: "single scope", input: []scope{scope(stubScope(""))}, expect: map[string][]string{ - stubResource: {rootCatStub.String()}, + "All": {rootCatStub.String()}, }, }, { @@ -94,13 +102,13 @@ func (suite *SelectorSuite) TestToResourceTypeMap() { input: []scope{ scope(stubScope("")), { - scopeKeyResource: filterize("smarf"), - scopeKeyDataType: filterize(unknownCatStub.String()), + rootCatStub.String(): filterize("smarf"), + scopeKeyDataType: filterize(unknownCatStub.String()), }, }, expect: map[string][]string{ - stubResource: {rootCatStub.String()}, - "smarf": {unknownCatStub.String()}, + "All": {rootCatStub.String()}, + "smarf": {unknownCatStub.String()}, }, }, { @@ -108,18 +116,18 @@ func (suite *SelectorSuite) TestToResourceTypeMap() { input: []scope{ scope(stubScope("")), { - scopeKeyResource: filterize(stubResource), - scopeKeyDataType: filterize("other"), + rootCatStub.String(): filterize(AnyTgt), + scopeKeyDataType: filterize("other"), }, }, expect: map[string][]string{ - stubResource: {rootCatStub.String(), "other"}, + "All": {rootCatStub.String(), "other"}, }, }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - rtm := toResourceTypeMap(test.input) + rtm := toResourceTypeMap[mockScope](test.input) assert.Equal(t, test.expect, rtm) }) }