Skip to main content

docspec_core/
depth.rs

1//! Saturating depth counter used by readers and writers to track nesting.
2
3/// A saturating `u32` counter for nesting-depth tracking.
4///
5/// All arithmetic uses saturating semantics: `inc()` saturates at `u32::MAX`
6/// (effectively unreachable for real documents), and `dec()` saturates at `0`.
7/// This prevents underflow when malformed input would otherwise cause it,
8/// in line with the project's fail-fast policy: a stuck-at-zero counter
9/// surfaces as an invalid-sequence error downstream rather than a panic.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub struct Depth(u32);
12
13impl Depth {
14    /// Saturating decrement by one. Calling on a zero value is a no-op.
15    #[inline]
16    pub fn dec(&mut self) {
17        self.0 = self.0.saturating_sub(1);
18    }
19
20    /// Returns the inner `u32` value.
21    #[inline]
22    #[must_use]
23    pub fn get(self) -> u32 {
24        self.0
25    }
26
27    /// Saturating increment by one. Calling at `u32::MAX` is a no-op.
28    #[inline]
29    pub fn inc(&mut self) {
30        self.0 = self.0.saturating_add(1);
31    }
32
33    /// Returns `true` when the depth is greater than zero.
34    #[inline]
35    #[must_use]
36    pub fn is_positive(self) -> bool {
37        self.0 > 0
38    }
39
40    /// Returns `true` when the depth is zero.
41    #[inline]
42    #[must_use]
43    pub fn is_zero(self) -> bool {
44        self.0 == 0
45    }
46
47    /// Resets the depth to zero. Used when a closing scope guarantees
48    /// the current nesting level returns to the outermost state.
49    #[inline]
50    pub fn reset(&mut self) {
51        self.0 = 0;
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn default_is_zero() {
61        let d = Depth::default();
62        assert_eq!(d.get(), 0);
63        assert!(d.is_zero());
64        assert!(!d.is_positive());
65    }
66
67    #[test]
68    fn inc_increases_by_one() {
69        let mut d = Depth::default();
70        d.inc();
71        assert_eq!(d.get(), 1);
72        assert!(d.is_positive());
73        assert!(!d.is_zero());
74    }
75
76    #[test]
77    fn dec_decreases_by_one() {
78        let mut d = Depth::default();
79        d.inc();
80        d.inc();
81        d.dec();
82        assert_eq!(d.get(), 1);
83    }
84
85    #[test]
86    fn dec_at_zero_does_not_panic_or_underflow() {
87        let mut d = Depth::default();
88        d.dec();
89        assert_eq!(d.get(), 0);
90        d.dec();
91        d.dec();
92        assert_eq!(d.get(), 0);
93    }
94
95    #[test]
96    fn inc_at_max_does_not_panic_or_overflow() {
97        let mut d = Depth(u32::MAX);
98        d.inc();
99        assert_eq!(d.get(), u32::MAX);
100    }
101
102    #[test]
103    fn reset_returns_to_zero() {
104        let mut d = Depth::default();
105        d.inc();
106        d.inc();
107        d.inc();
108        d.reset();
109        assert_eq!(d.get(), 0);
110        assert!(d.is_zero());
111    }
112}