Skip to main content

enough/
reason.rs

1//! Stop reason type.
2
3use core::fmt;
4
5/// Why an operation was stopped.
6///
7/// This is returned from [`Stop::check()`](crate::Stop::check) when the
8/// operation should stop.
9///
10/// # Error Integration
11///
12/// Implement `From<StopReason>` for your error type to use `?` naturally:
13///
14/// ```rust
15/// use enough::StopReason;
16///
17/// #[derive(Debug)]
18/// enum MyError {
19///     Stopped(StopReason),
20///     Io(std::io::Error),
21/// }
22///
23/// impl From<StopReason> for MyError {
24///     fn from(r: StopReason) -> Self { MyError::Stopped(r) }
25/// }
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[non_exhaustive]
29pub enum StopReason {
30    /// Operation was explicitly cancelled.
31    ///
32    /// This typically means someone called `cancel()` on a cancellation source,
33    /// or a parent operation was cancelled.
34    Cancelled,
35
36    /// Operation exceeded its deadline.
37    ///
38    /// This means a timeout was set and the deadline passed before the
39    /// operation completed.
40    TimedOut,
41}
42
43impl StopReason {
44    /// Returns `true` if this is a transient condition that might succeed on retry.
45    ///
46    /// Currently only `TimedOut` is considered transient, as the operation might
47    /// succeed with a longer timeout or under less load.
48    ///
49    /// `Cancelled` is not transient - it represents an explicit decision to stop.
50    #[inline]
51    pub fn is_transient(&self) -> bool {
52        matches!(self, Self::TimedOut)
53    }
54
55    /// Returns `true` if this was an explicit cancellation.
56    #[inline]
57    pub fn is_cancelled(&self) -> bool {
58        matches!(self, Self::Cancelled)
59    }
60
61    /// Returns `true` if this was a timeout.
62    #[inline]
63    pub fn is_timed_out(&self) -> bool {
64        matches!(self, Self::TimedOut)
65    }
66}
67
68impl fmt::Display for StopReason {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Self::Cancelled => write!(f, "operation cancelled"),
72            Self::TimedOut => write!(f, "operation timed out"),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn stop_reason_display() {
83        extern crate alloc;
84        use alloc::format;
85        assert_eq!(format!("{}", StopReason::Cancelled), "operation cancelled");
86        assert_eq!(format!("{}", StopReason::TimedOut), "operation timed out");
87    }
88
89    #[test]
90    fn stop_reason_equality() {
91        assert_eq!(StopReason::Cancelled, StopReason::Cancelled);
92        assert_eq!(StopReason::TimedOut, StopReason::TimedOut);
93        assert_ne!(StopReason::Cancelled, StopReason::TimedOut);
94    }
95
96    #[test]
97    fn stop_reason_is_transient() {
98        assert!(!StopReason::Cancelled.is_transient());
99        assert!(StopReason::TimedOut.is_transient());
100    }
101
102    #[test]
103    fn stop_reason_copy() {
104        let a = StopReason::Cancelled;
105        let b = a; // Copy
106        assert_eq!(a, b);
107    }
108
109    #[test]
110    fn stop_reason_hash() {
111        use core::hash::{Hash, Hasher};
112
113        struct DummyHasher(u64);
114        impl Hasher for DummyHasher {
115            fn finish(&self) -> u64 {
116                self.0
117            }
118            fn write(&mut self, bytes: &[u8]) {
119                for &b in bytes {
120                    self.0 = self.0.wrapping_add(b as u64);
121                }
122            }
123        }
124
125        let mut h1 = DummyHasher(0);
126        let mut h2 = DummyHasher(0);
127        StopReason::Cancelled.hash(&mut h1);
128        StopReason::Cancelled.hash(&mut h2);
129        assert_eq!(h1.finish(), h2.finish());
130    }
131}