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() {
252 return Err(GroupMembershipError::InactiveActor(remover));
253 } else if !remover_state.is_manager() {
254 return Err(GroupMembershipError::InsufficientAccess(remover));
255 }
256
257 if !state.members.contains_key(&removed) {
259 return Err(GroupMembershipError::UnrecognisedMember(removed));
260 };
261
262 if let Some(removed_state) = state.members.get(&removed)
264 && !removed_state.is_member()
265 {
266 return Err(GroupMembershipError::AlreadyRemoved(removed));
267 }
268
269 let mut state = state;
271 state.members.entry(removed).and_modify(|removed| {
272 if removed.is_member() {
273 removed.member_counter += 1;
274 removed.access_counter = 0;
275 }
276 });
277
278 Ok(state)
279}
280
281fn modify<ID: Eq + Hash, C: Conditions>(
288 state: GroupMembersState<ID, C>,
289 modifier: ID,
290 modified: ID,
291 access: Access<C>,
292) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
293 let Some(modifier_state) = state.members.get(&modifier) else {
295 return Err(GroupMembershipError::UnrecognisedActor(modifier));
296 };
297
298 if !modifier_state.is_member() {
300 return Err(GroupMembershipError::InactiveActor(modifier));
301 } else if !modifier_state.is_manager() {
302 return Err(GroupMembershipError::InsufficientAccess(modifier));
303 }
304
305 if let Some(modified_state) = state.members.get(&modified) {
307 if !modified_state.is_member() {
308 return Err(GroupMembershipError::InactiveMember(modified));
309 }
310 } else {
311 return Err(GroupMembershipError::UnrecognisedMember(modified));
312 }
313
314 let mut state = state;
316 state.members.entry(modified).and_modify(|modified| {
317 if modified.access != access {
319 modified.access = access;
320 modified.access_counter += 1;
321 }
322 });
323
324 Ok(state)
325}
326
327pub fn promote<ID: Eq + Hash, C: Conditions>(
336 state: GroupMembersState<ID, C>,
337 promoter: ID,
338 promoted: ID,
339 access: Access<C>,
340) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
341 if let Some(member) = state.members.get(&promoted) {
342 let new_state = if member.is_manager() {
344 state
345 } else {
346 modify(state, promoter, promoted, access)?
347 };
348
349 Ok(new_state)
350 } else {
351 Err(GroupMembershipError::UnrecognisedMember(promoted))
352 }
353}
354
355pub fn demote<ID: Eq + Hash, C: Conditions>(
364 state: GroupMembersState<ID, C>,
365 demoter: ID,
366 demoted: ID,
367 access: Access<C>,
368) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
369 if let Some(member) = state.members.get(&demoted) {
370 let new_state = if member.is_puller() {
372 state
373 } else {
374 modify(state, demoter, demoted, access)?
375 };
376
377 Ok(new_state)
378 } else {
379 Err(GroupMembershipError::UnrecognisedMember(demoted))
380 }
381}
382
383pub fn merge<ID: Clone + Eq + Hash, C: Conditions>(
393 state_1: GroupMembersState<ID, C>,
394 state_2: GroupMembersState<ID, C>,
395) -> GroupMembersState<ID, C> {
396 let mut next_state = state_2.clone();
398
399 for (id, member_state_1) in state_1.members {
401 if let Some(member_state) = next_state.members.get_mut(&id) {
402 if member_state_1.member_counter > member_state.member_counter {
404 member_state.member_counter = member_state_1.member_counter;
405 member_state.access = member_state_1.access.clone();
406 member_state.access_counter = member_state_1.access_counter;
407 }
408
409 if member_state_1.member_counter == member_state.member_counter {
412 if member_state_1.access_counter > member_state.access_counter {
413 member_state.access = member_state_1.access.clone();
414 member_state.access_counter = member_state_1.access_counter;
415 }
416
417 if member_state_1.access_counter == member_state.access_counter
419 && member_state_1.access < member_state.access
420 {
421 member_state.access = member_state_1.access;
422 }
423 }
424 } else {
425 next_state.members.insert(id, member_state_1);
427 }
428 }
429
430 next_state
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn create_add_remove() {
439 let alice = 0;
442 let bob = 1;
443 let charlie = 2;
444
445 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
446
447 let group_y = create(&initial_members);
449
450 assert!(group_y.members().contains(&alice));
451 assert!(group_y.members().contains(&bob));
452
453 assert!(group_y.managers().contains(&alice));
454 assert!(!group_y.managers().contains(&bob));
455
456 let group_y = add(group_y, alice, charlie, Access::write()).unwrap();
458
459 assert!(group_y.members().contains(&charlie));
460
461 let group_y = remove(group_y, alice, bob).unwrap();
463
464 assert!(!group_y.members().contains(&bob));
465 }
466
467 #[test]
468 fn promote_demote_modify() {
469 let alice = 0;
470 let bob = 1;
471
472 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
473
474 let group_y = create(&initial_members);
476
477 let group_y = promote(group_y, alice, bob, Access::write()).unwrap();
479
480 let group_y_clone = group_y.clone();
481
482 let bob_state = group_y_clone.members.get(&bob).unwrap();
483 assert!(bob_state.is_writer());
484
485 let group_y = demote(group_y.clone(), alice, bob, Access::read()).unwrap();
487
488 let group_y = modify(group_y, alice, bob, Access::manage()).unwrap();
490
491 let bob_state = group_y.members.get(&bob).unwrap();
492 assert!(bob_state.is_manager());
493 }
494
495 #[test]
496 fn add_errors() {
497 let alice = 0;
500 let bob = 1;
501 let charlie = 2;
502 let daphne = 3;
503
504 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
505
506 let group_y = create(&initial_members);
508
509 let result = add(group_y.clone(), charlie, daphne, Access::read());
511
512 assert!(matches!(
514 result,
515 Err(GroupMembershipError::UnrecognisedActor(_bob))
516 ));
517
518 let result = add(group_y.clone(), bob, daphne, Access::read());
520
521 assert!(matches!(
523 result,
524 Err(GroupMembershipError::InsufficientAccess(_bob))
525 ));
526
527 let result = add(group_y.clone(), alice, bob, Access::read());
529
530 assert!(matches!(
532 result,
533 Err(GroupMembershipError::AlreadyAdded(_bob))
534 ));
535
536 let group_y = remove(group_y, alice, bob).unwrap();
538
539 let result = add(group_y, bob, daphne, Access::read());
541
542 assert!(matches!(
544 result,
545 Err(GroupMembershipError::InactiveActor(_bob))
546 ));
547
548 }
559
560 #[test]
561 fn remove_errors() {
562 let alice = 0;
565 let bob = 1;
566 let charlie = 2;
567 let daphne = 3;
568
569 let initial_members = [
570 (alice, <Access>::manage()),
571 (bob, Access::read()),
572 (charlie, Access::read()),
573 ];
574
575 let group_y = create(&initial_members);
577
578 let result = remove(group_y.clone(), daphne, charlie);
580
581 assert!(matches!(
583 result,
584 Err(GroupMembershipError::UnrecognisedActor(_daphne))
585 ));
586
587 let result = remove(group_y.clone(), bob, charlie);
589
590 assert!(matches!(
592 result,
593 Err(GroupMembershipError::InsufficientAccess(_bob))
594 ));
595
596 let result = remove(group_y.clone(), alice, daphne);
598
599 assert!(matches!(
601 result,
602 Err(GroupMembershipError::UnrecognisedMember(_daphne))
603 ));
604
605 let group_y = remove(group_y, alice, charlie).unwrap();
607
608 let result = remove(group_y, alice, charlie);
610
611 assert!(matches!(
613 result,
614 Err(GroupMembershipError::AlreadyRemoved(_charlie))
615 ));
616 }
617
618 #[test]
619 fn promote_errors() {
620 let alice = 0;
623 let bob = 1;
624 let charlie = 2;
625 let daphne = 3;
626
627 let initial_members = [
628 (alice, <Access>::manage()),
629 (bob, Access::read()),
630 (charlie, Access::read()),
631 ];
632
633 let group_y = create(&initial_members);
635
636 let result = promote(group_y.clone(), daphne, charlie, Access::manage());
638
639 assert!(matches!(
641 result,
642 Err(GroupMembershipError::UnrecognisedActor(_daphne))
643 ));
644
645 let result = promote(group_y.clone(), bob, charlie, Access::write());
647
648 assert!(matches!(
650 result,
651 Err(GroupMembershipError::InsufficientAccess(_bob))
652 ));
653
654 let result = promote(group_y.clone(), alice, daphne, Access::read());
656
657 assert!(matches!(
659 result,
660 Err(GroupMembershipError::UnrecognisedMember(_daphne))
661 ));
662
663 let group_y = remove(group_y, alice, charlie).unwrap();
665
666 let result = promote(group_y.clone(), alice, charlie, Access::pull());
668
669 assert!(matches!(
671 result,
672 Err(GroupMembershipError::InactiveMember(_charlie))
673 ));
674
675 let result = promote(group_y, charlie, bob, Access::manage());
677
678 assert!(matches!(
680 result,
681 Err(GroupMembershipError::InactiveActor(_charlie))
682 ));
683 }
684
685 #[test]
686 fn demote_errors() {
687 let alice = 0;
690 let bob = 1;
691 let charlie = 2;
692 let daphne = 3;
693
694 let initial_members = [
695 (alice, <Access>::manage()),
696 (bob, Access::read()),
697 (charlie, Access::read()),
698 ];
699
700 let group_y = create(&initial_members);
702
703 let result = demote(group_y.clone(), daphne, charlie, Access::pull());
705
706 assert!(matches!(
708 result,
709 Err(GroupMembershipError::UnrecognisedActor(_daphne))
710 ));
711
712 let result = demote(group_y.clone(), bob, charlie, Access::pull());
714
715 assert!(matches!(
717 result,
718 Err(GroupMembershipError::InsufficientAccess(_bob))
719 ));
720
721 let result = demote(group_y.clone(), alice, daphne, Access::read());
723
724 assert!(matches!(
726 result,
727 Err(GroupMembershipError::UnrecognisedMember(_daphne))
728 ));
729
730 let group_y = remove(group_y, alice, charlie).unwrap();
732
733 let result = demote(group_y.clone(), alice, charlie, Access::pull());
735
736 assert!(matches!(
738 result,
739 Err(GroupMembershipError::InactiveMember(_charlie))
740 ));
741
742 let result = demote(group_y, charlie, bob, Access::pull());
744
745 assert!(matches!(
747 result,
748 Err(GroupMembershipError::InactiveActor(_charlie))
749 ));
750 }
751
752 #[test]
753 fn merge_state_member() {
754 let alice = 0;
758 let bob = 1;
759 let charlie = 2;
760 let daphne = 3;
761
762 let initial_members = [
763 (alice, <Access>::manage()),
764 (bob, Access::read()),
765 (charlie, Access::pull()),
766 ];
767
768 let group_y_i = create(&initial_members);
770
771 let group_y_ii = add(group_y_i.clone(), alice, daphne, Access::read()).unwrap();
773
774 let group_y = merge(group_y_i, group_y_ii);
776
777 assert!(group_y.members().contains(&daphne));
778 }
779
780 #[test]
781 fn merge_state_counter() {
782 let alice = 0;
786 let bob = 1;
787 let charlie = 2;
788
789 let initial_members = [
790 (alice, <Access>::manage()),
791 (bob, Access::read()),
792 (charlie, Access::pull()),
793 ];
794
795 let group_y_i = create(&initial_members);
797
798 let group_y_ii = remove(group_y_i.clone(), alice, bob).unwrap();
800
801 let group_y_ii = add(group_y_ii, alice, bob, Access::read()).unwrap();
803
804 let group_y = merge(group_y_i, group_y_ii);
806
807 assert!(group_y.members().contains(&alice));
808 assert!(group_y.members().contains(&bob));
809 assert!(group_y.members().contains(&charlie));
810
811 let bob_state = group_y.members.get(&bob).unwrap();
812
813 assert!(bob_state.member_counter == 3);
815 }
816
817 #[test]
818 fn merge_state_access_counter() {
819 let alice = 0;
823 let bob = 1;
824 let charlie = 2;
825
826 let initial_members = [
827 (alice, <Access>::manage()),
828 (bob, Access::read()),
829 (charlie, Access::pull()),
830 ];
831
832 let group_y_i = create(&initial_members);
834
835 let group_y_ii = promote(group_y_i.clone(), alice, charlie, Access::read()).unwrap();
837
838 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::pull()).unwrap();
840
841 let group_y = merge(group_y_i, group_y_ii);
843
844 let charlie_state = group_y.members.get(&charlie).unwrap();
845
846 assert!(charlie_state.access_counter == 2);
848
849 assert!(charlie_state.is_puller());
851 }
852
853 #[test]
854 fn merge_state_access() {
855 let alice = 0;
860 let bob = 1;
861 let charlie = 2;
862
863 let initial_members = [
864 (alice, <Access>::manage()),
865 (bob, Access::read()),
866 (charlie, Access::pull()),
867 ];
868
869 let group_y = create(&initial_members);
871
872 let group_y_i = promote(group_y.clone(), alice, charlie, Access::read()).unwrap();
874
875 let group_y_i = demote(group_y_i.clone(), alice, charlie, Access::pull()).unwrap();
877
878 let group_y_ii = modify(group_y.clone(), alice, charlie, Access::manage()).unwrap();
880
881 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::read()).unwrap();
883
884 let group_y = merge(group_y_i.clone(), group_y_ii.clone());
886
887 let charlie_state = group_y.members.get(&charlie).unwrap();
888
889 assert!(charlie_state.is_puller());
891 }
892}