From 8d2a437f1d4260d1512215570669a83ff5e326c8 Mon Sep 17 00:00:00 2001 From: Keepers Date: Mon, 22 Aug 2022 18:02:54 -0600 Subject: [PATCH] clean up scope production, cat comparators (#630) Scope production was still using service-type specific factories instead of a generic factory set. This has been centralized so that all service instances share the same scope production concerns. Additionally, category comparator funcs now use generic comparators as well, which allows for the removal of the isType() and includesType() comparator funcs. --- src/pkg/selectors/exchange.go | 103 ++++++++------------------- src/pkg/selectors/exchange_test.go | 66 +----------------- src/pkg/selectors/helpers_test.go | 26 +++---- src/pkg/selectors/onedrive.go | 66 +++++------------- src/pkg/selectors/scopes.go | 108 +++++++++++++++++++++++++---- src/pkg/selectors/selectors.go | 6 +- 6 files changed, 160 insertions(+), 215 deletions(-) diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index c7af07e90..caa889fc6 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -140,32 +140,12 @@ func (s *exchange) Include(scopes ...[]ExchangeScope) { // Scopes retrieves the list of exchangeScopes in the selector. func (s *exchange) Scopes() []ExchangeScope { - scopes := s.scopes() - es := make([]ExchangeScope, len(scopes)) - for i := range scopes { - es[i] = ExchangeScope(scopes[i]) - } - return es + return scopes[ExchangeScope](s.Selector) } // ------------------- // Scope Factories -func makeExchangeScope(granularity string, cat exchangeCategory, vs []string) ExchangeScope { - return ExchangeScope{ - scopeKeyGranularity: granularity, - scopeKeyCategory: cat.String(), - cat.String(): join(vs...), - } -} - -func makeExchangeUserScope(user, granularity string, cat exchangeCategory, vs []string) ExchangeScope { - es := makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user) - es[scopeKeyResource] = user - es[scopeKeyDataType] = cat.leafType().String() - return es -} - // Produces one or more exchange contact scopes. // One scope is created per combination of users,folders,contacts. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] @@ -180,7 +160,7 @@ func (s *exchange) Contacts(users, folders, contacts []string) []ExchangeScope { for _, f := range folders { scopes = append( scopes, - makeExchangeUserScope(u, Item, ExchangeContact, contacts).set(ExchangeContactFolder, f), + makeScope[ExchangeScope](u, Item, ExchangeContact, contacts).set(ExchangeContactFolder, f), ) } } @@ -199,7 +179,7 @@ func (s *exchange) ContactFolders(users, folders []string) []ExchangeScope { for _, u := range users { scopes = append( scopes, - makeExchangeUserScope(u, Group, ExchangeContactFolder, folders), + makeScope[ExchangeScope](u, Group, ExchangeContactFolder, folders), ) } return scopes @@ -217,7 +197,7 @@ func (s *exchange) Events(users, events []string) []ExchangeScope { for _, u := range users { scopes = append( scopes, - makeExchangeUserScope(u, Item, ExchangeEvent, events), + makeScope[ExchangeScope](u, Item, ExchangeEvent, events), ) } return scopes @@ -237,7 +217,7 @@ func (s *exchange) Mails(users, folders, mails []string) []ExchangeScope { for _, f := range folders { scopes = append( scopes, - makeExchangeUserScope(u, Item, ExchangeMail, mails).set(ExchangeMailFolder, f), + makeScope[ExchangeScope](u, Item, ExchangeMail, mails).set(ExchangeMailFolder, f), ) } } @@ -256,7 +236,7 @@ func (s *exchange) MailFolders(users, folders []string) []ExchangeScope { for _, u := range users { scopes = append( scopes, - makeExchangeUserScope(u, Group, ExchangeMailFolder, folders), + makeScope[ExchangeScope](u, Group, ExchangeMailFolder, folders), ) } return scopes @@ -271,23 +251,15 @@ func (s *exchange) Users(users []string) []ExchangeScope { users = normalize(users) scopes := []ExchangeScope{} for _, u := range users { - scopes = append(scopes, makeExchangeUserScope(u, Group, ExchangeContactFolder, Any())) - scopes = append(scopes, makeExchangeUserScope(u, Item, ExchangeEvent, Any())) - scopes = append(scopes, makeExchangeUserScope(u, Group, ExchangeMailFolder, Any())) + scopes = append(scopes, makeScope[ExchangeScope](u, Group, ExchangeContactFolder, Any())) + scopes = append(scopes, makeScope[ExchangeScope](u, Item, ExchangeEvent, Any())) + scopes = append(scopes, makeScope[ExchangeScope](u, Group, ExchangeMailFolder, Any())) } return scopes } -func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) ExchangeScope { - return ExchangeScope{ - scopeKeyGranularity: Filter, - scopeKeyCategory: cat.String(), - scopeKeyInfoFilter: filterCat.String(), - scopeKeyResource: Filter, - scopeKeyDataType: cat.leafType().String(), - filterCat.String(): join(vs...), - } -} +// ------------------- +// Filter Factories // Produces an exchange mail received-after filter scope. // Matches any mail which was received after the timestring. @@ -295,7 +267,7 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha // If the input is empty or selectors.None, the scope will always fail comparisons. func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope { return []ExchangeScope{ - makeExchangeFilterScope(ExchangeMail, ExchangeInfoMailReceivedAfter, []string{timeStrings}), + makeFilterScope[ExchangeScope](ExchangeMail, ExchangeInfoMailReceivedAfter, []string{timeStrings}), } } @@ -305,7 +277,7 @@ func (sr *ExchangeRestore) MailReceivedAfter(timeStrings string) []ExchangeScope // If the input is empty or selectors.None, the scope will always fail comparisons. func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScope { return []ExchangeScope{ - makeExchangeFilterScope(ExchangeMail, ExchangeInfoMailReceivedBefore, []string{timeStrings}), + makeFilterScope[ExchangeScope](ExchangeMail, ExchangeInfoMailReceivedBefore, []string{timeStrings}), } } @@ -316,7 +288,7 @@ func (sr *ExchangeRestore) MailReceivedBefore(timeStrings string) []ExchangeScop // If any slice is empty, it defaults to [selectors.None] func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope { return []ExchangeScope{ - makeExchangeFilterScope(ExchangeMail, ExchangeInfoMailSender, senderIDs), + makeFilterScope[ExchangeScope](ExchangeMail, ExchangeInfoMailSender, senderIDs), } } @@ -327,7 +299,7 @@ func (sr *ExchangeRestore) MailSender(senderIDs []string) []ExchangeScope { // If any slice is empty, it defaults to [selectors.None] func (sr *ExchangeRestore) MailSubject(subjectSubstrings []string) []ExchangeScope { return []ExchangeScope{ - makeExchangeFilterScope(ExchangeMail, ExchangeInfoMailSubject, subjectSubstrings), + makeFilterScope[ExchangeScope](ExchangeMail, ExchangeInfoMailSubject, subjectSubstrings), } } @@ -432,13 +404,13 @@ var exchangePathSet = map[categorizer][]categorizer{ ExchangeUser: {ExchangeUser}, // the root category must be represented } -// leafType returns the leaf category of the receiver. +// 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. -// Ex: ExchangeContactFolder.leafType() => ExchangeContact -// Ex: ExchangeEvent.leafType() => ExchangeEvent -// Ex: ExchangeUser.leafType() => ExchangeUser -func (ec exchangeCategory) leafType() exchangeCategory { +// Ex: ExchangeContactFolder.leafCat() => ExchangeContact +// Ex: ExchangeEvent.leafCat() => ExchangeEvent +// Ex: ExchangeUser.leafCat() => ExchangeUser +func (ec exchangeCategory) leafCat() categorizer { switch ec { case ExchangeContact, ExchangeContactFolder: return ExchangeContact @@ -448,28 +420,14 @@ func (ec exchangeCategory) leafType() exchangeCategory { 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() +// rootCat returns the root category type. +func (ec exchangeCategory) rootCat() categorizer { + return ExchangeUser } -// 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) +// unknownCat returns the unknown category type. +func (ec exchangeCategory) unknownCat() categorizer { + return ExchangeCategoryUnknown } // transforms a path to a map of identified properties. @@ -519,7 +477,7 @@ func (ec exchangeCategory) pathValues(path []string) map[categorizer]string { // pathKeys returns the path keys recognized by the receiver's leaf type. func (ec exchangeCategory) pathKeys() []categorizer { - return exchangePathSet[ec.leafType()] + return exchangePathSet[ec.leafCat()] } // --------------------------------------------------------------------------- @@ -566,7 +524,7 @@ func (s ExchangeScope) Granularity() string { // Ex: to check if the scope includes mail data: // s.IncludesCategory(selector.ExchangeMail) func (s ExchangeScope) IncludesCategory(cat exchangeCategory) bool { - return s.Category().isType(cat) + return categoryMatches(s.Category(), cat) } // returns true if the category is included in the scope's data type, @@ -584,8 +542,7 @@ func (s ExchangeScope) Get(cat exchangeCategory) []string { // sets a value by category to the scope. Only intended for internal use. func (s ExchangeScope) set(cat exchangeCategory, v string) ExchangeScope { - s[cat.String()] = v - return s + return set(s, cat, v) } // setDefaults ensures that contact folder, mail folder, and user category @@ -630,7 +587,7 @@ func (s ExchangeScope) matchesEntry( entry details.DetailsEntry, ) bool { // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat, pathValues) || s.matchesInfo(entry.Exchange) + return matchesPathValues(s, cat.(exchangeCategory), pathValues) || s.matchesInfo(entry.Exchange) } // matchesInfo handles the standard behavior when comparing a scope and an exchangeInfo diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index f9076a2bd..4a6dfff85 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -956,7 +956,7 @@ func (suite *ExchangeSourceSuite) TestIsAny() { } } -func (suite *ExchangeSourceSuite) TestExchangeCategory_LeafType() { +func (suite *ExchangeSourceSuite) TestExchangeCategory_leafCat() { table := []struct { cat exchangeCategory expect exchangeCategory @@ -971,69 +971,7 @@ func (suite *ExchangeSourceSuite) TestExchangeCategory_LeafType() { } 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)) + assert.Equal(t, test.expect, test.cat.leafCat(), test.cat.String()) }) } } diff --git a/src/pkg/selectors/helpers_test.go b/src/pkg/selectors/helpers_test.go index 8a4074fad..07970a74e 100644 --- a/src/pkg/selectors/helpers_test.go +++ b/src/pkg/selectors/helpers_test.go @@ -17,8 +17,8 @@ const ( var _ categorizer = unknownCatStub -func (sc mockCategorizer) String() string { - switch sc { +func (mc mockCategorizer) String() string { + switch mc { case leafCatStub: return "leaf" case rootCatStub: @@ -27,21 +27,23 @@ func (sc mockCategorizer) String() string { return "unknown" } -func (sc mockCategorizer) includesType(cat categorizer) bool { - switch sc { - case rootCatStub: - return cat == rootCatStub - case leafCatStub: - return true - } - return false +func (mc mockCategorizer) leafCat() categorizer { + return mc } -func (sc mockCategorizer) pathValues(path []string) map[categorizer]string { +func (mc mockCategorizer) rootCat() categorizer { + return rootCatStub +} + +func (mc mockCategorizer) unknownCat() categorizer { + return unknownCatStub +} + +func (mc mockCategorizer) pathValues(path []string) map[categorizer]string { return map[categorizer]string{rootCatStub: "stub"} } -func (sc mockCategorizer) pathKeys() []categorizer { +func (mc mockCategorizer) pathKeys() []categorizer { return []categorizer{rootCatStub, leafCatStub} } diff --git a/src/pkg/selectors/onedrive.go b/src/pkg/selectors/onedrive.go index baebd97ba..1a3a9dfbe 100644 --- a/src/pkg/selectors/onedrive.go +++ b/src/pkg/selectors/onedrive.go @@ -102,21 +102,6 @@ func (s *onedrive) Filter(scopes ...[]OneDriveScope) { s.Filters = appendScopes(s.Filters, scopes...) } -func makeOnedriveScope(granularity string, cat onedriveCategory, vs []string) OneDriveScope { - return OneDriveScope{ - scopeKeyGranularity: granularity, - scopeKeyCategory: cat.String(), - cat.String(): join(vs...), - } -} - -func makeOnedriveUserScope(user, granularity string, cat onedriveCategory, vs []string) OneDriveScope { - s := makeOnedriveScope(granularity, cat, vs).set(OneDriveUser, user) - s[scopeKeyResource] = user - s[scopeKeyDataType] = cat.String() // TODO: use leafType() instead of base cat. - return s -} - // Produces one or more onedrive user scopes. // One scope is created per user entry. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] @@ -126,19 +111,14 @@ func (s *onedrive) Users(users []string) []OneDriveScope { users = normalize(users) scopes := []OneDriveScope{} for _, u := range users { - scopes = append(scopes, makeOnedriveUserScope(u, Group, OneDriveUser, users)) + scopes = append(scopes, makeScope[OneDriveScope](u, Group, OneDriveUser, users)) } return scopes } // Scopes retrieves the list of onedriveScopes in the selector. func (s *onedrive) Scopes() []OneDriveScope { - scopes := s.scopes() - ss := make([]OneDriveScope, len(scopes)) - for i := range scopes { - ss[i] = OneDriveScope(scopes[i]) - } - return ss + return scopes[OneDriveScope](s.Selector) } // --------------------------------------------------------------------------- @@ -150,7 +130,7 @@ func (s *onedrive) Scopes() []OneDriveScope { type onedriveCategory int // interface compliance checks -// var _ categorizer = OneDriveCategoryUnknown +var _ categorizer = OneDriveCategoryUnknown //go:generate go run golang.org/x/tools/cmd/stringer -type=onedriveCategory const ( @@ -177,37 +157,23 @@ var oneDrivePathSet = map[categorizer][]categorizer{ OneDriveUser: {OneDriveUser}, // the root category must be represented } -// leafType returns the leaf category of the receiver. +// 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. -// Ex: ServiceTypeFolder.leafType() => ServiceTypeItem -// Ex: ServiceUser.leafType() => ServiceUser -func (c onedriveCategory) leafType() onedriveCategory { +// Ex: ServiceTypeFolder.leafCat() => ServiceTypeItem +// Ex: ServiceUser.leafCat() => ServiceUser +func (c onedriveCategory) leafCat() categorizer { return c } -// 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 (c onedriveCategory) isType(cat onedriveCategory) bool { - if cat == OneDriveCategoryUnknown || c == OneDriveCategoryUnknown { - return false - } - if cat == OneDriveUser || c == OneDriveUser { - return true - } - return c.leafType() == cat.leafType() +// rootCat returns the root category type. +func (c onedriveCategory) rootCat() categorizer { + return OneDriveUser } -// includesType returns true if it matches the isType check for -// the receiver's service category. -func (c onedriveCategory) includesType(cat categorizer) bool { - cc, ok := cat.(onedriveCategory) - if !ok { - return false - } - return c.isType(cc) +// unknownCat returns the unknown category type. +func (c onedriveCategory) unknownCat() categorizer { + return OneDriveCategoryUnknown } // pathValues transforms a path to a map of identified properties. @@ -239,7 +205,7 @@ func (c onedriveCategory) pathValues(path []string) map[categorizer]string { // pathKeys returns the path keys recognized by the receiver's leaf type. func (c onedriveCategory) pathKeys() []categorizer { - return oneDrivePathSet[c.leafType()] + return oneDrivePathSet[c.leafCat()] } // --------------------------------------------------------------------------- @@ -281,7 +247,7 @@ func (s OneDriveScope) Granularity() string { // Ex: to check if the scope includes file data: // s.IncludesCategory(selector.OneDriveFile) func (s OneDriveScope) IncludesCategory(cat onedriveCategory) bool { - return s.Category().isType(cat) + return categoryMatches(s.Category(), cat) } // Contains returns true if the category is included in the scope's @@ -321,7 +287,7 @@ func (s OneDriveScope) matchesEntry( entry details.DetailsEntry, ) bool { // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat, pathValues) || s.matchesInfo(entry.Onedrive) + return matchesPathValues(s, cat.(onedriveCategory), pathValues) || s.matchesInfo(entry.Onedrive) } // matchesInfo handles the standard behavior when comparing a scope and an onedriveInfo diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index 5862a8f04..c05ad0835 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -17,10 +17,18 @@ type ( // String should return the human readable name of the category. String() string - // includesType should return true if the parameterized category is, contextually - // within the service, a subset of the receiver category. Ex: a Mail category - // is a subset of a MailFolder category. - includesType(categorizer) bool + // leafCat should return the lowest level type matching the category. If the type + // has multiple leaf types (ex: the root category) or no leaves (ex: unknown values), + // the same value is returned. Otherwise, if the receiver is an intermediary type, + // such as a folder, then the child value should be returned. + // Ex: fooFolder.leafCat() => foo. + leafCat() categorizer + + // rootCat returns the root category for the categorizer + rootCat() categorizer + + // unknownType returns the unknown category value + unknownCat() categorizer // pathValues should produce a map of category:string pairs populated by extracting // values out of the path that match the given categorizer. @@ -106,14 +114,47 @@ type ( } ) +// makeScope produces a well formatted, typed scope that ensures all base values are populated. +func makeScope[T scopeT]( + resource, granularity string, + cat categorizer, + vs []string, +) T { + s := T{ + scopeKeyCategory: cat.String(), + scopeKeyDataType: cat.leafCat().String(), + scopeKeyGranularity: granularity, + scopeKeyResource: resource, + cat.String(): join(vs...), + cat.rootCat().String(): resource, + } + return s +} + +// makeFilterScope produces a well formatted, typed scope, with properties specifically oriented +// towards identifying filter-type scopes, that ensures all base values are populated. +func makeFilterScope[T scopeT]( + cat, filterCat categorizer, + vs []string, +) T { + return T{ + scopeKeyCategory: cat.String(), + scopeKeyDataType: cat.leafCat().String(), + scopeKeyGranularity: Filter, + scopeKeyInfoFilter: filterCat.String(), + scopeKeyResource: Filter, + filterCat.String(): join(vs...), + } +} + // --------------------------------------------------------------------------- -// funcs +// scope funcs // --------------------------------------------------------------------------- // contains returns true if the category is included in the scope's // data type, and the target string is included in the scope. -func contains[T scopeT](s T, cat categorizer, target string) bool { - if !s.categorizer().includesType(cat) { +func contains[T scopeT, C categoryT](s T, cat C, target string) bool { + if !typeAndCategoryMatches(cat, s.categorizer()) { return false } if len(target) == 0 { @@ -143,6 +184,13 @@ func getCatValue[T scopeT](s T, cat categorizer) []string { return split(v) } +// set sets a value by category to the scope. Only intended for internal +// use, not for exporting to callers. +func set[T scopeT](s T, cat categorizer, v string) T { + s[cat.String()] = v + return s +} + // granularity describes the granularity (directory || item) // of the data in scope. func granularity[T scopeT](s T) string { @@ -151,8 +199,8 @@ func granularity[T scopeT](s T) string { // returns true if the category is included in the scope's category type, // and the value is set to Any(). -func isAnyTarget[T scopeT](s T, cat categorizer) bool { - if !s.categorizer().includesType(cat) { +func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool { + if !typeAndCategoryMatches(cat, s.categorizer()) { return false } return s[cat.String()] == AnyTgt @@ -254,7 +302,7 @@ func scopesByCategory[T scopeT, C categoryT]( for _, sc := range scopes { for _, cat := range cats { t := T(sc) - if t.categorizer().includesType(cat) { + if typeAndCategoryMatches(cat, t.categorizer()) { m[cat] = append(m[cat], t) } } @@ -313,9 +361,9 @@ func passes[T scopeT]( // the categorizer. // Standard expectations apply: None() or missing values always fail, Any() // always succeeds. -func matchesPathValues[T scopeT]( +func matchesPathValues[T scopeT, C categoryT]( sc T, - cat categorizer, + cat C, pathValues map[categorizer]string, ) bool { for _, c := range cat.pathKeys() { @@ -334,7 +382,8 @@ func matchesPathValues[T scopeT]( return false } // all parts of the scope must match - if !isAnyTarget(sc, c) { + cc := c.(C) + if !isAnyTarget(sc, cc) { if !common.ContainsString(target, pv) { return false } @@ -342,3 +391,36 @@ func matchesPathValues[T scopeT]( } return true } + +// --------------------------------------------------------------------------- +// categorizer funcs +// --------------------------------------------------------------------------- + +// categoryMatches returns true if: +// - neither type is 'unknown' +// - either type is the root type +// - the leaf types match +func categoryMatches[C categoryT](a, b C) bool { + u := a.unknownCat() + if a == u || b == u { + return false + } + + r := a.rootCat() + if a == r || b == r { + return true + } + + return a.leafCat() == b.leafCat() +} + +// typeAndCategoryMatches returns true if: +// - both parameters are the same categoryT type +// - the category matches for both types +func typeAndCategoryMatches[C categoryT](a C, b categorizer) bool { + bb, ok := b.(C) + if !ok { + return false + } + return categoryMatches(a, bb) +} diff --git a/src/pkg/selectors/selectors.go b/src/pkg/selectors/selectors.go index c4b6d5b5c..97327adfe 100644 --- a/src/pkg/selectors/selectors.go +++ b/src/pkg/selectors/selectors.go @@ -124,10 +124,10 @@ func appendScopes[T scopeT](to []scope, scopes ...[]T) []scope { // scopes retrieves the list of scopes in the selector. // future TODO: if Inclues is nil, return filters. -func (s *Selector) scopes() []scope { - scopes := []scope{} +func scopes[T scopeT](s Selector) []T { + scopes := []T{} for _, v := range s.Includes { - scopes = append(scopes, v) + scopes = append(scopes, T(v)) } return scopes }