1use std::collections::{HashMap, HashSet};
13use std::fmt::Debug;
14use std::hash::Hash;
15
16use thiserror::Error;
17
18use crate::Access;
19
20#[derive(Debug, Error, PartialEq)]
23pub enum GroupMembershipError<ID> {
24 #[error("attempted to add a member who is already active in the group: {0}")]
25 AlreadyAdded(ID),
26
27 #[error("attempted to remove a member who is already inactive in the group: {0}")]
28 AlreadyRemoved(ID),
29
30 #[error("actor lacks sufficient access to update the group: {0}")]
31 InsufficientAccess(ID),
32
33 #[error("actor is not an active member of the group: {0}")]
34 InactiveActor(ID),
35
36 #[error("member is not an active member of the group: {0}")]
37 InactiveMember(ID),
38
39 #[error("actor is not known to the group: {0}")]
40 UnrecognisedActor(ID),
41
42 #[error("member is not known to the group: {0}")]
43 UnrecognisedMember(ID),
44}
45
46#[derive(Clone, Debug)]
50pub struct MemberState<C> {
51 pub(crate) member_counter: usize,
52 pub(crate) access: Access<C>,
53 pub(crate) access_counter: usize,
54}
55
56impl<C> MemberState<C>
57where
58 C: Clone + Debug + PartialEq,
59{
60 pub fn access(&self) -> Access<C> {
62 self.access.clone()
63 }
64
65 pub fn is_member(&self) -> bool {
67 self.member_counter % 2 != 0
68 }
69
70 pub fn is_puller(&self) -> bool {
72 self.access.is_pull()
73 }
74
75 pub fn is_reader(&self) -> bool {
77 self.access.is_read()
78 }
79
80 pub fn is_writer(&self) -> bool {
82 self.access.is_write()
83 }
84
85 pub fn is_manager(&self) -> bool {
87 self.access.is_manage()
88 }
89}
90
91#[derive(Clone, Debug)]
93pub struct GroupMembersState<ID, C> {
94 pub(crate) members: HashMap<ID, MemberState<C>>,
95}
96
97impl<ID, C> Default for GroupMembersState<ID, C>
98where
99 C: PartialEq,
100{
101 fn default() -> Self {
102 Self {
103 members: Default::default(),
104 }
105 }
106}
107
108impl<ID, C> GroupMembersState<ID, C>
109where
110 ID: Clone + Hash + Eq,
111 C: Clone + Debug + PartialEq,
112{
113 pub fn members(&self) -> HashSet<ID> {
115 self.members
116 .iter()
117 .filter_map(|(id, state)| {
118 if state.is_member() {
119 Some(id.to_owned())
120 } else {
121 None
122 }
123 })
124 .collect::<HashSet<ID>>()
125 }
126
127 pub fn managers(&self) -> HashSet<ID> {
129 self.members
130 .iter()
131 .filter_map(|(id, state)| {
132 if state.is_member() && state.is_manager() {
133 Some(id.to_owned())
134 } else {
135 None
136 }
137 })
138 .collect::<HashSet<_>>()
139 }
140}
141
142pub fn create<ID: Clone + Eq + Hash, C: Clone + PartialEq>(
144 initial_members: &[(ID, Access<C>)],
145) -> GroupMembersState<ID, C> {
146 let mut members = HashMap::new();
147 for (id, access) in initial_members {
148 let member = MemberState {
149 member_counter: 1,
150 access: access.clone(),
151 access_counter: 0,
152 };
153 members.insert(id.clone(), member);
154 }
155
156 GroupMembersState { members }
157}
158
159pub fn add<ID: Clone + Eq + Hash, C: Clone + Debug + PartialEq>(
167 state: GroupMembersState<ID, C>,
168 adder: ID,
169 added: ID,
170 access: Access<C>,
171) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
172 let Some(adder_state) = state.members.get(&adder) else {
174 return Err(GroupMembershipError::UnrecognisedActor(adder));
175 };
176
177 if !adder_state.is_member() {
179 return Err(GroupMembershipError::InactiveActor(adder));
180 } else if !adder_state.is_manager() {
181 return Err(GroupMembershipError::InsufficientAccess(adder));
182 }
183
184 if let Some(added_state) = state.members.get(&added) {
186 if added_state.is_member() {
187 return Err(GroupMembershipError::AlreadyAdded(added));
188 }
189 }
190
191 let mut state = state;
194 state
195 .members
196 .entry(added.clone())
197 .and_modify(|added| {
198 if !added.is_member() {
199 added.member_counter += 1;
200 added.access = access.clone();
201 added.access_counter = 0;
202 }
203 })
204 .or_insert(MemberState {
205 member_counter: 1,
206 access,
207 access_counter: 0,
208 });
209
210 Ok(state)
211}
212
213pub fn remove<ID: Eq + Hash, C: Clone + Debug + PartialEq>(
219 state: GroupMembersState<ID, C>,
220 remover: ID,
221 removed: ID,
222) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
223 let Some(remover_state) = state.members.get(&remover) else {
225 return Err(GroupMembershipError::UnrecognisedActor(remover));
226 };
227
228 if !remover_state.is_member() {
230 return Err(GroupMembershipError::InactiveActor(remover));
231 } else if !remover_state.is_manager() {
232 return Err(GroupMembershipError::InsufficientAccess(remover));
233 }
234
235 if !state.members.contains_key(&removed) {
237 return Err(GroupMembershipError::UnrecognisedMember(removed));
238 };
239
240 if let Some(removed_state) = state.members.get(&removed) {
242 if !removed_state.is_member() {
243 return Err(GroupMembershipError::AlreadyRemoved(removed));
244 }
245 }
246
247 let mut state = state;
249 state.members.entry(removed).and_modify(|removed| {
250 if removed.is_member() {
251 removed.member_counter += 1;
252 removed.access_counter = 0;
253 }
254 });
255
256 Ok(state)
257}
258
259fn modify<ID: Eq + Hash, C: Clone + Debug + PartialEq + PartialOrd>(
266 state: GroupMembersState<ID, C>,
267 modifier: ID,
268 modified: ID,
269 access: Access<C>,
270) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
271 let Some(modifier_state) = state.members.get(&modifier) else {
273 return Err(GroupMembershipError::UnrecognisedActor(modifier));
274 };
275
276 if !modifier_state.is_member() {
278 return Err(GroupMembershipError::InactiveActor(modifier));
279 } else if !modifier_state.is_manager() {
280 return Err(GroupMembershipError::InsufficientAccess(modifier));
281 }
282
283 if let Some(modified_state) = state.members.get(&modified) {
285 if !modified_state.is_member() {
286 return Err(GroupMembershipError::InactiveMember(modified));
287 }
288 } else {
289 return Err(GroupMembershipError::UnrecognisedMember(modified));
290 }
291
292 let mut state = state;
294 state.members.entry(modified).and_modify(|modified| {
295 if modified.access != access {
297 modified.access = access;
298 modified.access_counter += 1;
299 }
300 });
301
302 Ok(state)
303}
304
305pub fn promote<ID: Eq + Hash, C: Clone + Debug + PartialEq + PartialOrd>(
314 state: GroupMembersState<ID, C>,
315 promoter: ID,
316 promoted: ID,
317 access: Access<C>,
318) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
319 if let Some(member) = state.members.get(&promoted) {
320 let new_state = if member.is_manager() {
322 state
323 } else {
324 modify(state, promoter, promoted, access)?
325 };
326
327 Ok(new_state)
328 } else {
329 Err(GroupMembershipError::UnrecognisedMember(promoted))
330 }
331}
332
333pub fn demote<ID: Eq + Hash, C: Clone + Debug + PartialEq + PartialOrd>(
342 state: GroupMembersState<ID, C>,
343 demoter: ID,
344 demoted: ID,
345 access: Access<C>,
346) -> Result<GroupMembersState<ID, C>, GroupMembershipError<ID>> {
347 if let Some(member) = state.members.get(&demoted) {
348 let new_state = if member.is_puller() {
350 state
351 } else {
352 modify(state, demoter, demoted, access)?
353 };
354
355 Ok(new_state)
356 } else {
357 Err(GroupMembershipError::UnrecognisedMember(demoted))
358 }
359}
360
361pub fn merge<ID: Clone + Eq + Hash, C: Clone + Debug + PartialEq + PartialOrd>(
371 state_1: GroupMembersState<ID, C>,
372 state_2: GroupMembersState<ID, C>,
373) -> GroupMembersState<ID, C> {
374 let mut next_state = state_2.clone();
376
377 for (id, member_state_1) in state_1.members {
379 if let Some(member_state) = next_state.members.get_mut(&id) {
380 if member_state_1.member_counter > member_state.member_counter {
382 member_state.member_counter = member_state_1.member_counter;
383 member_state.access = member_state_1.access.clone();
384 member_state.access_counter = member_state_1.access_counter;
385 }
386
387 if member_state_1.member_counter == member_state.member_counter {
390 if member_state_1.access_counter > member_state.access_counter {
391 member_state.access = member_state_1.access.clone();
392 member_state.access_counter = member_state_1.access_counter;
393 }
394
395 if member_state_1.access_counter == member_state.access_counter
397 && member_state_1.access < member_state.access
398 {
399 member_state.access = member_state_1.access;
400 }
401 }
402 } else {
403 next_state.members.insert(id, member_state_1);
405 }
406 }
407
408 next_state
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn create_add_remove() {
417 let alice = 0;
420 let bob = 1;
421 let charlie = 2;
422
423 let initial_members = [(alice, Access::manage()), (bob, Access::read())];
424
425 let group_y = create(&initial_members);
427
428 assert!(group_y.members().contains(&alice));
429 assert!(group_y.members().contains(&bob));
430
431 assert!(group_y.managers().contains(&alice));
432 assert!(!group_y.managers().contains(&bob));
433
434 let group_y = add(
436 group_y,
437 alice,
438 charlie,
439 Access::write().with_conditions("requirement".to_string()),
440 )
441 .unwrap();
442
443 assert!(group_y.members().contains(&charlie));
444
445 let group_y = remove(group_y, alice, bob).unwrap();
447
448 assert!(!group_y.members().contains(&bob));
449 }
450
451 #[test]
452 fn promote_demote_modify() {
453 let alice = 0;
454 let bob = 1;
455
456 let initial_members = [(alice, Access::manage()), (bob, Access::read())];
457
458 let group_y = create(&initial_members);
460
461 let group_y = promote(
463 group_y,
464 alice,
465 bob,
466 Access::write().with_conditions("requirement".to_string()),
467 )
468 .unwrap();
469
470 let group_y_clone = group_y.clone();
471
472 let bob_state = group_y_clone.members.get(&bob).unwrap();
473 assert!(bob_state.is_writer());
474
475 let group_y = demote(group_y.clone(), alice, bob, Access::read()).unwrap();
477
478 let group_y = modify(group_y, alice, bob, Access::manage()).unwrap();
480
481 let bob_state = group_y.members.get(&bob).unwrap();
482 assert!(bob_state.is_manager());
483 }
484
485 #[test]
486 fn add_errors() {
487 let alice = 0;
490 let bob = 1;
491 let charlie = 2;
492 let daphne = 3;
493
494 let initial_members = [(alice, <Access>::manage()), (bob, Access::read())];
495
496 let group_y = create(&initial_members);
498
499 let result = add(group_y.clone(), charlie, daphne, Access::read());
501
502 assert!(matches!(
504 result,
505 Err(GroupMembershipError::UnrecognisedActor(_bob))
506 ));
507
508 let result = add(group_y.clone(), bob, daphne, Access::read());
510
511 assert!(matches!(
513 result,
514 Err(GroupMembershipError::InsufficientAccess(_bob))
515 ));
516
517 let result = add(group_y.clone(), alice, bob, Access::read());
519
520 assert!(matches!(
522 result,
523 Err(GroupMembershipError::AlreadyAdded(_bob))
524 ));
525
526 let group_y = remove(group_y, alice, bob).unwrap();
528
529 let result = add(group_y, bob, daphne, Access::read());
531
532 assert!(matches!(
534 result,
535 Err(GroupMembershipError::InactiveActor(_bob))
536 ));
537
538 }
549
550 #[test]
551 fn remove_errors() {
552 let alice = 0;
555 let bob = 1;
556 let charlie = 2;
557 let daphne = 3;
558
559 let initial_members = [
560 (alice, <Access>::manage()),
561 (bob, Access::read()),
562 (charlie, Access::read()),
563 ];
564
565 let group_y = create(&initial_members);
567
568 let result = remove(group_y.clone(), daphne, charlie);
570
571 assert!(matches!(
573 result,
574 Err(GroupMembershipError::UnrecognisedActor(_daphne))
575 ));
576
577 let result = remove(group_y.clone(), bob, charlie);
579
580 assert!(matches!(
582 result,
583 Err(GroupMembershipError::InsufficientAccess(_bob))
584 ));
585
586 let result = remove(group_y.clone(), alice, daphne);
588
589 assert!(matches!(
591 result,
592 Err(GroupMembershipError::UnrecognisedMember(_daphne))
593 ));
594
595 let group_y = remove(group_y, alice, charlie).unwrap();
597
598 let result = remove(group_y, alice, charlie);
600
601 assert!(matches!(
603 result,
604 Err(GroupMembershipError::AlreadyRemoved(_charlie))
605 ));
606 }
607
608 #[test]
609 fn promote_errors() {
610 let alice = 0;
613 let bob = 1;
614 let charlie = 2;
615 let daphne = 3;
616
617 let initial_members = [
618 (alice, Access::manage()),
619 (bob, Access::read()),
620 (charlie, Access::read()),
621 ];
622
623 let group_y = create(&initial_members);
625
626 let result = promote(group_y.clone(), daphne, charlie, Access::manage());
628
629 assert!(matches!(
631 result,
632 Err(GroupMembershipError::UnrecognisedActor(_daphne))
633 ));
634
635 let result = promote(
637 group_y.clone(),
638 bob,
639 charlie,
640 Access::write().with_conditions("paw".to_string()),
641 );
642
643 assert!(matches!(
645 result,
646 Err(GroupMembershipError::InsufficientAccess(_bob))
647 ));
648
649 let result = promote(group_y.clone(), alice, daphne, Access::read());
651
652 assert!(matches!(
654 result,
655 Err(GroupMembershipError::UnrecognisedMember(_daphne))
656 ));
657
658 let group_y = remove(group_y, alice, charlie).unwrap();
660
661 let result = promote(group_y.clone(), alice, charlie, Access::pull());
663
664 assert!(matches!(
666 result,
667 Err(GroupMembershipError::InactiveMember(_charlie))
668 ));
669
670 let result = promote(group_y, charlie, bob, Access::manage());
672
673 assert!(matches!(
675 result,
676 Err(GroupMembershipError::InactiveActor(_charlie))
677 ));
678 }
679
680 #[test]
681 fn demote_errors() {
682 let alice = 0;
685 let bob = 1;
686 let charlie = 2;
687 let daphne = 3;
688
689 let initial_members = [
690 (alice, <Access>::manage()),
691 (bob, Access::read()),
692 (charlie, Access::read()),
693 ];
694
695 let group_y = create(&initial_members);
697
698 let result = demote(group_y.clone(), daphne, charlie, Access::pull());
700
701 assert!(matches!(
703 result,
704 Err(GroupMembershipError::UnrecognisedActor(_daphne))
705 ));
706
707 let result = demote(group_y.clone(), bob, charlie, Access::pull());
709
710 assert!(matches!(
712 result,
713 Err(GroupMembershipError::InsufficientAccess(_bob))
714 ));
715
716 let result = demote(group_y.clone(), alice, daphne, Access::read());
718
719 assert!(matches!(
721 result,
722 Err(GroupMembershipError::UnrecognisedMember(_daphne))
723 ));
724
725 let group_y = remove(group_y, alice, charlie).unwrap();
727
728 let result = demote(group_y.clone(), alice, charlie, Access::pull());
730
731 assert!(matches!(
733 result,
734 Err(GroupMembershipError::InactiveMember(_charlie))
735 ));
736
737 let result = demote(group_y, charlie, bob, Access::pull());
739
740 assert!(matches!(
742 result,
743 Err(GroupMembershipError::InactiveActor(_charlie))
744 ));
745 }
746
747 #[test]
748 fn merge_state_member() {
749 let alice = 0;
753 let bob = 1;
754 let charlie = 2;
755 let daphne = 3;
756
757 let initial_members = [
758 (alice, <Access>::manage()),
759 (bob, Access::read()),
760 (charlie, Access::pull()),
761 ];
762
763 let group_y_i = create(&initial_members);
765
766 let group_y_ii = add(group_y_i.clone(), alice, daphne, Access::read()).unwrap();
768
769 let group_y = merge(group_y_i, group_y_ii);
771
772 assert!(group_y.members().contains(&daphne));
773 }
774
775 #[test]
776 fn merge_state_counter() {
777 let alice = 0;
781 let bob = 1;
782 let charlie = 2;
783
784 let initial_members = [
785 (alice, <Access>::manage()),
786 (bob, Access::read()),
787 (charlie, Access::pull()),
788 ];
789
790 let group_y_i = create(&initial_members);
792
793 let group_y_ii = remove(group_y_i.clone(), alice, bob).unwrap();
795
796 let group_y_ii = add(group_y_ii, alice, bob, Access::read()).unwrap();
798
799 let group_y = merge(group_y_i, group_y_ii);
801
802 assert!(group_y.members().contains(&alice));
803 assert!(group_y.members().contains(&bob));
804 assert!(group_y.members().contains(&charlie));
805
806 let bob_state = group_y.members.get(&bob).unwrap();
807
808 assert!(bob_state.member_counter == 3);
810 }
811
812 #[test]
813 fn merge_state_access_counter() {
814 let alice = 0;
818 let bob = 1;
819 let charlie = 2;
820
821 let initial_members = [
822 (alice, <Access>::manage()),
823 (bob, Access::read()),
824 (charlie, Access::pull()),
825 ];
826
827 let group_y_i = create(&initial_members);
829
830 let group_y_ii = promote(group_y_i.clone(), alice, charlie, Access::read()).unwrap();
832
833 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::pull()).unwrap();
835
836 let group_y = merge(group_y_i, group_y_ii);
838
839 let charlie_state = group_y.members.get(&charlie).unwrap();
840
841 assert!(charlie_state.access_counter == 2);
843
844 assert!(charlie_state.is_puller());
846 }
847
848 #[test]
849 fn merge_state_access() {
850 let alice = 0;
855 let bob = 1;
856 let charlie = 2;
857
858 let initial_members = [
859 (alice, <Access>::manage()),
860 (bob, Access::read()),
861 (charlie, Access::pull()),
862 ];
863
864 let group_y = create(&initial_members);
866
867 let group_y_i = promote(group_y.clone(), alice, charlie, Access::read()).unwrap();
869
870 let group_y_i = demote(group_y_i.clone(), alice, charlie, Access::pull()).unwrap();
872
873 let group_y_ii = modify(group_y.clone(), alice, charlie, Access::manage()).unwrap();
875
876 let group_y_ii = demote(group_y_ii.clone(), alice, charlie, Access::read()).unwrap();
878
879 let group_y = merge(group_y_i.clone(), group_y_ii.clone());
881
882 let charlie_state = group_y.members.get(&charlie).unwrap();
883
884 assert!(charlie_state.is_puller());
886 }
887}