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.
This commit is contained in:
Keepers 2022-08-22 18:02:54 -06:00 committed by GitHub
parent e7b863c444
commit 8d2a437f1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 215 deletions

View File

@ -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

View File

@ -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())
})
}
}

View File

@ -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}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}