Skip to main content

made_core/value_objects/ceremony/
state_visit.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::DomainError;
4
5/// A durable entry into a state, independent of within-state repetition.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
7#[serde(try_from = "u32", into = "u32")]
8pub struct StateVisit(u32);
9
10impl StateVisit {
11    pub const FIRST: Self = Self(1);
12
13    pub fn new(value: u32) -> Result<Self, DomainError> {
14        if value == 0 {
15            return Err(DomainError::MustBeNonZero {
16                field: "state_visit",
17            });
18        }
19        Ok(Self(value))
20    }
21
22    pub fn next(self) -> Result<Self, DomainError> {
23        self.0
24            .checked_add(1)
25            .map(Self)
26            .ok_or(DomainError::InvariantViolated {
27                reason: "state visit coordinate exhausted",
28            })
29    }
30
31    #[must_use]
32    pub const fn get(self) -> u32 {
33        self.0
34    }
35
36    #[must_use]
37    pub const fn is_first(&self) -> bool {
38        self.0 == 1
39    }
40}
41
42impl Default for StateVisit {
43    fn default() -> Self {
44        Self::FIRST
45    }
46}
47impl TryFrom<u32> for StateVisit {
48    type Error = DomainError;
49    fn try_from(value: u32) -> Result<Self, Self::Error> {
50        Self::new(value)
51    }
52}
53impl From<StateVisit> for u32 {
54    fn from(value: StateVisit) -> Self {
55        value.0
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    #[test]
63    fn visits_refuse_zero_and_overflow() {
64        assert!(StateVisit::new(0).is_err());
65        assert!(serde_json::from_str::<StateVisit>("0").is_err());
66        assert!(StateVisit::new(u32::MAX).unwrap().next().is_err());
67        assert_eq!(StateVisit::FIRST.next().unwrap().get(), 2);
68        assert_eq!(serde_json::to_string(&StateVisit::FIRST).unwrap(), "1");
69    }
70}