1mod action;
6mod authority_graphs;
7pub(crate) mod crdt;
8#[cfg(any(test, feature = "test_utils"))]
9mod display;
10mod member;
11mod message;
12pub mod resolver;
13
14pub use action::GroupAction;
15pub(crate) use authority_graphs::AuthorityGraphs;
16pub(crate) use crdt::apply_action;
17pub use crdt::state::{GroupMembersState, GroupMembershipError, MemberState};
18pub use crdt::{
19 GroupCrdt, GroupCrdtError, GroupCrdtInnerError, GroupCrdtInnerState, GroupCrdtState,
20 StateChangeResult,
21};
22pub use member::GroupMember;
23pub use message::GroupControlMessage;
24
25use std::collections::HashSet;
26use std::fmt::Debug;
27use std::marker::PhantomData;
28
29use thiserror::Error;
30
31use crate::Access;
32use crate::traits::{
33 Conditions, GroupMembership, Groups as GroupsTrait, IdentityHandle, OperationId, Orderer,
34 Resolver,
35};
36
37#[derive(Debug, Error)]
38pub enum GroupsError<ID, OP, C, RS, ORD>
40where
41 ID: IdentityHandle,
42 OP: OperationId + Ord,
43 RS: Resolver<ID, OP, C, ORD::Operation>,
44 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
45{
46 #[error(transparent)]
47 Group(#[from] GroupCrdtError<ID, OP, C, RS, ORD>),
48
49 #[error("group must be created with at least one initial member")]
50 EmptyGroup,
51
52 #[error("actor {0} is already a member of group {1}")]
53 GroupMember(ID, ID),
54
55 #[error("actor {0} is not a member of group {1}")]
56 NotGroupMember(ID, ID),
57
58 #[error("action requires manager access but actor {0} is {1} in group {2}")]
59 InsufficientAccess(ID, Access<C>, ID),
60
61 #[error("actor {0} already has access level {1} in group {2}")]
62 SameAccessLevel(ID, Access<C>, ID),
63
64 #[error("state not found for group member {0} in group {1}")]
65 MemberNotFound(ID, ID),
66}
67
68pub struct Groups<ID, OP, C, RS, ORD>
83where
84 ID: IdentityHandle,
85 OP: OperationId + Ord,
86 C: Conditions,
87 RS: Resolver<ID, OP, C, ORD::Operation, State = GroupCrdtInnerState<ID, OP, C, ORD::Operation>>
88 + Debug,
89 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
90 ORD::Operation: Clone,
91{
92 my_id: ID,
93 y: Option<GroupCrdtState<ID, OP, C, ORD>>,
94 _phantom: PhantomData<RS>,
95}
96
97impl<ID, OP, C, RS, ORD> Groups<ID, OP, C, RS, ORD>
98where
99 ID: IdentityHandle,
100 OP: OperationId + Ord,
101 C: Conditions,
102 RS: Resolver<ID, OP, C, ORD::Operation, State = GroupCrdtInnerState<ID, OP, C, ORD::Operation>>
103 + Debug,
104 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
105 ORD::Operation: Clone,
106{
107 pub fn new(my_id: ID, y: GroupCrdtState<ID, OP, C, ORD>) -> Self {
111 Self {
112 my_id,
113 y: Some(y),
114 _phantom: PhantomData,
115 }
116 }
117
118 pub fn take_state(mut self) -> GroupCrdtState<ID, OP, C, ORD> {
120 self.y.take().expect("state object present")
121 }
122}
123
124impl<ID, OP, C, RS, ORD> GroupsTrait<ID, OP, C, ORD::Operation> for Groups<ID, OP, C, RS, ORD>
125where
126 ID: IdentityHandle,
127 OP: OperationId + Ord,
128 C: Conditions,
129 RS: Resolver<ID, OP, C, ORD::Operation, State = GroupCrdtInnerState<ID, OP, C, ORD::Operation>>
130 + Debug,
131 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
132 ORD::Operation: Clone,
133{
134 type Error = GroupsError<ID, OP, C, RS, ORD>;
135
136 fn create(
143 &mut self,
144 group_id: ID,
145 members: Vec<(GroupMember<ID>, Access<C>)>,
146 ) -> Result<ORD::Operation, Self::Error> {
147 let creator = (GroupMember::Individual(self.my_id), Access::manage());
149
150 let mut initial_members = Vec::new();
151 initial_members.push(creator);
152 initial_members.extend(members);
153
154 let action = GroupControlMessage {
155 group_id,
156 action: GroupAction::Create { initial_members },
157 };
158
159 let y = self.y.take().expect("state object present");
160 let (y_i, operation) = GroupCrdt::prepare(y, &action)?;
161 let y_ii = GroupCrdt::process(y_i, &operation)?;
162 let _ = self.y.insert(y_ii);
163
164 Ok(operation)
165 }
166
167 fn receive_from_remote(&mut self, remote_operation: ORD::Operation) -> Result<(), Self::Error> {
172 let y = self.y.take().expect("state object present");
174 let y_i = GroupCrdt::process(y, &remote_operation)?;
175 let _ = self.y.insert(y_i);
176
177 Ok(())
178 }
179
180 fn add(
185 &mut self,
186 group_id: ID,
187 adder: ID,
188 added: ID,
189 access: Access<C>,
190 ) -> Result<ORD::Operation, Self::Error> {
191 if !self.is_manager(group_id, adder)? {
192 let adder_access = self.access(group_id, adder)?;
193 return Err(GroupsError::InsufficientAccess(
194 adder,
195 adder_access,
196 group_id,
197 ));
198 }
199
200 if self.is_member(group_id, added)? {
201 return Err(GroupsError::GroupMember(added, group_id));
202 }
203
204 let action = GroupControlMessage {
205 group_id,
206 action: GroupAction::Add {
207 member: GroupMember::Individual(added),
208 access,
209 },
210 };
211
212 let y = self.y.take().expect("state object present");
213 let (y_i, operation) = GroupCrdt::prepare(y, &action)?;
214 let y_ii = GroupCrdt::process(y_i, &operation)?;
215 let _ = self.y.insert(y_ii);
216
217 Ok(operation)
218 }
219
220 fn remove(
229 &mut self,
230 group_id: ID,
231 remover: ID,
232 removed: ID,
233 ) -> Result<ORD::Operation, Self::Error> {
234 if !self.is_manager(group_id, remover)? {
235 let remover_access = self.access(group_id, remover)?;
236 return Err(GroupsError::InsufficientAccess(
237 remover,
238 remover_access,
239 group_id,
240 ));
241 }
242
243 if !self.is_member(group_id, removed)? {
244 return Err(GroupsError::NotGroupMember(removed, group_id));
245 }
246
247 let action = GroupControlMessage {
248 group_id,
249 action: GroupAction::Remove {
250 member: GroupMember::Individual(removed),
251 },
252 };
253
254 let y = self.y.take().expect("state object present");
255 let (y_i, operation) = GroupCrdt::prepare(y, &action)?;
256 let y_ii = GroupCrdt::process(y_i, &operation)?;
257 let _ = self.y.insert(y_ii);
258
259 Ok(operation)
260 }
261
262 fn promote(
269 &mut self,
270 group_id: ID,
271 promoter: ID,
272 promoted: ID,
273 access: Access<C>,
274 ) -> Result<ORD::Operation, Self::Error> {
275 if !self.is_manager(group_id, promoter)? {
276 let promoter_access = self.access(group_id, promoter)?;
277 return Err(GroupsError::InsufficientAccess(
278 promoter,
279 promoter_access,
280 group_id,
281 ));
282 }
283
284 if !self.is_member(group_id, promoted)? {
285 return Err(GroupsError::NotGroupMember(promoted, group_id));
286 }
287
288 if self.access(group_id, promoted)? == access {
290 return Err(GroupsError::SameAccessLevel(promoted, access, group_id));
291 }
292
293 let action = GroupControlMessage {
294 group_id,
295 action: GroupAction::Promote {
296 member: GroupMember::Individual(promoted),
297 access,
298 },
299 };
300
301 let y = self.y.take().expect("state object present");
302 let (y_i, operation) = GroupCrdt::prepare(y, &action)?;
303 let y_ii = GroupCrdt::process(y_i, &operation)?;
304 let _ = self.y.insert(y_ii);
305
306 Ok(operation)
307 }
308
309 fn demote(
316 &mut self,
317 group_id: ID,
318 demoter: ID,
319 demoted: ID,
320 access: Access<C>,
321 ) -> Result<ORD::Operation, Self::Error> {
322 if !self.is_manager(group_id, demoter)? {
323 let demoter_access = self.access(group_id, demoter)?;
324 return Err(GroupsError::InsufficientAccess(
325 demoter,
326 demoter_access,
327 group_id,
328 ));
329 }
330
331 if !self.is_member(group_id, demoted)? {
332 return Err(GroupsError::NotGroupMember(demoted, group_id));
333 }
334
335 if self.access(group_id, demoted)? == access {
337 return Err(GroupsError::SameAccessLevel(demoted, access, group_id));
338 }
339
340 let action = GroupControlMessage {
341 group_id,
342 action: GroupAction::Demote {
343 member: GroupMember::Individual(demoted),
344 access,
345 },
346 };
347
348 let y = self.y.take().expect("state object present");
349 let (y_i, operation) = GroupCrdt::prepare(y, &action)?;
350 let y_ii = GroupCrdt::process(y_i, &operation)?;
351 let _ = self.y.insert(y_ii);
352
353 Ok(operation)
354 }
355}
356
357impl<ID, OP, C, RS, ORD> GroupMembership<ID, OP, C> for Groups<ID, OP, C, RS, ORD>
358where
359 ID: IdentityHandle,
360 OP: OperationId + Ord,
361 C: Conditions,
362 RS: Resolver<ID, OP, C, ORD::Operation, State = GroupCrdtInnerState<ID, OP, C, ORD::Operation>>
363 + Debug,
364 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
365 ORD::Operation: Clone,
366{
367 type Error = GroupsError<ID, OP, C, RS, ORD>;
368
369 fn access(&self, group_id: ID, member: ID) -> Result<Access<C>, Self::Error> {
373 let Some(y) = &self.y else { unreachable!() };
374
375 let member_state = y
376 .members(group_id)
377 .into_iter()
378 .find(|(member_id, _state)| member_id == &member);
379
380 if let Some(state) = member_state {
381 let access = state.1.to_owned();
382
383 Ok(access)
384 } else {
385 Err(GroupsError::MemberNotFound(group_id, member))
386 }
387 }
388
389 fn member_ids(&self, group_id: ID) -> Result<HashSet<ID>, Self::Error> {
391 let Some(y) = &self.y else { unreachable!() };
392
393 let member_ids = y
394 .members(group_id)
395 .into_iter()
396 .map(|(member_id, _state)| member_id)
397 .collect();
398
399 Ok(member_ids)
400 }
401
402 fn is_member(&self, group_id: ID, member: ID) -> Result<bool, Self::Error> {
404 let Some(y) = &self.y else { unreachable!() };
405
406 let member_state = y
407 .members(group_id)
408 .into_iter()
409 .find(|(member_id, _state)| member_id == &member);
410
411 let is_member = member_state.is_some();
412
413 Ok(is_member)
414 }
415
416 fn is_puller(&self, group_id: ID, member: ID) -> Result<bool, Self::Error> {
418 Ok(self.access(group_id, member)?.is_pull())
419 }
420
421 fn is_reader(&self, group_id: ID, member: ID) -> Result<bool, Self::Error> {
423 Ok(self.access(group_id, member)?.is_read())
424 }
425
426 fn is_writer(&self, group_id: ID, member: ID) -> Result<bool, Self::Error> {
428 Ok(self.access(group_id, member)?.is_write())
429 }
430
431 fn is_manager(&self, group_id: ID, member: ID) -> Result<bool, Self::Error> {
433 Ok(self.access(group_id, member)?.is_manage())
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use std::cell::RefCell;
440 use std::rc::Rc;
441
442 use rand::SeedableRng;
443 use rand::rngs::StdRng;
444
445 use crate::test_utils::partial_ord::{TestGroup, TestOrderer};
446 use crate::test_utils::{Conditions, MemberId, MessageId, TestOperation, TestResolver};
447
448 use super::*;
449
450 const ALICE: char = 'A';
451 const BOB: char = 'B';
452 const CLAIRE: char = 'C';
453 const DAVE: char = 'D';
454
455 const MY_ID: char = 'T';
456 const GROUP_ID: char = 'G';
457
458 pub type TestGroups = Groups<MemberId, MessageId, Conditions, TestResolver, TestOrderer>;
459
460 fn setup() -> (TestGroups, TestOperation) {
462 let initial_members = [
463 (GroupMember::Individual(ALICE), Access::manage()),
464 (GroupMember::Individual(BOB), Access::read()),
465 (GroupMember::Individual(CLAIRE), Access::write()),
466 ]
467 .to_vec();
468
469 let auth_heads_ref = Rc::new(RefCell::new(vec![]));
470 let orderer_y = TestOrderer::init(MY_ID, auth_heads_ref.clone(), StdRng::from_os_rng());
471 let y = TestGroup::init(orderer_y);
472 let mut groups = TestGroups::new(MY_ID, y);
473 let operation = groups.create(GROUP_ID, initial_members).unwrap();
474
475 (groups, operation)
476 }
477
478 #[test]
482 fn add_validation_errors() {
483 let (mut groups, _) = setup();
484
485 let _expected_access = <Access>::read();
487 assert!(matches!(
488 groups.add(GROUP_ID, BOB, DAVE, Access::pull()),
489 Err(GroupsError::InsufficientAccess(
490 BOB,
491 _expected_access,
492 GROUP_ID
493 ))
494 ));
495
496 assert!(matches!(
498 groups.add(GROUP_ID, ALICE, CLAIRE, Access::pull()),
499 Err(GroupsError::GroupMember(CLAIRE, GROUP_ID))
500 ));
501 }
502
503 #[test]
504 fn remove_validation_errors() {
505 let (mut groups, _) = setup();
506
507 let _expected_access = <Access>::read();
509 assert!(matches!(
510 groups.remove(GROUP_ID, BOB, CLAIRE),
511 Err(GroupsError::InsufficientAccess(
512 BOB,
513 _expected_access,
514 GROUP_ID
515 ))
516 ));
517
518 let err = groups.remove(GROUP_ID, ALICE, DAVE);
520 assert!(
521 matches!(err, Err(GroupsError::NotGroupMember(DAVE, GROUP_ID))),
522 "{err:?}"
523 );
524 }
525
526 #[test]
527 fn promote_validation_errors() {
528 let (mut groups, _) = setup();
529
530 let _expected_access = <Access>::read();
532 assert!(matches!(
533 groups.promote(GROUP_ID, BOB, CLAIRE, Access::manage()),
534 Err(GroupsError::InsufficientAccess(
535 BOB,
536 _expected_access,
537 GROUP_ID
538 ))
539 ));
540
541 assert!(matches!(
543 groups.promote(GROUP_ID, ALICE, DAVE, Access::read()),
544 Err(GroupsError::NotGroupMember(DAVE, GROUP_ID))
545 ));
546
547 let _expected_access = <Access>::read();
549 assert!(matches!(
550 groups.promote(GROUP_ID, ALICE, BOB, Access::read()),
551 Err(GroupsError::SameAccessLevel(
552 BOB,
553 _expected_access,
554 GROUP_ID
555 ))
556 ));
557 }
558
559 #[test]
560 fn demote_validation_errors() {
561 let (mut groups, _) = setup();
562
563 let _expected_access = <Access>::read();
565 assert!(matches!(
566 groups.demote(GROUP_ID, BOB, CLAIRE, Access::pull()),
567 Err(GroupsError::InsufficientAccess(
568 BOB,
569 _expected_access,
570 GROUP_ID
571 ))
572 ));
573
574 assert!(matches!(
576 groups.demote(GROUP_ID, ALICE, DAVE, Access::read()),
577 Err(GroupsError::NotGroupMember(DAVE, GROUP_ID))
578 ));
579
580 let _expected_access = <Access>::read();
582 assert!(matches!(
583 groups.demote(GROUP_ID, ALICE, BOB, Access::read()),
584 Err(GroupsError::SameAccessLevel(
585 BOB,
586 _expected_access,
587 GROUP_ID
588 ))
589 ));
590 }
591}