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(feature = "std")]
78impl std::error::Error for StopReason {}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn stop_reason_display() {
86        extern crate alloc;
87        use alloc::format;
88        assert_eq!(format!("{}", StopReason::Cancelled), "operation cancelled");
89        assert_eq!(format!("{}", StopReason::TimedOut), "operation timed out");
90    }
91
92    #[test]
93    fn stop_reason_equality() {
94        assert_eq!(StopReason::Cancelled, StopReason::Cancelled);
95        assert_eq!(StopReason::TimedOut, StopReason::TimedOut);
96        assert_ne!(StopReason::Cancelled, StopReason::TimedOut);
97    }
98
99    #[test]
100    fn stop_reason_is_transient() {
101        assert!(!StopReason::Cancelled.is_transient());
102        assert!(StopReason::TimedOut.is_transient());
103    }
104
105    #[test]
106    fn stop_reason_copy() {
107        let a = StopReason::Cancelled;
108        let b = a; // Copy
109        assert_eq!(a, b);
110    }
111
112    #[test]
113    fn stop_reason_hash() {
114        use core::hash::{Hash, Hasher};
115
116        struct DummyHasher(u64);
117        impl Hasher for DummyHasher {
118            fn finish(&self) -> u64 {
119                self.0
120            }
121            fn write(&mut self, bytes: &[u8]) {
122                for &b in bytes {
123                    self.0 = self.0.wrapping_add(b as u64);
124                }
125            }
126        }
127
128        let mut h1 = DummyHasher(0);
129        let mut h2 = DummyHasher(0);
130        StopReason::Cancelled.hash(&mut h1);
131        StopReason::Cancelled.hash(&mut h2);
132        assert_eq!(h1.finish(), h2.finish());
133    }
134}