1use std::collections::{HashMap, HashSet};
13use std::fmt::Debug;
14use std::hash::Hash;
15
16#[cfg(any(test, feature = "serde"))]
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20use crate::Access;
21use crate::traits::Conditions;
22
23#[derive(Debug, Error, PartialEq)]
26pub enum GroupMembershipError<ID> {
27 #[error("attempted to add a member who is already active in the group: {0}")]
28 AlreadyAdded(ID),
29
30 #[error("attempted to remove a member who is already inactive in the group: {0}")]
31 AlreadyRemoved(ID),
32
33 #[error("actor lacks sufficient access to update the group: {0}")]
34 InsufficientAccess(ID),
35
36 #[error("actor is not an active member of the group: {0}")]
37 InactiveActor(ID),
38
39 #[error("member is not an active member of the group: {0}")]
40 InactiveMember(ID),
41
42 #[error("actor is not known to the group: {0}")]
43 UnrecognisedActor(ID),
44
45 #[error("member is not known to the group: {0}")]
46 UnrecognisedMember(ID),
47}
48
49#[derive(Clone, Debug)]
53#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
54pub struct MemberState<C> {
55 pub(crate) member_counter: usize,
56 pub(crate) access: Access<C>,
57 pub(crate) access_counter: usize,
58}
59
60impl<C> MemberState<C>
61where
62 C: Clone + Debug + PartialEq,
63{
64 pub fn access(&self) -> Access<C> {
66 self.access.clone()
67 }
68
69 pub fn is_member(&self) -> bool {
71 !self.member_counter.is_multiple_of(2)
72 }
73
74 pub fn is_puller(&self) -> bool {
76 self.access.is_pull()
77 }
78
79 pub fn is_reader(&self) -> bool {
81 self.access.is_read()
82 }
83
84 pub fn is_writer(&self) -> bool {
86 self.access.is_write()
87 }
88
89 pub fn is_manager(&self) -> bool {
91 self.access.is_manage()
92 }
93}
94
95#[derive(Clone, Debug)]
97#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
98pub struct GroupMembersState<ID, C>
99where
100 ID: Hash + Eq,
101{
102 pub(crate) members: HashMap<ID, MemberState<C>>,
103}
104
105impl<ID, C> Default for GroupMembersState<ID, C>
106where
107 ID: Hash + Eq,
108{
109 fn default() -> Self {
110 Self {
111 members: Default::default(),
112 }
113 }
114}
115
116impl<ID, C> GroupMembersState<ID, C>
117where
118 ID: Clone + Hash + Eq,
119 C: Conditions,
120{
121 pub fn members(&self) -> HashSet<ID> {
123 self.members
124 .iter()
125 .filter_map(|(id, state)| {
126 if state.is_member() {
127 Some(id.to_owned())
128 } else {
129 None
130 }
131 })
132 .collect::<HashSet<ID>>()
133 }
134
135 pub fn access_levels(&self) -> Vec<(ID, Access<C>)> {
137 self.members
138 .iter()
139 .filter_map(|(id, state)| {
140 if state.is_member() {
141 Some((id.to_owned(), state.access()))
142 } else {
143 None
144 }
145 })
146 .collect::<Vec<(ID, Access<C>)>>()
147 }
148
149 pub fn managers(&self) -> HashSet<ID> {
151 self.members
152 .iter()
153 .filter_map(|(id, state)| {
154 if state.is_member() && state.is_manager() {
155 Some(id.to_owned())
156 } else {
157 None
158 }
159 })
160 .collect::<HashSet<_>>()
161 }
162}
163
164pub fn create<ID: Clone + Eq + Hash, C: Conditions>(
166 initial_members: &[(ID, Access<C>)],
167) -> GroupMembersState<ID, C> {
168 let mut members = HashMap::new();
169 for (id, access) in initial_members {
170 let member = MemberState {
171 member_counter: 1,
172 access: access.clone(),
173 access_counter: 0,
174 };
175 members.insert(id.clone(), member);
176 }
177
178 GroupMembersState { members }
179}
180
181pub fn add<ID: Clone + Eq + Hash, C: Conditions>(
189 state: GroupMembersState<ID, C>,
190 adder: ID,
191 added: ID,
192 access: Access<C>,
193) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
194 let Some(adder_state) = state.members.get(&adder) else {
196 return Err(GroupMembershipError::UnrecognisedActor(adder));
197 };
198
199 if !adder_state.is_member() {
201 return Err(GroupMembershipError::InactiveActor(adder));
202 } else if !adder_state.is_manager() {
203 return Err(GroupMembershipError::InsufficientAccess(adder));
204 }
205
206 if let Some(added_state) = state.members.get(&added)
208 && added_state.is_member()
209 {
210 return Err(GroupMembershipError::AlreadyAdded(added));
211 }
212
213 let mut state = state;
216 state
217 .members
218 .entry(added.clone())
219 .and_modify(|added| {
220 if !added.is_member() {
221 added.member_counter += 1;
222 added.access = access.clone();
223 added.access_counter = 0;
224 }
225 })
226 .or_insert(MemberState {
227 member_counter: 1,
228 access,
229 access_counter: 0,
230 });
231
232 Ok(state)
233}
234
235pub fn remove<ID: Eq + Hash, C: Conditions>(
241 state: GroupMembersState<ID, C>,
242 remover: ID,
243 removed: ID,
244) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
245 let Some(remover_state) = state.members.get(&remover) else {
247 return Err(GroupMembershipError::UnrecognisedActor(remover));
248 };
249
250 if !remover_state.is_member() {
253 return Err(GroupMembershipError::InactiveActor(remover));
254 } else if !remover_state.is_manager() && remover != removed {
255 return Err(GroupMembershipError::InsufficientAccess(remover));
256 }
257
258 if !state.members.contains_key(&removed) {
260 return Err(GroupMembershipError::UnrecognisedMember(removed));
261 };
262
263 if let Some(removed_state) = state.members.get(&removed)
265 && !removed_state.is_member()
266 {
267 return Err(GroupMembershipError::AlreadyRemoved(removed));
268 }
269
270 let mut state = state;
272 state.members.entry(removed).and_modify(|removed| {
273 if removed.is_member() {
274 removed.member_counter += 1;
275 removed.access_counter = 0;
276 }
277 });
278
279 Ok(state)
280}
281
282fn modify<ID: Eq + Hash, C: Conditions>(
289 state: GroupMembersState<ID, C>,
290 modifier: ID,
291 modified: ID,
292 access: Access<C>,
293) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
294 let Some(modifier_state) = state.members.get(&modifier) else {
296 return Err(GroupMembershipError::UnrecognisedActor(modifier));
297 };
298
299 if !modifier_state.is_member() {
301 return Err(GroupMembershipError::InactiveActor(modifier));
302 } else if !modifier_state.is_manager() {
303 return Err(GroupMembershipError::InsufficientAccess(modifier));
304 }
305
306 if let Some(modified_state) = state.members.get(&modified) {
308 if !modified_state.is_member() {
309 return Err(GroupMembershipError::InactiveMember(modified));
310 }
311 } else {
312 return Err(GroupMembershipError::UnrecognisedMember(modified));
313 }
314
315 let mut state = state;
317 state.members.entry(modified).and_modify(|modified| {
318 if modified.access != access {
320 modified.access = access;
321 modified.access_counter += 1;
322 }
323 });
324
325 Ok(state)
326}
327
328pub fn promote<ID: Eq + Hash, C: Conditions>(
337 state: GroupMembersState<ID, C>,
338 promoter: ID,
339 promoted: ID,
340 access: Access<C>,
341) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
342 if let Some(member) = state.members.get(&promoted) {
343 let new_state = if member.is_manager() {
345 state
346 } else {
347 modify(state, promoter, promoted, access)?
348 };
349
350 Ok(new_state)
351 } else {
352 Err(GroupMembershipError::UnrecognisedMember(promoted))
353 }
354}
355
356pub fn demote<ID: Eq + Hash, C: Conditions>(
365 state: GroupMembersState<ID, C>,
366 demoter: ID,
367 demoted: ID,
368 access: Access<C>,
369) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
370 if let Some(member) = state.members.get(&demoted) {
371 let new_state = if member.is_puller() {
373 state
374 } else {
375 modify(state, demoter, demoted, access)?
376 };
377
378 Ok(new_state)
379 } else {
380 Err(GroupMembershipError::UnrecognisedMember(demoted))
381 }
382}
383
384pub fn merge<ID: Clone + Eq + Hash, C: Conditions>(
394 state_1: GroupMembersState<ID, C>,
395 state_2: GroupMembersState<ID, C>,
396) -> GroupMembersState<ID, C> {
397 let mut next_state = state_2.clone();
399
400 for (id, member_state_1) in state_1.members {
402 if let Some(member_state) = next_state.members.get_mut(&id) {
403 if member_state_1.member_counter > member_state.member_counter {
405 member_state.member_counter = member_state_1.member_counter;
406 member_state.access = member_state_1.access.clone();
407 member_state.access_counter = member_state_1.access_counter;
408 }
409
410 if member_state_1.member_counter == member_state.member_counter {
413 if member_state_1.access_counter > member_state.access_counter {
414 member_state.access = member_state_1.access.clone();
415 member_state.access_counter = member_state_1.access_counter;
416 }
417
418 if member_state_1.access_counter == member_state.access_counter
420 && member_state_1.access < member_state.access
421 {
422 member_state.access = member_state_1.access;
423 }
424 }
425 } else {
426 next_state.members.insert(id, member_state_1);
428 }
429 }
430
431 next_state
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn create_add_remove() {
440 let alice = 0;
443 let bob = 1;
444 let charlie = 2;
445
446 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
447
448 let group_y = create(&initial_members);
450
451 assert!(group_y.members().contains(&alice));
452 assert!(group_y.members().contains(&bob));
453
454 assert!(group_y.managers().contains(&alice));
455 assert!(!group_y.managers().contains(&bob));
456
457 let group_y = add(group_y, alice, charlie, Access::write()).unwrap();
459
460 assert!(group_y.members().contains(&charlie));
461
462 let group_y = remove(group_y, alice, bob).unwrap();
464
465 assert!(!group_y.members().contains(&bob));
466 }
467
468 #[test]
469 fn self_remove() {
470 let alice = 0;
471 let bob = 1;
472
473 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
474
475 let group_y = create(&initial_members);
477
478 assert!(group_y.members().contains(&alice));
479 assert!(group_y.members().contains(&bob));
480
481 assert!(group_y.managers().contains(&alice));
482 assert!(!group_y.managers().contains(&bob));
483
484 let group_y = remove(group_y, alice, alice).unwrap();
486
487 assert!(!group_y.members().contains(&alice));
488 assert!(group_y.members().contains(&bob));
489 }
490
491 #[test]
492 fn promote_demote_modify() {
493 let alice = 0;
494 let bob = 1;
495
496 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
497
498 let group_y = create(&initial_members);
500
501 let group_y = promote(group_y, alice, bob, Access::write()).unwrap();
503
504 let group_y_clone = group_y.clone();
505
506 let bob_state = group_y_clone.members.get(&bob).unwrap();
507 assert!(bob_state.is_writer());
508
509 let group_y = demote(group_y.clone(), alice, bob, Access::read()).unwrap();
511
512 let group_y = modify(group_y, alice, bob, Access::manage()).unwrap();
514
515 let bob_state = group_y.members.get(&bob).unwrap();
516 assert!(bob_state.is_manager());
517 }
518
519 #[test]
520 fn add_errors() {
521 let alice = 0;
524 let bob = 1;
525 let charlie = 2;
526 let daphne = 3;
527
528 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
529
530 let group_y = create(&initial_members);
532
533 let result = add(group_y.clone(), charlie, daphne, Access::read());
535
536 std::assert_matches!(result, Err(GroupMembershipError::UnrecognisedActor(_bob)));
538
539 let result = add(group_y.clone(), bob, daphne, Access::read());
541
542 std::assert_matches!(result, Err(GroupMembershipError::InsufficientAccess(_bob)));
544
545 let result = add(group_y.clone(), alice, bob, Access::read());
547
548 std::assert_matches!(result, Err(GroupMembershipError::AlreadyAdded(_bob)));
550
551 let group_y = remove(group_y, alice, bob).unwrap();
553
554 let result = add(group_y, bob, daphne, Access::read());
556
557 std::assert_matches!(result, Err(GroupMembershipError::InactiveActor(_bob)));
559
560 }
571
572 #[test]
573 fn remove_errors() {
574 let alice = 0;
577 let bob = 1;
578 let charlie = 2;
579 let daphne = 3;
580
581 let initial_members = [
582 (alice, <Access>::manage()),
583 (bob, Access::read()),
584 (charlie, Access::read()),
585 ];
586
587 let group_y = create(&initial_members);
589
590 let result = remove(group_y.clone(), daphne, charlie);
592
593 std::assert_matches!(
595 result,
596 Err(GroupMembershipError::UnrecognisedActor(_daphne))
597 );
598
599 let result = remove(group_y.clone(), bob, charlie);
601
602 std::assert_matches!(result, Err(GroupMembershipError::InsufficientAccess(_bob)));
604
605 let result = remove(group_y.clone(), alice, daphne);
607
608 std::assert_matches!(
610 result,
611 Err(GroupMembershipError::UnrecognisedMember(_daphne))
612 );
613
614 let group_y = remove(group_y, alice, charlie).unwrap();
616
617 let result = remove(group_y, alice, charlie);
619
620 std::assert_matches!(result, Err(GroupMembershipError::AlreadyRemoved(_charlie)));
622 }
623
624 #[test]
625 fn promote_errors() {
626 let alice = 0;
629 let bob = 1;
630 let charlie = 2;
631 let daphne = 3;
632
633 let initial_members = [
634 (alice, <Access>::manage()),
635 (bob, Access::read()),
636 (charlie, Access::read()),
637 ];
638
639 let group_y = create(&initial_members);
641
642 let result = promote(group_y.clone(), daphne, charlie, Access::manage());
644
645 std::assert_matches!(
647 result,
648 Err(GroupMembershipError::UnrecognisedActor(_daphne))
649 );
650
651 let result = promote(group_y.clone(), bob, charlie, Access::write());
653
654 std::assert_matches!(result, Err(GroupMembershipError::InsufficientAccess(_bob)));
656
657 let result = promote(group_y.clone(), alice, daphne, Access::read());
659
660 std::assert_matches!(
662 result,
663 Err(GroupMembershipError::UnrecognisedMember(_daphne))
664 );
665
666 let group_y = remove(group_y, alice, charlie).unwrap();
668
669 let result = promote(group_y.clone(), alice, charlie, Access::pull());
671
672 std::assert_matches!(result, Err(GroupMembershipError::InactiveMember(_charlie)));
674
675 let result = promote(group_y, charlie, bob, Access::manage());
677
678 std::assert_matches!(result, Err(GroupMembershipError::InactiveActor(_charlie)));
680 }
681
682 #[test]
683 fn demote_errors() {
684 let alice = 0;
687 let bob = 1;
688 let charlie = 2;
689 let daphne = 3;
690
691 let initial_members = [
692 (alice, <Access>::manage()),
693 (bob, Access::read()),
694 (charlie, Access::read()),
695 ];
696
697 let group_y = create(&initial_members);
699
700 let result = demote(group_y.clone(), daphne, charlie, Access::pull());
702
703 std::assert_matches!(
705 result,
706 Err(GroupMembershipError::UnrecognisedActor(_daphne))
707 );
708
709 let result = demote(group_y.clone(), bob, charlie, Access::pull());
711
712 std::assert_matches!(result, Err(GroupMembershipError::InsufficientAccess(_bob)));
714
715 let result = demote(group_y.clone(), alice, daphne, Access::read());
717
718 std::assert_matches!(
720 result,
721 Err(GroupMembershipError::UnrecognisedMember(_daphne))
722 );
723
724 let group_y = remove(group_y, alice, charlie).unwrap();
726
727 let result = demote(group_y.clone(), alice, charlie, Access::pull());
729
730 std::assert_matches!(result, Err(GroupMembershipError::InactiveMember(_charlie)));
732
733 let result = demote(group_y, charlie, bob, Access::pull());
735
736 std::assert_matches!(result, Err(GroupMembershipError::InactiveActor(_charlie)));
738 }
739
740 #[test]
741 fn merge_state_member() {
742 let alice = 0;
746 let bob = 1;
747 let charlie = 2;
748 let daphne = 3;
749
750 let initial_members = [
751 (alice, <Access>::manage()),
752 (bob, Access::read()),
753 (charlie, Access::pull()),
754 ];
755
756 let group_y_i = create(&initial_members);
758
759 let group_y_ii = add(group_y_i.clone(), alice, daphne, Access::read()).unwrap();
761
762 let group_y = merge(group_y_i, group_y_ii);
764
765 assert!(group_y.members().contains(&daphne));
766 }
767
768 #[test]
769 fn merge_state_counter() {
770 let alice = 0;
774 let bob = 1;
775 let charlie = 2;
776
777 let initial_members = [
778 (alice, <Access>::manage()),
779 (bob, Access::read()),
780 (charlie, Access::pull()),
781 ];
782
783 let group_y_i = create(&initial_members);
785
786 let group_y_ii = remove(group_y_i.clone(), alice, bob).unwrap();
788
789 let group_y_ii = add(group_y_ii, alice, bob, Access::read()).unwrap();
791
792 let group_y = merge(group_y_i, group_y_ii);
794
795 assert!(group_y.members().contains(&alice));
796 assert!(group_y.members().contains(&bob));
797 assert!(group_y.members().contains(&charlie));
798
799 let bob_state = group_y.members.get(&bob).unwrap();
800
801 assert!(bob_state.member_counter == 3);
803 }
804
805 #[test]
806 fn merge_state_access_counter() {
807 let alice = 0;
811 let bob = 1;
812 let charlie = 2;
813
814 let initial_members = [
815 (alice, <Access>::manage()),
816 (bob, Access::read()),
817 (charlie, Access::pull()),
818 ];
819
820 let group_y_i = create(&initial_members);
822
823 let group_y_ii = promote(group_y_i.clone(), alice, charlie, Access::read()).unwrap();
825
826 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::pull()).unwrap();
828
829 let group_y = merge(group_y_i, group_y_ii);
831
832 let charlie_state = group_y.members.get(&charlie).unwrap();
833
834 assert!(charlie_state.access_counter == 2);
836
837 assert!(charlie_state.is_puller());
839 }
840
841 #[test]
842 fn merge_state_access() {
843 let alice = 0;
848 let bob = 1;
849 let charlie = 2;
850
851 let initial_members = [
852 (alice, <Access>::manage()),
853 (bob, Access::read()),
854 (charlie, Access::pull()),
855 ];
856
857 let group_y = create(&initial_members);
859
860 let group_y_i = promote(group_y.clone(), alice, charlie, Access::read()).unwrap();
862
863 let group_y_i = demote(group_y_i.clone(), alice, charlie, Access::pull()).unwrap();
865
866 let group_y_ii = modify(group_y.clone(), alice, charlie, Access::manage()).unwrap();
868
869 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::read()).unwrap();
871
872 let group_y = merge(group_y_i.clone(), group_y_ii.clone());
874
875 let charlie_state = group_y.members.get(&charlie).unwrap();
876
877 assert!(charlie_state.is_puller());
879 }
880}