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    #[track_caller]
81    fn check(&self) -> Result<(), StopReason> {
82        self.a.check()?;
83        self.b.check()
84    }
85
86    #[inline]
87    #[track_caller]
88    fn should_stop(&self) -> bool {
89        self.a.should_stop() || self.b.should_stop()
90    }
91
92    /// Returns `false` if neither half may stop (e.g., both are
93    /// `Unstoppable`).
94    #[inline]
95    fn may_stop(&self) -> bool {
96        self.a.may_stop() || self.b.may_stop()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::{StopSource, Unstoppable};
104
105    #[test]
106    fn or_stop_neither() {
107        let a = StopSource::new();
108        let b = StopSource::new();
109        let combined = OrStop::new(a.as_ref(), b.as_ref());
110
111        assert!(!combined.should_stop());
112        assert!(combined.check().is_ok());
113    }
114
115    #[test]
116    fn or_stop_first() {
117        let a = StopSource::new();
118        let b = StopSource::new();
119        let combined = OrStop::new(a.as_ref(), b.as_ref());
120
121        a.cancel();
122
123        assert!(combined.should_stop());
124        assert_eq!(combined.check(), Err(StopReason::Cancelled));
125    }
126
127    #[test]
128    fn or_stop_second() {
129        let a = StopSource::new();
130        let b = StopSource::new();
131        let combined = OrStop::new(a.as_ref(), b.as_ref());
132
133        b.cancel();
134
135        assert!(combined.should_stop());
136        assert_eq!(combined.check(), Err(StopReason::Cancelled));
137    }
138
139    #[test]
140    fn or_stop_both() {
141        let a = StopSource::new();
142        let b = StopSource::new();
143        let combined = OrStop::new(a.as_ref(), b.as_ref());
144
145        a.cancel();
146        b.cancel();
147
148        assert!(combined.should_stop());
149    }
150
151    #[test]
152    fn or_stop_chain() {
153        let a = StopSource::new();
154        let b = StopSource::new();
155        let c = StopSource::new();
156
157        let combined = OrStop::new(OrStop::new(a.as_ref(), b.as_ref()), c.as_ref());
158
159        assert!(!combined.should_stop());
160
161        c.cancel();
162        assert!(combined.should_stop());
163    }
164
165    #[test]
166    fn or_stop_with_unstoppable() {
167        let source = StopSource::new();
168        let combined = OrStop::new(Unstoppable, source.as_ref());
169
170        assert!(!combined.should_stop());
171
172        source.cancel();
173        assert!(combined.should_stop());
174    }
175
176    #[test]
177    fn or_stop_is_send_sync() {
178        fn assert_send_sync<T: Send + Sync>() {}
179        assert_send_sync::<OrStop<crate::StopRef<'_>, crate::StopRef<'_>>>();
180    }
181
182    #[test]
183    fn or_stop_accessors() {
184        let a = StopSource::new();
185        let b = StopSource::new();
186        let combined = OrStop::new(a.as_ref(), b.as_ref());
187
188        assert!(!combined.first().should_stop());
189        assert!(!combined.second().should_stop());
190
191        a.cancel();
192        assert!(combined.first().should_stop());
193
194        let (first, second) = combined.into_inner();
195        assert!(first.should_stop());
196        assert!(!second.should_stop());
197    }
198
199    #[test]
200    fn or_stop_ref_is_copy() {
201        // OrStop<StopRef, StopRef> is Copy since StopRef is Copy
202        let a = StopSource::new();
203        let b = StopSource::new();
204        let combined = OrStop::new(a.as_ref(), b.as_ref());
205        let combined2 = combined; // Copy
206
207        a.cancel();
208        assert!(combined.should_stop()); // Original still usable
209        assert!(combined2.should_stop());
210    }
211
212    #[test]
213    fn or_stop_is_copy() {
214        let a = StopSource::new();
215        let b = StopSource::new();
216        let combined = OrStop::new(a.as_ref(), b.as_ref());
217        let combined2 = combined; // Copy
218        let _ = combined; // Still valid
219
220        assert!(!combined2.should_stop());
221    }
222
223    #[test]
224    fn may_stop_both_unstoppable() {
225        let combined = OrStop::new(Unstoppable, Unstoppable);
226        assert!(!combined.may_stop());
227    }
228
229    #[test]
230    fn may_stop_one_side() {
231        let source = StopSource::new();
232        let combined = OrStop::new(Unstoppable, source.as_ref());
233        assert!(combined.may_stop());
234    }
235
236    #[test]
237    fn may_stop_both_sides() {
238        let a = StopSource::new();
239        let b = StopSource::new();
240        let combined = OrStop::new(a.as_ref(), b.as_ref());
241        assert!(combined.may_stop());
242    }
243}