Skip to main content

almost_enough/
or.rs

1//! Combinator for combining multiple stop sources.
2//!
3//! This module provides [`OrStop`], which combines two stop sources into one
4//! that stops when either source stops.
5//!
6//! # Example
7//!
8//! ```rust
9//! use almost_enough::{StopSource, OrStop, Stop};
10//!
11//! let source_a = StopSource::new();
12//! let source_b = StopSource::new();
13//!
14//! // Combine: stop if either stops
15//! let combined = OrStop::new(source_a.as_ref(), source_b.as_ref());
16//!
17//! assert!(!combined.should_stop());
18//!
19//! source_a.cancel();
20//! assert!(combined.should_stop());
21//! ```
22
23use crate::{Stop, StopReason};
24
25/// Combines two [`Stop`] implementations.
26///
27/// The combined stop will trigger when either source stops.
28///
29/// # Example
30///
31/// ```rust
32/// use almost_enough::{StopSource, OrStop, Stop};
33///
34/// let timeout_source = StopSource::new();
35/// let cancel_source = StopSource::new();
36///
37/// let combined = OrStop::new(timeout_source.as_ref(), cancel_source.as_ref());
38///
39/// // Not stopped yet
40/// assert!(!combined.should_stop());
41///
42/// // Either source can trigger stop
43/// cancel_source.cancel();
44/// assert!(combined.should_stop());
45/// ```
46#[derive(Debug, Clone, Copy)]
47pub struct OrStop<A, B> {
48    a: A,
49    b: B,
50}
51
52impl<A, B> OrStop<A, B> {
53    /// Create a new combined stop that triggers when either source stops.
54    #[inline]
55    pub fn new(a: A, b: B) -> Self {
56        Self { a, b }
57    }
58
59    /// Get a reference to the first stop source.
60    #[inline]
61    pub fn first(&self) -> &A {
62        &self.a
63    }
64
65    /// Get a reference to the second stop source.
66    #[inline]
67    pub fn second(&self) -> &B {
68        &self.b
69    }
70
71    /// Decompose into the two inner stop sources.
72    #[inline]
73    pub fn into_inner(self) -> (A, B) {
74        (self.a, self.b)
75    }
76}
77
78impl<A: Stop, B: Stop> Stop for OrStop<A, B> {
79    #[inline]
80    fn check(&self) -> Result<(), StopReason> {
81        self.a.check()?;
82        self.b.check()
83    }
84
85    #[inline]
86    fn should_stop(&self) -> bool {
87        self.a.should_stop() || self.b.should_stop()
88    }
89
90    /// Returns `false` if neither half may stop (e.g., both are
91    /// `Unstoppable`).
92    #[inline]
93    fn may_stop(&self) -> bool {
94        self.a.may_stop() || self.b.may_stop()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::{StopSource, Unstoppable};
102
103    #[test]
104    fn or_stop_neither() {
105        let a = StopSource::new();
106        let b = StopSource::new();
107        let combined = OrStop::new(a.as_ref(), b.as_ref());
108
109        assert!(!combined.should_stop());
110        assert!(combined.check().is_ok());
111    }
112
113    #[test]
114    fn or_stop_first() {
115        let a = StopSource::new();
116        let b = StopSource::new();
117        let combined = OrStop::new(a.as_ref(), b.as_ref());
118
119        a.cancel();
120
121        assert!(combined.should_stop());
122        assert_eq!(combined.check(), Err(StopReason::Cancelled));
123    }
124
125    #[test]
126    fn or_stop_second() {
127        let a = StopSource::new();
128        let b = StopSource::new();
129        let combined = OrStop::new(a.as_ref(), b.as_ref());
130
131        b.cancel();
132
133        assert!(combined.should_stop());
134        assert_eq!(combined.check(), Err(StopReason::Cancelled));
135    }
136
137    #[test]
138    fn or_stop_both() {
139        let a = StopSource::new();
140        let b = StopSource::new();
141        let combined = OrStop::new(a.as_ref(), b.as_ref());
142
143        a.cancel();
144        b.cancel();
145
146        assert!(combined.should_stop());
147    }
148
149    #[test]
150    fn or_stop_chain() {
151        let a = StopSource::new();
152        let b = StopSource::new();
153        let c = StopSource::new();
154
155        let combined = OrStop::new(OrStop::new(a.as_ref(), b.as_ref()), c.as_ref());
156
157        assert!(!combined.should_stop());
158
159        c.cancel();
160        assert!(combined.should_stop());
161    }
162
163    #[test]
164    fn or_stop_with_unstoppable() {
165        let source = StopSource::new();
166        let combined = OrStop::new(Unstoppable, source.as_ref());
167
168        assert!(!combined.should_stop());
169
170        source.cancel();
171        assert!(combined.should_stop());
172    }
173
174    #[test]
175    fn or_stop_is_send_sync() {
176        fn assert_send_sync<T: Send + Sync>() {}
177        assert_send_sync::<OrStop<crate::StopRef<'_>, crate::StopRef<'_>>>();
178    }
179
180    #[test]
181    fn or_stop_accessors() {
182        let a = StopSource::new();
183        let b = StopSource::new();
184        let combined = OrStop::new(a.as_ref(), b.as_ref());
185
186        assert!(!combined.first().should_stop());
187        assert!(!combined.second().should_stop());
188
189        a.cancel();
190        assert!(combined.first().should_stop());
191
192        let (first, second) = combined.into_inner();
193        assert!(first.should_stop());
194        assert!(!second.should_stop());
195    }
196
197    #[test]
198    fn or_stop_ref_is_copy() {
199        // OrStop<StopRef, StopRef> is Copy since StopRef is Copy
200        let a = StopSource::new();
201        let b = StopSource::new();
202        let combined = OrStop::new(a.as_ref(), b.as_ref());
203        let combined2 = combined; // Copy
204
205        a.cancel();
206        assert!(combined.should_stop()); // Original still usable
207        assert!(combined2.should_stop());
208    }
209
210    #[test]
211    fn or_stop_is_copy() {
212        let a = StopSource::new();
213        let b = StopSource::new();
214        let combined = OrStop::new(a.as_ref(), b.as_ref());
215        let combined2 = combined; // Copy
216        let _ = combined; // Still valid
217
218        assert!(!combined2.should_stop());
219    }
220
221    #[test]
222    fn may_stop_both_unstoppable() {
223        let combined = OrStop::new(Unstoppable, Unstoppable);
224        assert!(!combined.may_stop());
225    }
226
227    #[test]
228    fn may_stop_one_side() {
229        let source = StopSource::new();
230        let combined = OrStop::new(Unstoppable, source.as_ref());
231        assert!(combined.may_stop());
232    }
233
234    #[test]
235    fn may_stop_both_sides() {
236        let a = StopSource::new();
237        let b = StopSource::new();
238        let combined = OrStop::new(a.as_ref(), b.as_ref());
239        assert!(combined.may_stop());
240    }
241}