1pub(crate) mod state;
4
5use std::collections::{HashMap, HashSet};
6use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use petgraph::prelude::DiGraphMap;
10use petgraph::visit::{Bfs, DfsPostOrder, IntoNodeIdentifiers, NodeIndexable, Reversed};
11#[cfg(any(test, feature = "serde"))]
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15use crate::access::Access;
16use crate::group::{GroupAction, GroupMember, GroupMembersState, GroupMembershipError};
17use crate::traits::{Conditions, IdentityHandle, Operation, OperationId, Resolver};
18
19const MAX_NESTED_DEPTH: u32 = 1000;
25
26#[derive(Debug, Error)]
28pub enum GroupCrdtInnerError<OP> {
29 #[error("states {0:?} not found")]
30 StatesNotFound(Vec<OP>),
31}
32
33#[derive(Debug, Error)]
35pub enum GroupCrdtError<ID, OP, M, C, RS>
36where
37 ID: IdentityHandle,
38 OP: OperationId + Ord,
39 RS: Resolver<ID, OP, M, C>,
40{
41 #[error(transparent)]
42 Inner(#[from] GroupCrdtInnerError<OP>),
43
44 #[error("duplicate operation {0} processed in group {1}")]
45 DuplicateOperation(OP, ID),
46
47 #[error("group cycle detected adding {0} to {1} operation={2}")]
48 GroupCycle(ID, ID, OP),
49
50 #[error("state change error processing operation {0}: {1:?}")]
51 StateChangeError(OP, GroupMembershipError<GroupMember<ID>>),
52
53 #[error("attempted to add group {0} with manage access")]
54 ManagerGroupsNotAllowed(ID),
55
56 #[error("resolver error: {0}")]
57 Resolver(RS::Error),
58}
59
60pub(crate) type GroupStates<ID, C> = HashMap<ID, GroupMembersState<GroupMember<ID>, C>>;
61
62#[derive(Debug)]
65#[cfg_attr(
66 any(test, feature = "test_utils", feature = "processor"),
67 derive(Clone)
68)]
69#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
70pub struct GroupCrdtInnerState<ID, OP, M, C>
71where
72 ID: IdentityHandle,
73 OP: OperationId + Ord,
74{
75 pub operations: HashMap<OP, M>,
77
78 pub ignore: HashSet<OP>,
80
81 pub mutual_removes: HashSet<OP>,
83
84 pub states: HashMap<OP, GroupStates<ID, C>>,
86
87 pub graph: DiGraphMap<OP, ()>,
89}
90
91impl<ID, OP, M, C> Default for GroupCrdtInnerState<ID, OP, M, C>
92where
93 ID: IdentityHandle,
94 OP: OperationId + Ord,
95{
96 fn default() -> Self {
97 Self {
98 operations: Default::default(),
99 ignore: Default::default(),
100 mutual_removes: Default::default(),
101 states: Default::default(),
102 graph: Default::default(),
103 }
104 }
105}
106
107impl<ID, OP, M, C> GroupCrdtInnerState<ID, OP, M, C>
108where
109 ID: IdentityHandle,
110 OP: OperationId + Ord,
111 M: Operation<ID, OP, C>,
112 C: Conditions,
113{
114 pub fn heads(&self) -> HashSet<OP> {
116 self.graph
117 .clone()
125 .into_graph::<usize>()
126 .externals(petgraph::Direction::Outgoing)
127 .map(|idx| self.graph.from_index(idx.index()))
128 .collect::<HashSet<_>>()
129 }
130
131 pub fn heads_filtered(&self, groups: &[ID]) -> HashSet<OP> {
134 let global_heads = self.heads();
135 global_heads
136 .into_iter()
137 .filter(|id| {
138 let reversed = Reversed(&self.graph);
139 let mut bfs = Bfs::new(&reversed, *id);
140 while let Some(inner_id) = bfs.next(&reversed) {
141 let operation = self
142 .operations
143 .get(&inner_id)
144 .expect("operation is present in map");
145 if operation.action().is_create() && groups.contains(&operation.group_id()) {
146 return true;
147 }
148 }
149 false
150 })
151 .collect()
152 }
153
154 pub fn current_state(&self) -> GroupStates<ID, C> {
159 self.merge_states(&self.heads())
160 .expect("states exist for processed operations")
161 }
162
163 pub fn state_at(
165 &self,
166 dependencies: &HashSet<OP>,
167 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
168 self.merge_states(dependencies)
169 }
170
171 fn merge_states(
173 &self,
174 ids: &HashSet<OP>,
175 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
176 let mut current_state = HashMap::new();
177 for id in ids {
178 let group_states = match self.states.get(id) {
180 Some(group_states) => group_states.clone(),
181 None => {
182 return Err(GroupCrdtInnerError::StatesNotFound(
183 ids.iter().cloned().collect(),
184 ));
185 }
186 };
187 for (id, state) in group_states.into_iter() {
188 current_state
189 .entry(id)
190 .and_modify(
191 |current_state: &mut GroupMembersState<GroupMember<ID>, C>| {
192 *current_state = state::merge(state.clone(), current_state.clone())
193 },
194 )
195 .or_insert(state);
196 }
197 }
198 Ok(current_state)
199 }
200
201 fn members_inner(
202 &self,
203 group_id: ID,
204 members: &mut HashMap<ID, Access<C>>,
205 root_access: Option<Access<C>>,
206 mut depth: u32,
207 ) {
208 if depth == MAX_NESTED_DEPTH {
210 return;
211 }
212 depth += 1;
213
214 let current_states = self.current_state();
215 let Some(group_state) = current_states.get(&group_id) else {
216 return;
217 };
218
219 for (member, access) in group_state.access_levels() {
220 let next_access = match root_access.clone() {
224 Some(root_access) => {
225 if access <= root_access {
226 access.clone()
227 } else {
228 root_access
229 }
230 }
231 None => access.clone(),
232 };
233
234 match member {
235 GroupMember::Individual(id) => {
236 members
238 .entry(id)
239 .and_modify(|current_access| {
240 if *current_access < next_access {
251 *current_access = next_access.clone();
252 }
253 })
254 .or_insert_with(|| next_access);
255 }
256 GroupMember::Group(id) => self.members_inner(id, members, Some(next_access), depth),
257 }
258 }
259 }
260
261 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
263 let mut members = HashMap::new();
264 self.members_inner(group_id, &mut members, None, 0);
265 members.into_iter().collect()
266 }
267
268 pub(crate) fn would_create_cycle(&self, operation: &M) -> bool {
269 let parent_group_id = operation.group_id();
270
271 if let GroupAction::Add {
272 member: GroupMember::Group(child_group_id),
273 ..
274 } = &operation.action()
275 {
276 let states = self.current_state();
277 let mut stack = vec![*child_group_id];
278 let mut visited = HashSet::new();
279
280 while let Some(child_group_id) = stack.pop() {
281 if !visited.insert(child_group_id) {
282 continue;
283 }
284 if child_group_id == parent_group_id {
285 return true;
287 }
288 if let Some(group_state) = states.get(&child_group_id) {
289 for (member, _) in group_state.access_levels() {
290 if let GroupMember::Group(id) = member {
291 stack.push(id);
292 }
293 }
294 }
295 }
296 }
297
298 false
299 }
300}
301
302#[derive(Debug)]
305#[cfg_attr(
306 any(test, feature = "test_utils", feature = "processor"),
307 derive(Clone)
308)]
309#[cfg_attr(
310 any(test, feature = "serde"),
311 derive(Deserialize, Serialize),
312 serde(bound(
313 deserialize = "
314 ID: Deserialize<'de>,
315 OP: Deserialize<'de>,
316 M: Deserialize<'de>,
317 C: Deserialize<'de>,
318 ",
319 serialize = "
320 ID: Serialize,
321 OP: Serialize,
322 M: Serialize,
323 C: Serialize,
324 "
325 ))
326)]
327pub struct GroupCrdtState<ID, OP, M, C>
328where
329 ID: IdentityHandle,
330 OP: OperationId + Ord,
331{
332 pub inner: GroupCrdtInnerState<ID, OP, M, C>,
334}
335
336impl<ID, OP, M, C> Default for GroupCrdtState<ID, OP, M, C>
337where
338 ID: IdentityHandle,
339 OP: OperationId + Ord,
340 M: Operation<ID, OP, C>,
341 C: Conditions,
342{
343 fn default() -> Self {
344 Self {
345 inner: Default::default(),
346 }
347 }
348}
349
350impl<ID, OP, M, C> GroupCrdtState<ID, OP, M, C>
351where
352 ID: IdentityHandle,
353 OP: OperationId + Ord,
354 M: Operation<ID, OP, C>,
355 C: Conditions,
356{
357 pub fn new() -> Self {
359 Self::default()
360 }
361
362 pub fn root_members(&self, group_id: ID) -> Vec<(GroupMember<ID>, Access<C>)> {
367 match self.inner.current_state().get(&group_id) {
368 Some(group_y) => group_y.access_levels(),
369 None => vec![],
370 }
371 }
372
373 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
378 self.inner.members(group_id)
379 }
380
381 pub fn has_group(&self, group_id: ID) -> bool {
383 self.inner.current_state().contains_key(&group_id)
384 }
385
386 pub fn heads(&self) -> Vec<OP> {
388 self.inner.heads().into_iter().collect()
389 }
390
391 pub fn heads_filtered(&self, groups: &[ID]) -> Vec<OP> {
394 self.inner.heads_filtered(groups).into_iter().collect()
395 }
396}
397
398#[derive(Clone, Debug, Default)]
438pub struct GroupCrdt<ID, OP, M, C, RS> {
439 _phantom: PhantomData<(ID, OP, M, C, RS)>,
440}
441
442impl<ID, OP, M, C, RS> GroupCrdt<ID, OP, M, C, RS>
443where
444 ID: IdentityHandle,
445 OP: OperationId + Ord,
446 M: Operation<ID, OP, C> + Clone,
447 C: Conditions,
448 RS: Resolver<ID, OP, M, C, State = GroupCrdtInnerState<ID, OP, M, C>>,
449{
450 pub fn init() -> GroupCrdtState<ID, OP, M, C> {
451 GroupCrdtState {
452 inner: GroupCrdtInnerState::default(),
453 }
454 }
455
456 #[allow(clippy::type_complexity)]
458 pub fn process(
459 mut y: GroupCrdtState<ID, OP, M, C>,
460 operation: &M,
461 ) -> Result<GroupCrdtState<ID, OP, M, C>, GroupCrdtError<ID, OP, M, C, RS>> {
462 let operation_id = operation.id();
463 let actor = operation.author();
464 let dependencies = HashSet::from_iter(operation.dependencies().clone());
465 let group_id = operation.group_id();
466 let rebuild_required =
467 RS::rebuild_required(&y.inner, operation).map_err(GroupCrdtError::Resolver)?;
468
469 y = GroupCrdt::validate(y, operation)?;
476 y = Self::add_operation(y, operation);
477
478 if rebuild_required {
479 y.inner = RS::process(y.inner).map_err(GroupCrdtError::Resolver)?;
480 return Ok(y);
481 }
482
483 let mut groups_y = y.inner.state_at(&dependencies)?;
486 groups_y = apply_action(
487 groups_y,
488 group_id,
489 operation_id,
490 actor,
491 &operation.action(),
492 &y.inner.ignore,
493 )
494 .state()
495 .to_owned();
496
497 y.inner.states.insert(operation_id, groups_y);
498
499 Ok(y)
500 }
501
502 #[allow(clippy::type_complexity)]
514 pub(crate) fn validate(
515 y: GroupCrdtState<ID, OP, M, C>,
516 operation: &M,
517 ) -> Result<GroupCrdtState<ID, OP, M, C>, GroupCrdtError<ID, OP, M, C, RS>> {
518 if y.inner.operations.contains_key(&operation.id()) {
520 return Err(GroupCrdtError::DuplicateOperation(
522 operation.id(),
523 operation.group_id(),
524 ));
525 }
526
527 match &operation.action() {
534 GroupAction::Add { member, access } | GroupAction::Promote { member, access }
535 if member.is_group() && access.is_manage() =>
536 {
537 return Err(GroupCrdtError::ManagerGroupsNotAllowed(member.id()));
538 }
539 _ => (),
540 };
541
542 let last_graph = y.inner.graph.clone();
543 let last_ignore = y.inner.ignore.clone();
544 let last_mutual_removes = y.inner.mutual_removes.clone();
545 let last_states = y.inner.states.clone();
546
547 let dependencies = HashSet::from_iter(operation.dependencies().clone());
548
549 let temp_y = if y.inner.heads() != dependencies {
552 let mut temp_y = y;
553
554 let mut predecessors = HashSet::new();
556 for dependency in operation.dependencies() {
557 let reversed = Reversed(&temp_y.inner.graph);
558 let mut dfs_rev = DfsPostOrder::new(&reversed, dependency);
559 while let Some(id) = dfs_rev.next(&reversed) {
560 predecessors.insert(id);
561 }
562 }
563
564 let to_remove: Vec<_> = temp_y
566 .inner
567 .graph
568 .node_identifiers()
569 .filter(|n| !predecessors.contains(n))
570 .collect();
571
572 for node in &to_remove {
573 temp_y.inner.graph.remove_node(*node);
574 }
575
576 temp_y.inner = RS::process(temp_y.inner).map_err(GroupCrdtError::Resolver)?;
577 temp_y
578 } else {
579 y
580 };
581
582 if temp_y.inner.would_create_cycle(operation) {
584 let parent_group = operation.group_id();
585
586 let GroupAction::Add {
588 member: sub_group, ..
589 } = operation.action()
590 else {
591 unreachable!()
592 };
593
594 return Err(GroupCrdtError::GroupCycle(
595 parent_group,
596 sub_group.id(),
597 operation.id(),
598 ));
599 }
600
601 let result = apply_action(
603 temp_y.inner.current_state(),
604 operation.group_id(),
605 operation.id(),
606 operation.author(),
607 &operation.action(),
608 &temp_y.inner.ignore,
609 );
610
611 match result {
612 StateChangeResult::Ok { state } => state,
613 StateChangeResult::Error { error, .. } => {
614 return Err(GroupCrdtError::StateChangeError(operation.id(), error));
617 }
618 StateChangeResult::Filtered { .. } => {
619 unreachable!();
621 }
622 };
623
624 let mut y = temp_y;
625 y.inner.graph = last_graph;
626 y.inner.ignore = last_ignore;
627 y.inner.mutual_removes = last_mutual_removes;
628 y.inner.states = last_states;
629
630 Ok(y)
631 }
632
633 fn add_operation(
637 mut y: GroupCrdtState<ID, OP, M, C>,
638 operation: &M,
639 ) -> GroupCrdtState<ID, OP, M, C> {
640 let operation_id = operation.id();
641 let dependencies = operation.dependencies();
642
643 y.inner.graph.add_node(operation_id);
645 for dependency in &dependencies {
646 y.inner.graph.add_edge(*dependency, operation_id, ());
647 }
648
649 y.inner.operations.insert(operation_id, operation.clone());
651
652 y
653 }
654}
655
656pub(crate) fn apply_action<ID, OP, C>(
658 mut groups_y: GroupStates<ID, C>,
659 group_id: ID,
660 id: OP,
661 actor: ID,
662 action: &GroupAction<ID, C>,
663 filter: &HashSet<OP>,
664) -> StateChangeResult<ID, C>
665where
666 ID: IdentityHandle,
667 OP: OperationId + Ord,
668 C: Conditions,
669{
670 let members_y = if action.is_create() {
671 GroupMembersState::default()
672 } else {
673 groups_y
674 .remove(&group_id)
675 .expect("group already present in states map")
676 };
677
678 if filter.contains(&id) {
679 groups_y.insert(group_id, members_y);
680 return StateChangeResult::Filtered { state: groups_y };
681 }
682
683 let result = match action.clone() {
684 GroupAction::Add { member, access, .. } => state::add(
685 members_y.clone(),
686 GroupMember::Individual(actor),
687 member,
688 access,
689 ),
690 GroupAction::Remove { member, .. } => {
691 state::remove(members_y.clone(), GroupMember::Individual(actor), member)
692 }
693 GroupAction::Promote { member, access } => state::promote(
694 members_y.clone(),
695 GroupMember::Individual(actor),
696 member,
697 access,
698 ),
699 GroupAction::Demote { member, access } => state::demote(
700 members_y.clone(),
701 GroupMember::Individual(actor),
702 member,
703 access,
704 ),
705 GroupAction::Create { initial_members } => Ok(state::create(&initial_members)),
706 };
707
708 match result {
709 Ok(members_y_i) => {
710 groups_y.insert(group_id, members_y_i);
711 StateChangeResult::Ok { state: groups_y }
712 }
713 Err(err) => {
714 groups_y.insert(group_id, members_y);
728 StateChangeResult::Error {
729 state: groups_y,
730 error: err,
731 }
732 }
733 }
734}
735
736pub(crate) fn apply_remove_unsafe<ID, C>(
739 mut groups_y: GroupStates<ID, C>,
740 group_id: ID,
741 removed: GroupMember<ID>,
742) -> GroupStates<ID, C>
743where
744 ID: IdentityHandle,
745 C: Conditions,
746{
747 let mut members_y = groups_y
748 .remove(&group_id)
749 .expect("group already present in states map");
750
751 members_y.members.entry(removed).and_modify(|state| {
752 if state.member_counter % 2 != 0 {
753 state.member_counter += 1
754 }
755 });
756 groups_y.insert(group_id, members_y);
757 groups_y
758}
759
760pub enum StateChangeResult<ID, C>
762where
763 ID: IdentityHandle,
764 C: Conditions,
765{
766 Ok { state: GroupStates<ID, C> },
768
769 Error {
771 state: GroupStates<ID, C>,
772 #[allow(unused)]
773 error: GroupMembershipError<GroupMember<ID>>,
774 },
775
776 Filtered { state: GroupStates<ID, C> },
778}
779
780impl<ID, C> StateChangeResult<ID, C>
781where
782 ID: IdentityHandle,
783 C: Conditions,
784{
785 pub fn state(&self) -> &GroupStates<ID, C> {
786 match self {
787 StateChangeResult::Ok { state }
788 | StateChangeResult::Error { state, .. }
789 | StateChangeResult::Filtered { state } => state,
790 }
791 }
792}
793
794#[cfg(test)]
795pub(crate) mod tests {
796 use crate::Access;
797 use crate::group::{GroupCrdtError, GroupMember, GroupMembershipError};
798 use crate::test_utils::{
799 TestGroup, TestGroupState, add_member, create_group, demote_member, promote_member,
800 remove_member,
801 };
802 use crate::traits::Operation;
803
804 const G1: char = '1';
805 const G2: char = '2';
806 const G3: char = '3';
807 const G4: char = '4';
808
809 const ALICE: char = 'A';
810 const BOB: char = 'B';
811 const CLAIRE: char = 'C';
812 const DAN: char = 'D';
813 const EVE: char = 'E';
814
815 #[test]
816 fn group_operations() {
817 let y = TestGroupState::new();
818
819 let op1 = create_group(
820 ALICE,
821 0,
822 G1,
823 vec![(GroupMember::Individual(ALICE), Access::manage())],
824 vec![],
825 );
826
827 let y_i = TestGroup::process(y, &op1).unwrap();
828 let mut members = y_i.members(G1);
829 members.sort();
830 assert_eq!(members, vec![(ALICE, Access::manage())]);
831
832 let op2 = add_member(
833 ALICE,
834 1,
835 G1,
836 GroupMember::Individual(BOB),
837 Access::read(),
838 vec![op1.id()],
839 );
840
841 let y_ii = TestGroup::process(y_i, &op2).unwrap();
842 let mut members = y_ii.members(G1);
843 members.sort();
844 assert_eq!(
845 members,
846 vec![(ALICE, Access::manage()), (BOB, Access::read())]
847 );
848
849 let op3 = add_member(
850 ALICE,
851 2,
852 G1,
853 GroupMember::Individual(CLAIRE),
854 Access::write(),
855 vec![op2.id()],
856 );
857
858 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
859 let mut members = y_iii.members(G1);
860 members.sort();
861 assert_eq!(
862 members,
863 vec![
864 (ALICE, Access::manage()),
865 (BOB, Access::read()),
866 (CLAIRE, Access::write())
867 ]
868 );
869
870 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op3.id()]);
871
872 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
873 let mut members = y_iv.members(G1);
874 members.sort();
875 assert_eq!(
876 members,
877 vec![(ALICE, Access::manage()), (CLAIRE, Access::write())]
878 );
879 }
880
881 #[test]
882 fn concurrent_removal() {
883 let y = TestGroupState::new();
884
885 let op1 = create_group(
886 ALICE,
887 0,
888 G1,
889 vec![(GroupMember::Individual(ALICE), Access::manage())],
890 vec![],
891 );
892
893 let y_i = TestGroup::process(y, &op1).unwrap();
894 let mut members = y_i.members(G1);
895 members.sort();
896 assert_eq!(members, vec![(ALICE, Access::manage())]);
897
898 let op2 = add_member(
899 ALICE,
900 1,
901 G1,
902 GroupMember::Individual(BOB),
903 Access::manage(),
904 vec![op1.id()],
905 );
906
907 let y_ii = TestGroup::process(y_i, &op2).unwrap();
908 let mut members = y_ii.members(G1);
909 members.sort();
910 assert_eq!(
911 members,
912 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
913 );
914
915 let op3 = add_member(
916 BOB,
917 2,
918 G1,
919 GroupMember::Individual(CLAIRE),
920 Access::write(),
921 vec![op2.id()],
922 );
923
924 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
925 let mut members = y_iii.members(G1);
926 members.sort();
927 assert_eq!(
928 members,
929 vec![
930 (ALICE, Access::manage()),
931 (BOB, Access::manage()),
932 (CLAIRE, Access::write())
933 ]
934 );
935
936 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
937
938 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
939 let mut members = y_iv.members(G1);
940 members.sort();
941 assert_eq!(members, vec![(ALICE, Access::manage())]);
942 }
943
944 #[test]
945 fn mutual_concurrent_removal() {
946 let y = TestGroupState::new();
947
948 let op1 = create_group(
949 ALICE,
950 0,
951 G1,
952 vec![(GroupMember::Individual(ALICE), Access::manage())],
953 vec![],
954 );
955
956 let y_i = TestGroup::process(y, &op1).unwrap();
957 let mut members = y_i.members(G1);
958 members.sort();
959 assert_eq!(members, vec![(ALICE, Access::manage())]);
960
961 let op2 = add_member(
962 ALICE,
963 1,
964 G1,
965 GroupMember::Individual(BOB),
966 Access::manage(),
967 vec![op1.id()],
968 );
969
970 let y_ii = TestGroup::process(y_i, &op2).unwrap();
971 let mut members = y_ii.members(G1);
972 members.sort();
973 assert_eq!(
974 members,
975 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
976 );
977
978 let op3 = add_member(
979 BOB,
980 2,
981 G1,
982 GroupMember::Individual(CLAIRE),
983 Access::manage(),
984 vec![op2.id()],
985 );
986
987 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
988 let mut members = y_iii.members(G1);
989 members.sort();
990 assert_eq!(
991 members,
992 vec![
993 (ALICE, Access::manage()),
994 (BOB, Access::manage()),
995 (CLAIRE, Access::manage())
996 ]
997 );
998
999 let op4 = remove_member(BOB, 3, G1, GroupMember::Individual(CLAIRE), vec![op3.id()]);
1000
1001 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1002 let mut members = y_iv.members(G1);
1003 members.sort();
1004 assert_eq!(
1005 members,
1006 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1007 );
1008
1009 let op5 = remove_member(CLAIRE, 4, G1, GroupMember::Individual(BOB), vec![op3.id()]);
1010
1011 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1012 let mut members = y_v.members(G1);
1013 members.sort();
1014 assert_eq!(members, vec![(ALICE, Access::manage())]);
1015 }
1016
1017 #[test]
1018 fn nested_groups() {
1019 let y = TestGroupState::new();
1020
1021 let op1 = create_group(
1022 ALICE,
1023 0,
1024 G1,
1025 vec![(GroupMember::Individual(ALICE), Access::manage())],
1026 vec![],
1027 );
1028
1029 let y_i = TestGroup::process(y, &op1).unwrap();
1030 let mut members = y_i.members(G1);
1031 members.sort();
1032 assert_eq!(members, vec![(ALICE, Access::manage())]);
1033
1034 let op2 = create_group(
1035 BOB,
1036 1,
1037 G2,
1038 vec![(GroupMember::Individual(BOB), Access::manage())],
1039 vec![op1.id()],
1040 );
1041
1042 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1043 let mut members = y_ii.members(G2);
1044 members.sort();
1045 assert_eq!(members, vec![(BOB, Access::manage())]);
1046
1047 let op3 = add_member(
1048 ALICE,
1049 2,
1050 G1,
1051 GroupMember::Group(G2),
1052 Access::read(),
1053 vec![op2.id()],
1054 );
1055
1056 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1057 let mut members = y_iii.members(G1);
1058 members.sort();
1059 assert_eq!(
1060 members,
1061 vec![(ALICE, Access::manage()), (BOB, Access::read())]
1062 );
1063 }
1064
1065 #[test]
1066 fn error_on_unauthorized_add() {
1067 let y = TestGroupState::new();
1068
1069 let op1 = create_group(
1070 ALICE,
1071 0,
1072 G1,
1073 vec![(GroupMember::Individual(ALICE), Access::manage())],
1074 vec![],
1075 );
1076
1077 let y_i = TestGroup::process(y, &op1).unwrap();
1078
1079 let op2 = add_member(
1080 ALICE,
1081 1,
1082 G1,
1083 GroupMember::Individual(BOB),
1084 Access::read(),
1085 vec![op1.id()],
1086 );
1087
1088 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1089
1090 let op3 = add_member(
1091 BOB,
1092 2,
1093 G1,
1094 GroupMember::Individual(CLAIRE),
1095 Access::read(),
1096 vec![op2.id()],
1097 );
1098
1099 assert!(TestGroup::process(y_ii, &op3).is_err());
1100 }
1101
1102 #[test]
1103 fn error_on_remove_non_member() {
1104 let y = TestGroupState::new();
1105
1106 let op1 = create_group(
1107 ALICE,
1108 0,
1109 G1,
1110 vec![(GroupMember::Individual(ALICE), Access::manage())],
1111 vec![],
1112 );
1113
1114 let y_i = TestGroup::process(y, &op1).unwrap();
1115
1116 let op2 = remove_member(ALICE, 1, G1, GroupMember::Individual(BOB), vec![op1.id()]);
1117
1118 assert!(TestGroup::process(y_i, &op2).is_err());
1119 }
1120
1121 #[test]
1122 fn error_on_promote_non_member() {
1123 let y = TestGroupState::new();
1124
1125 let op1 = create_group(
1126 ALICE,
1127 0,
1128 G1,
1129 vec![(GroupMember::Individual(ALICE), Access::manage())],
1130 vec![],
1131 );
1132
1133 let y_i = TestGroup::process(y, &op1).unwrap();
1134
1135 let op2 = promote_member(
1136 ALICE,
1137 1,
1138 G1,
1139 GroupMember::Individual(BOB),
1140 Access::manage(),
1141 vec![op1.id()],
1142 );
1143
1144 assert!(TestGroup::process(y_i, &op2).is_err());
1145 }
1146
1147 #[test]
1148 fn error_on_add_manager_group() {
1149 let y = TestGroupState::new();
1150
1151 let op1 = create_group(
1152 ALICE,
1153 0,
1154 G1,
1155 vec![(GroupMember::Individual(ALICE), Access::manage())],
1156 vec![],
1157 );
1158
1159 let y_i = TestGroup::process(y, &op1).unwrap();
1160
1161 let op2 = add_member(
1162 ALICE,
1163 1,
1164 G1,
1165 GroupMember::Group(BOB),
1166 Access::manage(),
1167 vec![op1.id()],
1168 );
1169
1170 assert!(TestGroup::process(y_i, &op2).is_err());
1171 }
1172
1173 #[test]
1174 fn error_on_demote_non_member() {
1175 let y = TestGroupState::new();
1176
1177 let op1 = create_group(
1178 ALICE,
1179 0,
1180 G1,
1181 vec![(GroupMember::Individual(ALICE), Access::manage())],
1182 vec![],
1183 );
1184
1185 let y_i = TestGroup::process(y, &op1).unwrap();
1186
1187 let op2 = demote_member(
1188 ALICE,
1189 1,
1190 G1,
1191 GroupMember::Individual(BOB),
1192 Access::read(),
1193 vec![op1.id()],
1194 );
1195
1196 assert!(TestGroup::process(y_i, &op2).is_err());
1197 }
1198
1199 #[test]
1200 fn error_on_add_existing_member() {
1201 let y = TestGroupState::new();
1202
1203 let op1 = create_group(
1204 ALICE,
1205 0,
1206 G1,
1207 vec![(GroupMember::Individual(ALICE), Access::manage())],
1208 vec![],
1209 );
1210
1211 let y_i = TestGroup::process(y, &op1).unwrap();
1212
1213 let op2 = add_member(
1214 ALICE,
1215 1,
1216 G1,
1217 GroupMember::Individual(ALICE),
1218 Access::manage(),
1219 vec![op1.id()],
1220 );
1221
1222 assert!(TestGroup::process(y_i, &op2).is_err());
1223 }
1224
1225 #[test]
1226 fn error_on_remove_nonexistent_subgroup() {
1227 let y = TestGroupState::new();
1228
1229 let op1 = create_group(
1230 ALICE,
1231 0,
1232 G1,
1233 vec![(GroupMember::Individual(ALICE), Access::manage())],
1234 vec![],
1235 );
1236 let y_i = TestGroup::process(y, &op1).unwrap();
1237
1238 let op2 = remove_member(ALICE, 1, G1, GroupMember::Group(G2), vec![op1.id()]);
1240
1241 assert!(TestGroup::process(y_i, &op2).is_err());
1242 }
1243
1244 #[test]
1245 fn deeply_nested_groups_with_removals() {
1246 let y = TestGroupState::new();
1247
1248 let op1 = create_group(
1250 ALICE,
1251 0,
1252 G1,
1253 vec![(GroupMember::Individual(ALICE), Access::manage())],
1254 vec![],
1255 );
1256 let y_i = TestGroup::process(y, &op1).unwrap();
1257
1258 let op2 = create_group(
1260 BOB,
1261 1,
1262 G2,
1263 vec![(GroupMember::Individual(BOB), Access::manage())],
1264 vec![op1.id()],
1265 );
1266 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1267
1268 let op3 = create_group(
1270 CLAIRE,
1271 2,
1272 G3,
1273 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1274 vec![op2.id()],
1275 );
1276 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1277
1278 let op4 = create_group(
1280 DAN,
1281 3,
1282 G4,
1283 vec![(GroupMember::Individual(DAN), Access::write())],
1284 vec![op3.id()],
1285 );
1286 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1287
1288 let op5 = add_member(
1290 CLAIRE,
1291 4,
1292 G3,
1293 GroupMember::Group(G4),
1294 Access::read(),
1295 vec![op4.id()],
1296 );
1297 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1298
1299 let op6 = add_member(
1301 BOB,
1302 5,
1303 G2,
1304 GroupMember::Group(G3),
1305 Access::write(),
1306 vec![op5.id()],
1307 );
1308 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1309
1310 let op7 = add_member(
1312 ALICE,
1313 6,
1314 G1,
1315 GroupMember::Group(G2),
1316 Access::read(),
1317 vec![op6.id()],
1318 );
1319 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1320
1321 let mut members = y_vii.members(G1);
1322 members.sort();
1323 assert_eq!(
1324 members,
1325 vec![
1326 (ALICE, Access::manage()),
1327 (BOB, Access::read()),
1328 (CLAIRE, Access::read()),
1329 (DAN, Access::read()),
1330 ]
1331 );
1332
1333 let op8 = remove_member(BOB, 7, G2, GroupMember::Group(G3), vec![op7.id()]);
1335 let y_viii = TestGroup::process(y_vii, &op8).unwrap();
1336
1337 let mut members_after_removal = y_viii.members(G1);
1338 members_after_removal.sort();
1339 assert_eq!(
1340 members_after_removal,
1341 vec![(ALICE, Access::manage()), (BOB, Access::read()),]
1342 );
1343 }
1344
1345 #[test]
1346 fn nested_groups_with_concurrent_removal_and_promotion() {
1347 let y = TestGroupState::new();
1348
1349 let op1 = create_group(
1351 ALICE,
1352 0,
1353 G1,
1354 vec![(GroupMember::Individual(ALICE), Access::manage())],
1355 vec![],
1356 );
1357 let y_i = TestGroup::process(y, &op1).unwrap();
1358
1359 let op2 = create_group(
1361 BOB,
1362 1,
1363 G2,
1364 vec![(GroupMember::Individual(BOB), Access::manage())],
1365 vec![op1.id()],
1366 );
1367 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1368
1369 let op3 = create_group(
1371 CLAIRE,
1372 2,
1373 G3,
1374 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1375 vec![op2.id()],
1376 );
1377 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1378
1379 let op4 = add_member(
1381 CLAIRE,
1382 3,
1383 G3,
1384 GroupMember::Individual(DAN),
1385 Access::write(),
1386 vec![op3.id()],
1387 );
1388 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1389
1390 let op5 = add_member(
1392 BOB,
1393 4,
1394 G2,
1395 GroupMember::Group(G3),
1396 Access::write(),
1397 vec![op4.id()],
1398 );
1399 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1400
1401 let op6 = add_member(
1403 BOB,
1404 5,
1405 G2,
1406 GroupMember::Individual(CLAIRE),
1407 Access::read(),
1408 vec![op5.id()],
1409 );
1410 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1411
1412 let op7 = add_member(
1414 ALICE,
1415 6,
1416 G1,
1417 GroupMember::Group(G2),
1418 Access::read(),
1419 vec![op6.id()],
1420 );
1421 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1422
1423 let mut members = y_vii.members(G1);
1424 members.sort();
1425 assert_eq!(
1426 members,
1427 vec![
1428 (ALICE, Access::manage()),
1429 (BOB, Access::read()),
1430 (CLAIRE, Access::read()),
1431 (DAN, Access::read()),
1432 ]
1433 );
1434
1435 let op8_remove_g2 = remove_member(ALICE, 7, G1, GroupMember::Group(G2), vec![op7.id()]);
1437 let op9_promote_claire = promote_member(
1438 BOB,
1439 8,
1440 G2,
1441 GroupMember::Individual(CLAIRE),
1442 Access::manage(),
1443 vec![op7.id()],
1444 );
1445
1446 let y_after_remove = TestGroup::process(y_vii.clone(), &op8_remove_g2).unwrap();
1448 let mut members = y_after_remove.members(G1);
1449 members.sort();
1450 assert_eq!(members, vec![(ALICE, Access::manage())]);
1451
1452 let y_after_both = TestGroup::process(y_after_remove, &op9_promote_claire).unwrap();
1454 let mut g1_members = y_after_both.members(G1);
1455 g1_members.sort();
1456 assert_eq!(g1_members, vec![(ALICE, Access::manage())]);
1457
1458 let mut g2_members = y_after_both.members(G2);
1459 g2_members.sort();
1460 assert_eq!(
1461 g2_members,
1462 vec![
1463 (BOB, Access::manage()),
1464 (CLAIRE, Access::manage()),
1465 (DAN, Access::write()),
1466 ]
1467 );
1468 }
1469
1470 #[test]
1471 fn concurrent_removal_ooo_processing() {
1472 let y = TestGroupState::new();
1473
1474 let op1 = create_group(
1476 ALICE,
1477 0,
1478 G1,
1479 vec![(GroupMember::Individual(ALICE), Access::manage())],
1480 vec![],
1481 );
1482 let y_i = TestGroup::process(y, &op1).unwrap();
1483
1484 let op2 = add_member(
1486 ALICE,
1487 1,
1488 G1,
1489 GroupMember::Individual(BOB),
1490 Access::manage(),
1491 vec![op1.id()],
1492 );
1493 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1494
1495 let op3 = add_member(
1497 BOB,
1498 2,
1499 G1,
1500 GroupMember::Individual(CLAIRE),
1501 Access::read(),
1502 vec![op2.id()],
1503 );
1504
1505 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
1507
1508 let y_iii_a = TestGroup::process(y_ii.clone(), &op3).unwrap();
1510 let y_iv_a = TestGroup::process(y_iii_a, &op4).unwrap();
1511
1512 let y_iii_b = TestGroup::process(y_ii.clone(), &op4).unwrap();
1514 let y_iv_b = TestGroup::process(y_iii_b, &op3).unwrap();
1515
1516 for (_, y) in [y_iv_a, y_iv_b].into_iter().enumerate() {
1517 let mut members = y.members(G1);
1518 members.sort();
1519 assert_eq!(members, vec![(ALICE, Access::manage())],);
1520 }
1521 }
1522
1523 #[test]
1524 fn concurrent_add_with_insufficient_access() {
1525 let y0 = TestGroupState::new();
1526
1527 let op1 = create_group(
1529 ALICE,
1530 0,
1531 G1,
1532 vec![(GroupMember::Individual(ALICE), Access::manage())],
1533 vec![],
1534 );
1535 let y1 = TestGroup::process(y0, &op1).unwrap();
1536
1537 let op2 = add_member(
1539 ALICE,
1540 1,
1541 G1,
1542 GroupMember::Individual(BOB),
1543 Access::manage(),
1544 vec![op1.id()],
1545 );
1546
1547 let op3 = add_member(
1549 BOB,
1550 2,
1551 G1,
1552 GroupMember::Individual(EVE),
1553 Access::read(),
1554 vec![op1.id()],
1555 );
1556
1557 let result = TestGroup::process(y1.clone(), &op3);
1559 assert!(matches!(
1560 result,
1561 Err(GroupCrdtError::StateChangeError(
1562 _,
1563 GroupMembershipError::UnrecognisedActor(_)
1564 ))
1565 ));
1566
1567 let y1_alt = TestGroup::process(y1, &op2).unwrap();
1569 let result = TestGroup::process(y1_alt.clone(), &op3);
1570 assert!(matches!(
1571 result,
1572 Err(GroupCrdtError::StateChangeError(
1573 _,
1574 GroupMembershipError::UnrecognisedActor(_)
1575 ))
1576 ));
1577
1578 let mut members = y1_alt.members(G1);
1580 members.sort();
1581 assert_eq!(
1582 members,
1583 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1584 );
1585 }
1586
1587 #[test]
1588 fn add_group_with_concurrent_change() {
1589 let y = TestGroupState::new();
1590
1591 let op1 = create_group(
1593 ALICE,
1594 0,
1595 G1,
1596 vec![(GroupMember::Individual(ALICE), Access::manage())],
1597 vec![],
1598 );
1599 let y_i = TestGroup::process(y, &op1).unwrap();
1600
1601 let op2 = create_group(
1603 BOB,
1604 1,
1605 G2,
1606 vec![(GroupMember::Individual(BOB), Access::manage())],
1607 vec![op1.id()],
1608 );
1609 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1610
1611 let op3a = add_member(
1613 ALICE,
1614 2,
1615 G1,
1616 GroupMember::Group(G2),
1617 Access::read(),
1618 vec![op2.id()],
1619 );
1620
1621 let op3b = add_member(
1623 BOB,
1624 3,
1625 G2,
1626 GroupMember::Individual(CLAIRE),
1627 Access::write(),
1628 vec![op2.id()],
1629 );
1630
1631 let y_iii = TestGroup::process(y_ii.clone(), &op3a).unwrap();
1633 let y_iv = TestGroup::process(y_iii, &op3b).unwrap();
1634
1635 let mut members_1 = y_iv.members(G1);
1636 members_1.sort();
1637 assert_eq!(
1638 members_1,
1639 vec![
1640 (ALICE, Access::manage()),
1641 (BOB, Access::read()),
1642 (CLAIRE, Access::read())
1643 ]
1644 );
1645
1646 let y_iii_alt = TestGroup::process(y_ii.clone(), &op3b).unwrap();
1648 let y_iv_alt = TestGroup::process(y_iii_alt, &op3a).unwrap();
1649
1650 let mut members_1 = y_iv_alt.members(G1);
1651 members_1.sort();
1652 assert_eq!(
1653 members_1,
1654 vec![
1655 (ALICE, Access::manage()),
1656 (BOB, Access::read()),
1657 (CLAIRE, Access::read())
1658 ]
1659 );
1660 }
1661
1662 #[test]
1663 fn nested_group_cycle_error() {
1664 let y = TestGroupState::new();
1665
1666 let op1 = create_group(
1668 ALICE,
1669 0,
1670 G1,
1671 vec![(GroupMember::Individual(ALICE), Access::manage())],
1672 vec![],
1673 );
1674 let y_i = TestGroup::process(y, &op1).unwrap();
1675
1676 let op2 = create_group(
1678 BOB,
1679 1,
1680 G2,
1681 vec![
1682 (GroupMember::Individual(BOB), Access::manage()),
1683 (GroupMember::Group(G1), Access::read()),
1684 ],
1685 vec![op1.id()],
1686 );
1687 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1688
1689 let op3 = add_member(
1691 ALICE,
1692 2,
1693 G1,
1694 GroupMember::Group(G2),
1695 Access::read(),
1696 vec![op2.id()],
1697 );
1698
1699 let result = TestGroup::process(y_ii, &op3);
1701 assert!(
1702 result.is_err(),
1703 "Creating a group cycle should cause an error"
1704 );
1705 }
1706
1707 #[test]
1708 fn serde_to_from_bytes() {
1709 let y = TestGroupState::new();
1710 let op1 = create_group(
1711 ALICE,
1712 0,
1713 G1,
1714 vec![(GroupMember::Individual(ALICE), Access::manage())],
1715 vec![],
1716 );
1717 let y_i = TestGroup::process(y, &op1).unwrap();
1718 let members = y_i.members(G1);
1719 assert_eq!(members, vec![(ALICE, Access::manage())]);
1720
1721 let mut bytes = vec![];
1723 ciborium::ser::into_writer(&y_i, &mut bytes).unwrap();
1724
1725 let y_i_de: TestGroupState = ciborium::from_reader(&bytes[..]).unwrap();
1727
1728 let members = y_i_de.members(G1);
1730 assert_eq!(members, vec![(ALICE, Access::manage())]);
1731 }
1732}