summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/internal/developmentmembership
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/authzed/spicedb/internal/developmentmembership
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/authzed/spicedb/internal/developmentmembership')
-rw-r--r--vendor/github.com/authzed/spicedb/internal/developmentmembership/doc.go2
-rw-r--r--vendor/github.com/authzed/spicedb/internal/developmentmembership/foundsubject.go127
-rw-r--r--vendor/github.com/authzed/spicedb/internal/developmentmembership/membership.go167
-rw-r--r--vendor/github.com/authzed/spicedb/internal/developmentmembership/onrset.go87
-rw-r--r--vendor/github.com/authzed/spicedb/internal/developmentmembership/trackingsubjectset.go235
5 files changed, 618 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/internal/developmentmembership/doc.go b/vendor/github.com/authzed/spicedb/internal/developmentmembership/doc.go
new file mode 100644
index 0000000..325981d
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/internal/developmentmembership/doc.go
@@ -0,0 +1,2 @@
+// Package developmentmembership defines operations with sets. To be used in tests only.
+package developmentmembership
diff --git a/vendor/github.com/authzed/spicedb/internal/developmentmembership/foundsubject.go b/vendor/github.com/authzed/spicedb/internal/developmentmembership/foundsubject.go
new file mode 100644
index 0000000..bf93d56
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/internal/developmentmembership/foundsubject.go
@@ -0,0 +1,127 @@
+package developmentmembership
+
+import (
+ "sort"
+ "strings"
+
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// NewFoundSubject creates a new FoundSubject for a subject and a set of its resources.
+func NewFoundSubject(subject *core.DirectSubject, resources ...tuple.ObjectAndRelation) FoundSubject {
+ return FoundSubject{tuple.FromCoreObjectAndRelation(subject.Subject), nil, subject.CaveatExpression, NewONRSet(resources...)}
+}
+
+// FoundSubject contains a single found subject and all the relationships in which that subject
+// is a member which were found via the ONRs expansion.
+type FoundSubject struct {
+ // subject is the subject found.
+ subject tuple.ObjectAndRelation
+
+ // excludedSubjects are any subjects excluded. Only should be set if subject is a wildcard.
+ excludedSubjects []FoundSubject
+
+ // caveatExpression is the conditional expression on the found subject.
+ caveatExpression *core.CaveatExpression
+
+ // resources are the resources under which the subject lives that informed the locating
+ // of this subject for the root ONR.
+ resources ONRSet
+}
+
+// GetSubjectId is named to match the Subject interface for the BaseSubjectSet.
+//
+//nolint:all
+func (fs FoundSubject) GetSubjectId() string {
+ return fs.subject.ObjectID
+}
+
+func (fs FoundSubject) GetCaveatExpression() *core.CaveatExpression {
+ return fs.caveatExpression
+}
+
+func (fs FoundSubject) GetExcludedSubjects() []FoundSubject {
+ return fs.excludedSubjects
+}
+
+// Subject returns the Subject of the FoundSubject.
+func (fs FoundSubject) Subject() tuple.ObjectAndRelation {
+ return fs.subject
+}
+
+// WildcardType returns the object type for the wildcard subject, if this is a wildcard subject.
+func (fs FoundSubject) WildcardType() (string, bool) {
+ if fs.subject.ObjectID == tuple.PublicWildcard {
+ return fs.subject.ObjectType, true
+ }
+
+ return "", false
+}
+
+// ExcludedSubjectsFromWildcard returns those subjects excluded from the wildcard subject.
+// If not a wildcard subject, returns false.
+func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]FoundSubject, bool) {
+ if fs.subject.ObjectID == tuple.PublicWildcard {
+ return fs.excludedSubjects, true
+ }
+
+ return nil, false
+}
+
+func (fs FoundSubject) excludedSubjectStrings() []string {
+ excludedStrings := make([]string, 0, len(fs.excludedSubjects))
+ for _, excludedSubject := range fs.excludedSubjects {
+ excludedSubjectString := tuple.StringONR(excludedSubject.subject)
+ if excludedSubject.GetCaveatExpression() != nil {
+ excludedSubjectString += "[...]"
+ }
+ excludedStrings = append(excludedStrings, excludedSubjectString)
+ }
+
+ sort.Strings(excludedStrings)
+ return excludedStrings
+}
+
+// ToValidationString returns the FoundSubject in a format that is consumable by the validationfile
+// package.
+func (fs FoundSubject) ToValidationString() string {
+ onrString := tuple.StringONR(fs.Subject())
+ validationString := onrString
+ if fs.caveatExpression != nil {
+ validationString = validationString + "[...]"
+ }
+
+ excluded, isWildcard := fs.ExcludedSubjectsFromWildcard()
+ if isWildcard && len(excluded) > 0 {
+ validationString = validationString + " - {" + strings.Join(fs.excludedSubjectStrings(), ", ") + "}"
+ }
+
+ return validationString
+}
+
+func (fs FoundSubject) String() string {
+ return fs.ToValidationString()
+}
+
+// ParentResources returns all the resources in which the subject was found as per the expand.
+func (fs FoundSubject) ParentResources() []tuple.ObjectAndRelation {
+ return fs.resources.AsSlice()
+}
+
+// FoundSubjects contains the subjects found for a specific ONR.
+type FoundSubjects struct {
+ // subjects is a map from the Subject ONR (as a string) to the FoundSubject information.
+ subjects *TrackingSubjectSet
+}
+
+// ListFound returns a slice of all the FoundSubject's.
+func (fs FoundSubjects) ListFound() []FoundSubject {
+ return fs.subjects.ToSlice()
+}
+
+// LookupSubject returns the FoundSubject for a matching subject, if any.
+func (fs FoundSubjects) LookupSubject(subject tuple.ObjectAndRelation) (FoundSubject, bool) {
+ return fs.subjects.Get(subject)
+}
diff --git a/vendor/github.com/authzed/spicedb/internal/developmentmembership/membership.go b/vendor/github.com/authzed/spicedb/internal/developmentmembership/membership.go
new file mode 100644
index 0000000..caa5e8f
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/internal/developmentmembership/membership.go
@@ -0,0 +1,167 @@
+package developmentmembership
+
+import (
+ "fmt"
+
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// Set represents the set of membership for one or more ONRs, based on expansion
+// trees.
+type Set struct {
+ // objectsAndRelations is a map from an ONR (as a string) to the subjects found for that ONR.
+ objectsAndRelations map[string]FoundSubjects
+}
+
+// SubjectsByONR returns a map from ONR (as a string) to the FoundSubjects for that ONR.
+func (ms *Set) SubjectsByONR() map[string]FoundSubjects {
+ return ms.objectsAndRelations
+}
+
+// NewMembershipSet constructs a new membership set.
+//
+// NOTE: This is designed solely for the developer API and should *not* be used in any performance
+// sensitive code.
+func NewMembershipSet() *Set {
+ return &Set{
+ objectsAndRelations: map[string]FoundSubjects{},
+ }
+}
+
+// AddExpansion adds the expansion of an ONR to the membership set. Returns false if the ONR was already added.
+//
+// NOTE: The expansion tree *should* be the fully recursive expansion.
+func (ms *Set) AddExpansion(onr tuple.ObjectAndRelation, expansion *core.RelationTupleTreeNode) (FoundSubjects, bool, error) {
+ onrString := tuple.StringONR(onr)
+ existing, ok := ms.objectsAndRelations[onrString]
+ if ok {
+ return existing, false, nil
+ }
+
+ tss, err := populateFoundSubjects(onr, expansion)
+ if err != nil {
+ return FoundSubjects{}, false, err
+ }
+
+ fs := tss.ToFoundSubjects()
+ ms.objectsAndRelations[onrString] = fs
+ return fs, true, nil
+}
+
+// AccessibleExpansionSubjects returns a TrackingSubjectSet representing the set of accessible subjects in the expansion.
+func AccessibleExpansionSubjects(treeNode *core.RelationTupleTreeNode) (*TrackingSubjectSet, error) {
+ return populateFoundSubjects(tuple.FromCoreObjectAndRelation(treeNode.Expanded), treeNode)
+}
+
+func populateFoundSubjects(rootONR tuple.ObjectAndRelation, treeNode *core.RelationTupleTreeNode) (*TrackingSubjectSet, error) {
+ resource := rootONR
+ if treeNode.Expanded != nil {
+ resource = tuple.FromCoreObjectAndRelation(treeNode.Expanded)
+ }
+
+ switch typed := treeNode.NodeType.(type) {
+ case *core.RelationTupleTreeNode_IntermediateNode:
+ switch typed.IntermediateNode.Operation {
+ case core.SetOperationUserset_UNION:
+ toReturn := NewTrackingSubjectSet()
+ for _, child := range typed.IntermediateNode.ChildNodes {
+ tss, err := populateFoundSubjects(resource, child)
+ if err != nil {
+ return nil, err
+ }
+
+ err = toReturn.AddFrom(tss)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ toReturn.ApplyParentCaveatExpression(treeNode.CaveatExpression)
+ return toReturn, nil
+
+ case core.SetOperationUserset_INTERSECTION:
+ if len(typed.IntermediateNode.ChildNodes) == 0 {
+ return nil, fmt.Errorf("found intersection with no children")
+ }
+
+ firstChildSet, err := populateFoundSubjects(rootONR, typed.IntermediateNode.ChildNodes[0])
+ if err != nil {
+ return nil, err
+ }
+
+ toReturn := NewTrackingSubjectSet()
+ err = toReturn.AddFrom(firstChildSet)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, child := range typed.IntermediateNode.ChildNodes[1:] {
+ childSet, err := populateFoundSubjects(rootONR, child)
+ if err != nil {
+ return nil, err
+ }
+
+ updated, err := toReturn.Intersect(childSet)
+ if err != nil {
+ return nil, err
+ }
+
+ toReturn = updated
+ }
+
+ toReturn.ApplyParentCaveatExpression(treeNode.CaveatExpression)
+ return toReturn, nil
+
+ case core.SetOperationUserset_EXCLUSION:
+ if len(typed.IntermediateNode.ChildNodes) == 0 {
+ return nil, fmt.Errorf("found exclusion with no children")
+ }
+
+ firstChildSet, err := populateFoundSubjects(rootONR, typed.IntermediateNode.ChildNodes[0])
+ if err != nil {
+ return nil, err
+ }
+
+ toReturn := NewTrackingSubjectSet()
+ err = toReturn.AddFrom(firstChildSet)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, child := range typed.IntermediateNode.ChildNodes[1:] {
+ childSet, err := populateFoundSubjects(rootONR, child)
+ if err != nil {
+ return nil, err
+ }
+ toReturn = toReturn.Exclude(childSet)
+ }
+
+ toReturn.ApplyParentCaveatExpression(treeNode.CaveatExpression)
+ return toReturn, nil
+
+ default:
+ return nil, spiceerrors.MustBugf("unknown expand operation")
+ }
+
+ case *core.RelationTupleTreeNode_LeafNode:
+ toReturn := NewTrackingSubjectSet()
+ for _, subject := range typed.LeafNode.Subjects {
+ fs := NewFoundSubject(subject)
+ err := toReturn.Add(fs)
+ if err != nil {
+ return nil, err
+ }
+
+ fs.resources.Add(resource)
+ }
+
+ toReturn.ApplyParentCaveatExpression(treeNode.CaveatExpression)
+ return toReturn, nil
+
+ default:
+ return nil, spiceerrors.MustBugf("unknown TreeNode type")
+ }
+}
diff --git a/vendor/github.com/authzed/spicedb/internal/developmentmembership/onrset.go b/vendor/github.com/authzed/spicedb/internal/developmentmembership/onrset.go
new file mode 100644
index 0000000..ad7fcfd
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/internal/developmentmembership/onrset.go
@@ -0,0 +1,87 @@
+package developmentmembership
+
+import (
+ "github.com/ccoveille/go-safecast"
+
+ "github.com/authzed/spicedb/pkg/genutil/mapz"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// TODO(jschorr): Replace with the generic set over tuple.ObjectAndRelation
+
+// ONRSet is a set of ObjectAndRelation's.
+type ONRSet struct {
+ onrs *mapz.Set[tuple.ObjectAndRelation]
+}
+
+// NewONRSet creates a new set.
+func NewONRSet(onrs ...tuple.ObjectAndRelation) ONRSet {
+ created := ONRSet{
+ onrs: mapz.NewSet[tuple.ObjectAndRelation](),
+ }
+ created.Update(onrs)
+ return created
+}
+
+// Length returns the size of the set.
+func (ons ONRSet) Length() uint64 {
+ // This is the length of a set so we should never fall out of bounds.
+ length, _ := safecast.ToUint64(ons.onrs.Len())
+ return length
+}
+
+// IsEmpty returns whether the set is empty.
+func (ons ONRSet) IsEmpty() bool {
+ return ons.onrs.IsEmpty()
+}
+
+// Has returns true if the set contains the given ONR.
+func (ons ONRSet) Has(onr tuple.ObjectAndRelation) bool {
+ return ons.onrs.Has(onr)
+}
+
+// Add adds the given ONR to the set. Returns true if the object was not in the set before this
+// call and false otherwise.
+func (ons ONRSet) Add(onr tuple.ObjectAndRelation) bool {
+ return ons.onrs.Add(onr)
+}
+
+// Update updates the set by adding the given ONRs to it.
+func (ons ONRSet) Update(onrs []tuple.ObjectAndRelation) {
+ for _, onr := range onrs {
+ ons.Add(onr)
+ }
+}
+
+// UpdateFrom updates the set by adding the ONRs found in the other set to it.
+func (ons ONRSet) UpdateFrom(otherSet ONRSet) {
+ if otherSet.onrs == nil {
+ return
+ }
+ ons.onrs.Merge(otherSet.onrs)
+}
+
+// Intersect returns an intersection between this ONR set and the other set provided.
+func (ons ONRSet) Intersect(otherSet ONRSet) ONRSet {
+ return ONRSet{ons.onrs.Intersect(otherSet.onrs)}
+}
+
+// Subtract returns a subtraction from this ONR set of the other set provided.
+func (ons ONRSet) Subtract(otherSet ONRSet) ONRSet {
+ return ONRSet{ons.onrs.Subtract(otherSet.onrs)}
+}
+
+// Union returns a copy of this ONR set with the other set's elements added in.
+func (ons ONRSet) Union(otherSet ONRSet) ONRSet {
+ return ONRSet{ons.onrs.Union(otherSet.onrs)}
+}
+
+// AsSlice returns the ONRs found in the set as a slice.
+func (ons ONRSet) AsSlice() []tuple.ObjectAndRelation {
+ slice := make([]tuple.ObjectAndRelation, 0, ons.Length())
+ _ = ons.onrs.ForEach(func(onr tuple.ObjectAndRelation) error {
+ slice = append(slice, onr)
+ return nil
+ })
+ return slice
+}
diff --git a/vendor/github.com/authzed/spicedb/internal/developmentmembership/trackingsubjectset.go b/vendor/github.com/authzed/spicedb/internal/developmentmembership/trackingsubjectset.go
new file mode 100644
index 0000000..00f8836
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/internal/developmentmembership/trackingsubjectset.go
@@ -0,0 +1,235 @@
+package developmentmembership
+
+import (
+ "github.com/authzed/spicedb/internal/datasets"
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+ "github.com/authzed/spicedb/pkg/tuple"
+)
+
+// TrackingSubjectSet defines a set that tracks accessible subjects and their associated
+// relationships.
+//
+// NOTE: This is designed solely for the developer API and testing and should *not* be used in any
+// performance sensitive code.
+type TrackingSubjectSet struct {
+ setByType map[tuple.RelationReference]datasets.BaseSubjectSet[FoundSubject]
+}
+
+// NewTrackingSubjectSet creates a new TrackingSubjectSet
+func NewTrackingSubjectSet() *TrackingSubjectSet {
+ tss := &TrackingSubjectSet{
+ setByType: map[tuple.RelationReference]datasets.BaseSubjectSet[FoundSubject]{},
+ }
+ return tss
+}
+
+// MustNewTrackingSubjectSetWith creates a new TrackingSubjectSet, and adds the specified
+// subjects to it.
+func MustNewTrackingSubjectSetWith(subjects ...FoundSubject) *TrackingSubjectSet {
+ tss := NewTrackingSubjectSet()
+ for _, subject := range subjects {
+ err := tss.Add(subject)
+ if err != nil {
+ panic(err)
+ }
+ }
+ return tss
+}
+
+// AddFrom adds the subjects found in the other set to this set.
+func (tss *TrackingSubjectSet) AddFrom(otherSet *TrackingSubjectSet) error {
+ for key, oss := range otherSet.setByType {
+ err := tss.getSetForKey(key).UnionWithSet(oss)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// MustAddFrom adds the subjects found in the other set to this set.
+func (tss *TrackingSubjectSet) MustAddFrom(otherSet *TrackingSubjectSet) {
+ err := tss.AddFrom(otherSet)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// RemoveFrom removes any subjects found in the other set from this set.
+func (tss *TrackingSubjectSet) RemoveFrom(otherSet *TrackingSubjectSet) {
+ for key, oss := range otherSet.setByType {
+ tss.getSetForKey(key).SubtractAll(oss)
+ }
+}
+
+// MustAdd adds the given subjects to this set.
+func (tss *TrackingSubjectSet) MustAdd(subjectsAndResources ...FoundSubject) {
+ err := tss.Add(subjectsAndResources...)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Add adds the given subjects to this set.
+func (tss *TrackingSubjectSet) Add(subjectsAndResources ...FoundSubject) error {
+ for _, fs := range subjectsAndResources {
+ err := tss.getSet(fs).Add(fs)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (tss *TrackingSubjectSet) getSetForKey(key tuple.RelationReference) datasets.BaseSubjectSet[FoundSubject] {
+ if existing, ok := tss.setByType[key]; ok {
+ return existing
+ }
+
+ created := datasets.NewBaseSubjectSet(
+ func(subjectID string, caveatExpression *core.CaveatExpression, excludedSubjects []FoundSubject, sources ...FoundSubject) FoundSubject {
+ fs := NewFoundSubject(&core.DirectSubject{
+ Subject: &core.ObjectAndRelation{
+ Namespace: key.ObjectType,
+ ObjectId: subjectID,
+ Relation: key.Relation,
+ },
+ CaveatExpression: caveatExpression,
+ })
+ fs.excludedSubjects = excludedSubjects
+ fs.caveatExpression = caveatExpression
+ for _, source := range sources {
+ fs.resources.UpdateFrom(source.resources)
+ }
+ return fs
+ },
+ )
+ tss.setByType[key] = created
+ return created
+}
+
+func (tss *TrackingSubjectSet) getSet(fs FoundSubject) datasets.BaseSubjectSet[FoundSubject] {
+ return tss.getSetForKey(fs.subject.RelationReference())
+}
+
+// Get returns the found subject in the set, if any.
+func (tss *TrackingSubjectSet) Get(subject tuple.ObjectAndRelation) (FoundSubject, bool) {
+ set, ok := tss.setByType[subject.RelationReference()]
+ if !ok {
+ return FoundSubject{}, false
+ }
+
+ return set.Get(subject.ObjectID)
+}
+
+// Contains returns true if the set contains the given subject.
+func (tss *TrackingSubjectSet) Contains(subject tuple.ObjectAndRelation) bool {
+ _, ok := tss.Get(subject)
+ return ok
+}
+
+// Exclude returns a new set that contains the items in this set minus those in the other set.
+func (tss *TrackingSubjectSet) Exclude(otherSet *TrackingSubjectSet) *TrackingSubjectSet {
+ newSet := NewTrackingSubjectSet()
+
+ for key, bss := range tss.setByType {
+ cloned := bss.Clone()
+ if oss, ok := otherSet.setByType[key]; ok {
+ cloned.SubtractAll(oss)
+ }
+
+ newSet.setByType[key] = cloned
+ }
+
+ return newSet
+}
+
+// MustIntersect returns a new set that contains the items in this set *and* the other set. Note that
+// if wildcard is found in *both* sets, it will be returned *along* with any concrete subjects found
+// on the other side of the intersection.
+func (tss *TrackingSubjectSet) MustIntersect(otherSet *TrackingSubjectSet) *TrackingSubjectSet {
+ updated, err := tss.Intersect(otherSet)
+ if err != nil {
+ panic(err)
+ }
+ return updated
+}
+
+// Intersect returns a new set that contains the items in this set *and* the other set. Note that
+// if wildcard is found in *both* sets, it will be returned *along* with any concrete subjects found
+// on the other side of the intersection.
+func (tss *TrackingSubjectSet) Intersect(otherSet *TrackingSubjectSet) (*TrackingSubjectSet, error) {
+ newSet := NewTrackingSubjectSet()
+
+ for key, bss := range tss.setByType {
+ if oss, ok := otherSet.setByType[key]; ok {
+ cloned := bss.Clone()
+ err := cloned.IntersectionDifference(oss)
+ if err != nil {
+ return nil, err
+ }
+
+ newSet.setByType[key] = cloned
+ }
+ }
+
+ return newSet, nil
+}
+
+// ApplyParentCaveatExpression applies the given parent caveat expression (if any) to each subject set.
+func (tss *TrackingSubjectSet) ApplyParentCaveatExpression(parentCaveatExpr *core.CaveatExpression) {
+ if parentCaveatExpr == nil {
+ return
+ }
+
+ for key, bss := range tss.setByType {
+ tss.setByType[key] = bss.WithParentCaveatExpression(parentCaveatExpr)
+ }
+}
+
+// removeExact removes the given subject(s) from the set. If the subject is a wildcard, only
+// the exact matching wildcard will be removed.
+func (tss *TrackingSubjectSet) removeExact(subjects ...tuple.ObjectAndRelation) {
+ for _, subject := range subjects {
+ if set, ok := tss.setByType[subject.RelationReference()]; ok {
+ set.UnsafeRemoveExact(FoundSubject{
+ subject: subject,
+ })
+ }
+ }
+}
+
+func (tss *TrackingSubjectSet) getSubjects() []string {
+ var subjects []string
+ for _, subjectSet := range tss.setByType {
+ for _, foundSubject := range subjectSet.AsSlice() {
+ subjects = append(subjects, tuple.StringONR(foundSubject.subject))
+ }
+ }
+ return subjects
+}
+
+// ToSlice returns a slice of all subjects found in the set.
+func (tss *TrackingSubjectSet) ToSlice() []FoundSubject {
+ subjects := []FoundSubject{}
+ for _, bss := range tss.setByType {
+ subjects = append(subjects, bss.AsSlice()...)
+ }
+
+ return subjects
+}
+
+// ToFoundSubjects returns the set as a FoundSubjects struct.
+func (tss *TrackingSubjectSet) ToFoundSubjects() FoundSubjects {
+ return FoundSubjects{tss}
+}
+
+// IsEmpty returns true if the tracking subject set is empty.
+func (tss *TrackingSubjectSet) IsEmpty() bool {
+ for _, bss := range tss.setByType {
+ if !bss.IsEmpty() {
+ return false
+ }
+ }
+ return true
+}