Skip to main content

made_core/value_objects/audit/
audit_sequence.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::DomainError;
4
5/// Position of a record within one ceremony's audit journal.
6///
7/// Sequences start at 1 and advance by exactly one. A gap is not a
8/// missing record to be tolerated: it is evidence that the journal was
9/// truncated.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct AuditSequence(u64);
13
14impl AuditSequence {
15    pub const FIRST: Self = Self(1);
16
17    pub fn new(value: u64) -> Result<Self, DomainError> {
18        if value == 0 {
19            return Err(DomainError::MustBeNonZero {
20                field: "audit_sequence",
21            });
22        }
23        Ok(Self(value))
24    }
25
26    #[must_use]
27    pub fn value(self) -> u64 {
28        self.0
29    }
30
31    /// The sequence that must follow this one.
32    ///
33    /// Saturates rather than wrapping: a wrapped sequence would let a
34    /// journal appear ordered while replaying earlier positions.
35    #[must_use]
36    pub fn next(self) -> Self {
37        Self(self.0.saturating_add(1))
38    }
39
40    #[must_use]
41    pub fn follows(self, previous: Self) -> bool {
42        self.0 == previous.0.saturating_add(1)
43    }
44
45    #[must_use]
46    pub fn is_first(self) -> bool {
47        self == Self::FIRST
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn zero_is_rejected() {
57        assert!(matches!(
58            AuditSequence::new(0),
59            Err(DomainError::MustBeNonZero {
60                field: "audit_sequence"
61            })
62        ));
63    }
64
65    #[test]
66    fn the_first_sequence_is_one() {
67        assert_eq!(AuditSequence::FIRST.value(), 1);
68        assert!(AuditSequence::FIRST.is_first());
69    }
70
71    #[test]
72    fn follows_only_accepts_the_immediate_successor() {
73        let first = AuditSequence::FIRST;
74
75        assert!(first.next().follows(first));
76        assert!(!AuditSequence::new(3).unwrap().follows(first));
77        assert!(!first.follows(first));
78    }
79}