Skip to content

Commit c184fe1

Browse files
committed
chore: implement generalized symmetric difference for set comparison
Going to be used in Organization Sync + maybe group sync. Felt better to reuse, rather than copy
1 parent d52bc91 commit c184fe1

File tree

3 files changed

+86
-23
lines changed

3 files changed

+86
-23
lines changed

coderd/rbac/roles.go

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -764,29 +764,9 @@ func SiteRoles() []Role {
764764
// RBAC checks can be applied using "ActionCreate" and "ActionDelete" for
765765
// "added" and "removed" roles respectively.
766766
func ChangeRoleSet(from []RoleIdentifier, to []RoleIdentifier) (added []RoleIdentifier, removed []RoleIdentifier) {
767-
has := make(map[RoleIdentifier]struct{})
768-
for _, exists := range from {
769-
has[exists] = struct{}{}
770-
}
771-
772-
for _, roleName := range to {
773-
// If the user already has the role assigned, we don't need to check the permission
774-
// to reassign it. Only run permission checks on the difference in the set of
775-
// roles.
776-
if _, ok := has[roleName]; ok {
777-
delete(has, roleName)
778-
continue
779-
}
780-
781-
added = append(added, roleName)
782-
}
783-
784-
// Remaining roles are the ones removed/deleted.
785-
for roleName := range has {
786-
removed = append(removed, roleName)
787-
}
788-
789-
return added, removed
767+
return slice.SymmetricDifferenceFunc(from, to, func(a, b RoleIdentifier) bool {
768+
return a.Name == b.Name && a.OrganizationID == b.OrganizationID
769+
})
790770
}
791771

792772
// Permissions is just a helper function to make building roles that list out resources

coderd/util/slice/slice.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,40 @@ func Ascending[T constraints.Ordered](a, b T) int {
107107
func Descending[T constraints.Ordered](a, b T) int {
108108
return -Ascending[T](a, b)
109109
}
110+
111+
// SymmetricDifference returns the elements that need to be added and removed
112+
// to get from set 'a' to set 'b'.
113+
// In classical set theory notation, SymmetricDifference returns
114+
// all elements of {add} and {remove} together. It is more useful to
115+
// return them as their own slices.
116+
// Example:
117+
//
118+
// a := []int{1, 3, 4}
119+
// b := []int{1, 2}
120+
// add, remove := SymmetricDifference(a, b)
121+
// fmt.Println(add) // [2]
122+
// fmt.Println(remove) // [3, 4]
123+
func SymmetricDifference[T comparable](a, b []T) (add []T, remove []T) {
124+
return Difference(b, a), Difference(a, b)
125+
}
126+
127+
// Difference returns the elements in 'a' that are not in 'b'.
128+
func Difference[T comparable](a []T, b []T) []T {
129+
return DifferenceFunc(a, b, func(a, b T) bool {
130+
return a == b
131+
})
132+
}
133+
134+
func SymmetricDifferenceFunc[T any](a, b []T, equal func(a, b T) bool) (add []T, remove []T) {
135+
return DifferenceFunc(a, b, equal), DifferenceFunc(b, a, equal)
136+
}
137+
138+
func DifferenceFunc[T any](a []T, b []T, equal func(a, b T) bool) []T {
139+
tmp := make([]T, 0, len(a))
140+
for _, v := range a {
141+
if !ContainsCompare(b, v, equal) {
142+
tmp = append(tmp, v)
143+
}
144+
}
145+
return tmp
146+
}

coderd/util/slice/slice_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,49 @@ func TestOmit(t *testing.T) {
131131
slice.Omit([]string{"a", "b", "c", "d", "e", "f"}, "c", "d", "e"),
132132
)
133133
}
134+
135+
func TestSymmetricDifference(t *testing.T) {
136+
t.Parallel()
137+
138+
t.Run("Simple", func(t *testing.T) {
139+
add, remove := slice.SymmetricDifference([]int{1, 3, 4}, []int{1, 2})
140+
require.ElementsMatch(t, []int{2}, add)
141+
require.ElementsMatch(t, []int{3, 4}, remove)
142+
})
143+
144+
t.Run("Large", func(t *testing.T) {
145+
add, remove := slice.SymmetricDifference(
146+
[]int{1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15},
147+
[]int{1, 3, 7, 9, 11, 13, 17},
148+
)
149+
require.ElementsMatch(t, []int{7, 9, 17}, add)
150+
require.ElementsMatch(t, []int{2, 4, 5, 10, 12, 14, 15}, remove)
151+
})
152+
153+
t.Run("AddOnly", func(t *testing.T) {
154+
add, remove := slice.SymmetricDifference(
155+
[]int{1, 2},
156+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
157+
)
158+
require.ElementsMatch(t, []int{3, 4, 5, 6, 7, 8, 9}, add)
159+
require.ElementsMatch(t, []int{}, remove)
160+
})
161+
162+
t.Run("RemoveOnly", func(t *testing.T) {
163+
add, remove := slice.SymmetricDifference(
164+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
165+
[]int{1, 2},
166+
)
167+
require.ElementsMatch(t, []int{}, add)
168+
require.ElementsMatch(t, []int{3, 4, 5, 6, 7, 8, 9}, remove)
169+
})
170+
171+
t.Run("Equal", func(t *testing.T) {
172+
add, remove := slice.SymmetricDifference(
173+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
174+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
175+
)
176+
require.ElementsMatch(t, []int{}, add)
177+
require.ElementsMatch(t, []int{}, remove)
178+
})
179+
}

0 commit comments

Comments
 (0)