1mod action;
6pub(crate) mod crdt;
7#[cfg(any(test, feature = "test_utils"))]
8mod display;
9mod member;
10mod message;
11pub mod resolver;
12
13pub use action::GroupAction;
14pub use crdt::state::{GroupMembersState, GroupMembershipError, MemberState};
15pub use crdt::{GroupCrdt, GroupCrdtError, GroupCrdtState, StateChangeResult};
16pub use member::GroupMember;
17pub use message::GroupControlMessage;
18
19use std::fmt::{Debug, Display};
20use std::marker::PhantomData;
21
22use thiserror::Error;
23
24use crate::Access;
25use crate::traits::{
26 Group as GroupTrait, GroupMembership, GroupStore, IdentityHandle, Operation, OperationId,
27 Orderer, Resolver,
28};
29
30#[derive(Debug, Error)]
31pub enum GroupError<ID, OP, C, RS, ORD, GS>
33where
34 ID: IdentityHandle,
35 OP: OperationId + Ord,
36 RS: Resolver<ID, OP, C, ORD, GS>,
37 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
38 GS: GroupStore<ID, OP, C, RS, ORD>,
39{
40 #[error(transparent)]
41 Group(#[from] GroupCrdtError<ID, OP, C, RS, ORD, GS>),
42
43 #[error("group must be created with at least one initial member")]
44 EmptyGroup,
45
46 #[error("actor {0} is already a member of group {1}")]
47 GroupMember(ID, ID),
48
49 #[error("actor {0} is not a member of group {1}")]
50 NotGroupMember(ID, ID),
51
52 #[error("action requires manager access but actor {0} is {1} in group {2}")]
53 InsufficientAccess(ID, Access<C>, ID),
54
55 #[error("actor {0} already has access level {1} in group {2}")]
56 SameAccessLevel(ID, Access<C>, ID),
57}
58
59pub struct Group<ID, OP, C, RS, ORD, GS>
74where
75 ID: IdentityHandle,
76 OP: OperationId + Ord,
77 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
78 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
79 GS: GroupStore<ID, OP, C, RS, ORD>,
80{
81 my_id: ID,
83
84 store: GS,
86
87 orderer: ORD::State,
89
90 _phantom: PhantomData<(ID, OP, C, RS, ORD, GS)>,
91}
92
93impl<ID, OP, C, RS, ORD, GS> Group<ID, OP, C, RS, ORD, GS>
94where
95 ID: IdentityHandle,
96 OP: OperationId + Ord,
97 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
98 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
99 GS: GroupStore<ID, OP, C, RS, ORD>,
100{
101 pub fn init(my_id: ID, store: GS, orderer: ORD::State) -> Self {
105 Self {
106 _phantom: PhantomData,
107 my_id,
108 store,
109 orderer,
110 }
111 }
112}
113
114impl<ID, OP, C, RS, ORD, GS> GroupTrait<ID, OP, C, ORD> for Group<ID, OP, C, RS, ORD, GS>
115where
116 ID: IdentityHandle + Display,
117 OP: OperationId + Ord + Display,
118 C: Clone + Debug + PartialEq + PartialOrd,
119 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
120 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Clone + Debug,
121 ORD::Operation: Clone,
122 ORD::State: Clone,
123 GS: GroupStore<ID, OP, C, RS, ORD> + Clone + Debug,
124{
125 type State = GroupCrdtState<ID, OP, C, RS, ORD, GS>;
126 type Action = GroupControlMessage<ID, C>;
127 type Error = GroupError<ID, OP, C, RS, ORD, GS>;
128
129 fn create(
136 &self,
137 group_id: ID,
138 members: Vec<(GroupMember<ID>, Access<C>)>,
139 ) -> Result<(Self::State, ORD::Operation), Self::Error> {
140 let creator = (GroupMember::Individual(self.my_id), Access::manage());
142
143 let mut initial_members = Vec::new();
144 initial_members.push(creator);
145 initial_members.extend(members);
146
147 let y = GroupCrdtState::new(
148 self.my_id,
149 group_id,
150 self.store.clone(),
151 self.orderer.clone(),
152 );
153
154 let action = GroupControlMessage {
155 group_id: y.group_id,
156 action: GroupAction::Create { initial_members },
157 };
158
159 let (y, operation) = GroupCrdt::prepare(y, &action)?;
160 let y = GroupCrdt::process(y, &operation)?;
161
162 Ok((y, operation))
163 }
164
165 fn create_from_remote(
167 &self,
168 remote_operation: ORD::Operation,
169 ) -> Result<Self::State, Self::Error> {
170 let group_id = remote_operation.payload().group_id();
171
172 let y = GroupCrdtState::new(
173 self.my_id,
174 group_id,
175 self.store.clone(),
176 self.orderer.clone(),
177 );
178
179 let y = GroupCrdt::process(y, &remote_operation)?;
180
181 Ok(y)
182 }
183
184 fn receive_from_remote(
189 y: Self::State,
190 remote_operation: ORD::Operation,
191 ) -> Result<Self::State, Self::Error> {
192 let y = GroupCrdt::process(y, &remote_operation)?;
194
195 Ok(y)
196 }
197
198 fn add(
203 y: Self::State,
204 adder: ID,
205 added: ID,
206 access: Access<C>,
207 ) -> Result<(Self::State, ORD::Operation), Self::Error> {
208 if !Self::State::is_manager(&y, &adder)? {
209 let adder_access = Self::State::access(&y, &adder)?;
210 return Err(GroupError::InsufficientAccess(
211 adder,
212 adder_access,
213 y.group_id,
214 ));
215 }
216
217 if Self::State::is_member(&y, &added)? {
218 return Err(GroupError::GroupMember(added, y.group_id));
219 }
220
221 let action = GroupControlMessage {
222 group_id: y.group_id,
223 action: GroupAction::Add {
224 member: GroupMember::Individual(added),
225 access,
226 },
227 };
228
229 let (y, operation) = GroupCrdt::prepare(y, &action)?;
230 let y = GroupCrdt::process(y, &operation)?;
231
232 Ok((y, operation))
233 }
234
235 fn remove(
244 y: Self::State,
245 remover: ID,
246 removed: ID,
247 ) -> Result<(Self::State, ORD::Operation), Self::Error> {
248 if !Self::State::is_manager(&y, &remover)? {
249 let remover_access = Self::State::access(&y, &remover)?;
250 return Err(GroupError::InsufficientAccess(
251 remover,
252 remover_access,
253 y.group_id,
254 ));
255 }
256
257 if !Self::State::is_member(&y, &removed)? {
258 return Err(GroupError::NotGroupMember(removed, y.group_id));
259 }
260
261 let action = GroupControlMessage {
262 group_id: y.group_id,
263 action: GroupAction::Remove {
264 member: GroupMember::Individual(removed),
265 },
266 };
267
268 let (y, operation) = GroupCrdt::prepare(y, &action)?;
269 let y = GroupCrdt::process(y, &operation)?;
270
271 Ok((y, operation))
272 }
273
274 fn promote(
281 y: Self::State,
282 promoter: ID,
283 promoted: ID,
284 access: Access<C>,
285 ) -> Result<(Self::State, ORD::Operation), Self::Error> {
286 if !Self::State::is_manager(&y, &promoter)? {
287 let promoter_access = Self::State::access(&y, &promoter)?;
288 return Err(GroupError::InsufficientAccess(
289 promoter,
290 promoter_access,
291 y.group_id,
292 ));
293 }
294
295 if !Self::State::is_member(&y, &promoted)? {
296 return Err(GroupError::NotGroupMember(promoted, y.group_id));
297 }
298
299 if Self::State::access(&y, &promoted)? == access {
301 return Err(GroupError::SameAccessLevel(promoted, access, y.group_id));
302 }
303
304 let action = GroupControlMessage {
305 group_id: y.group_id,
306 action: GroupAction::Promote {
307 member: GroupMember::Individual(promoted),
308 access,
309 },
310 };
311
312 let (y, operation) = GroupCrdt::prepare(y, &action)?;
313 let y = GroupCrdt::process(y, &operation)?;
314
315 Ok((y, operation))
316 }
317
318 fn demote(
325 y: Self::State,
326 demoter: ID,
327 demoted: ID,
328 access: Access<C>,
329 ) -> Result<(Self::State, ORD::Operation), Self::Error> {
330 if !Self::State::is_manager(&y, &demoter)? {
331 let demoter_access = Self::State::access(&y, &demoter)?;
332 return Err(GroupError::InsufficientAccess(
333 demoter,
334 demoter_access,
335 y.group_id,
336 ));
337 }
338
339 if !Self::State::is_member(&y, &demoted)? {
340 return Err(GroupError::NotGroupMember(demoted, y.group_id));
341 }
342
343 if Self::State::access(&y, &demoted)? == access {
344 return Err(GroupError::SameAccessLevel(demoted, access, y.group_id));
345 }
346
347 let action = GroupControlMessage {
348 group_id: y.group_id,
349 action: GroupAction::Demote {
350 member: GroupMember::Individual(demoted),
351 access,
352 },
353 };
354
355 let (y, operation) = GroupCrdt::prepare(y, &action)?;
356 let y = GroupCrdt::process(y, &operation)?;
357
358 Ok((y, operation))
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use rand::SeedableRng;
365 use rand::rngs::StdRng;
366
367 use crate::test_utils::{TestGroupState, TestGroupStore, TestOrdererState};
368
369 use super::*;
370
371 const ALICE: char = 'A';
372 const BOB: char = 'B';
373 const CLAIRE: char = 'C';
374 const DAVE: char = 'D';
375
376 const MY_ID: char = 'T';
377 const GROUP_ID: char = 'G';
378
379 fn setup() -> TestGroupState {
381 let rng = StdRng::from_os_rng();
382
383 let store = TestGroupStore::default();
384 let orderer = TestOrdererState::new(MY_ID, store.clone(), rng);
385
386 let initial_members = [
387 (GroupMember::Individual(ALICE), Access::manage()),
388 (GroupMember::Individual(BOB), Access::read()),
389 (GroupMember::Individual(CLAIRE), Access::write()),
390 ]
391 .to_vec();
392
393 let group = Group::init(MY_ID, store, orderer);
394
395 let (group_y, _operation) = group.create(GROUP_ID, initial_members).unwrap();
396
397 group_y
398 }
399
400 #[test]
404 fn add_validation_errors() {
405 let y = setup();
406
407 let _expected_access = <Access>::read();
409 assert!(matches!(
410 Group::add(y.clone(), BOB, DAVE, Access::pull()),
411 Err(GroupError::InsufficientAccess(
412 BOB,
413 _expected_access,
414 GROUP_ID
415 ))
416 ));
417
418 assert!(matches!(
420 Group::add(y, ALICE, CLAIRE, Access::pull()),
421 Err(GroupError::GroupMember(CLAIRE, GROUP_ID))
422 ));
423 }
424
425 #[test]
426 fn remove_validation_errors() {
427 let y = setup();
428
429 let _expected_access = <Access>::read();
431 assert!(matches!(
432 Group::remove(y.clone(), BOB, CLAIRE),
433 Err(GroupError::InsufficientAccess(
434 BOB,
435 _expected_access,
436 GROUP_ID
437 ))
438 ));
439
440 assert!(matches!(
442 Group::remove(y, ALICE, DAVE),
443 Err(GroupError::NotGroupMember(DAVE, GROUP_ID))
444 ));
445 }
446
447 #[test]
448 fn promote_validation_errors() {
449 let y = setup();
450
451 let _expected_access = <Access>::read();
453 assert!(matches!(
454 Group::promote(y.clone(), BOB, CLAIRE, Access::manage()),
455 Err(GroupError::InsufficientAccess(
456 BOB,
457 _expected_access,
458 GROUP_ID
459 ))
460 ));
461
462 assert!(matches!(
464 Group::promote(y.clone(), ALICE, DAVE, Access::read()),
465 Err(GroupError::NotGroupMember(DAVE, GROUP_ID))
466 ));
467
468 let _expected_access = <Access>::read();
470 assert!(matches!(
471 Group::promote(y, ALICE, BOB, Access::read()),
472 Err(GroupError::SameAccessLevel(BOB, _expected_access, GROUP_ID))
473 ));
474 }
475
476 #[test]
477 fn demote_validation_errors() {
478 let y = setup();
479
480 let _expected_access = <Access>::read();
482 assert!(matches!(
483 Group::demote(y.clone(), BOB, CLAIRE, Access::pull()),
484 Err(GroupError::InsufficientAccess(
485 BOB,
486 _expected_access,
487 GROUP_ID
488 ))
489 ));
490
491 assert!(matches!(
493 Group::demote(y.clone(), ALICE, DAVE, Access::read()),
494 Err(GroupError::NotGroupMember(DAVE, GROUP_ID))
495 ));
496
497 let _expected_access = <Access>::read();
499 assert!(matches!(
500 Group::demote(y, ALICE, BOB, Access::read()),
501 Err(GroupError::SameAccessLevel(BOB, _expected_access, GROUP_ID))
502 ));
503 }
504}