Skip to main content

made_core/value_objects/audit/
global_position.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::DomainError;
4
5/// Where a sealed record sits in the order every stream shares.
6///
7/// Positions start at 1 and strictly increase across all streams in the
8/// order the store accepted the appends; the store assigns them inside
9/// the transaction that lands the records, so two appends can never
10/// claim the same one. A consumer that reads every stream — a
11/// publisher, a projection — keeps a position as its cursor instead of
12/// one sequence per ceremony.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct GlobalPosition(u64);
16
17impl GlobalPosition {
18    /// The position of the first record ever appended.
19    pub const FIRST: Self = Self(1);
20
21    pub fn new(value: u64) -> Result<Self, DomainError> {
22        if value == 0 {
23            return Err(DomainError::MustBeNonZero {
24                field: "global_position",
25            });
26        }
27        Ok(Self(value))
28    }
29
30    #[must_use]
31    pub fn value(self) -> u64 {
32        self.0
33    }
34
35    /// The position the next record will take. Saturates rather than
36    /// wrapping: a wrapped position would let a cursor replay the
37    /// beginning.
38    #[must_use]
39    pub fn next(self) -> Self {
40        Self(self.0.saturating_add(1))
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn zero_is_rejected() {
50        assert!(matches!(
51            GlobalPosition::new(0),
52            Err(DomainError::MustBeNonZero {
53                field: "global_position"
54            })
55        ));
56    }
57
58    #[test]
59    fn the_first_position_is_one_and_positions_advance_by_one() {
60        assert_eq!(GlobalPosition::FIRST.value(), 1);
61        assert_eq!(GlobalPosition::FIRST.next().value(), 2);
62        assert!(GlobalPosition::FIRST < GlobalPosition::FIRST.next());
63    }
64}