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
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::{Never, StopSource};
95
96    #[test]
97    fn or_stop_neither() {
98        let a = StopSource::new();
99        let b = StopSource::new();
100        let combined = OrStop::new(a.as_ref(), b.as_ref());
101
102        assert!(!combined.should_stop());
103        assert!(combined.check().is_ok());
104    }
105
106    #[test]
107    fn or_stop_first() {
108        let a = StopSource::new();
109        let b = StopSource::new();
110        let combined = OrStop::new(a.as_ref(), b.as_ref());
111
112        a.cancel();
113
114        assert!(combined.should_stop());
115        assert_eq!(combined.check(), Err(StopReason::Cancelled));
116    }
117
118    #[test]
119    fn or_stop_second() {
120        let a = StopSource::new();
121        let b = StopSource::new();
122        let combined = OrStop::new(a.as_ref(), b.as_ref());
123
124        b.cancel();
125
126        assert!(combined.should_stop());
127        assert_eq!(combined.check(), Err(StopReason::Cancelled));
128    }
129
130    #[test]
131    fn or_stop_both() {
132        let a = StopSource::new();
133        let b = StopSource::new();
134        let combined = OrStop::new(a.as_ref(), b.as_ref());
135
136        a.cancel();
137        b.cancel();
138
139        assert!(combined.should_stop());
140    }
141
142    #[test]
143    fn or_stop_chain() {
144        let a = StopSource::new();
145        let b = StopSource::new();
146        let c = StopSource::new();
147
148        let combined = OrStop::new(OrStop::new(a.as_ref(), b.as_ref()), c.as_ref());
149
150        assert!(!combined.should_stop());
151
152        c.cancel();
153        assert!(combined.should_stop());
154    }
155
156    #[test]
157    fn or_stop_with_never() {
158        let source = StopSource::new();
159        let combined = OrStop::new(Never, source.as_ref());
160
161        assert!(!combined.should_stop());
162
163        source.cancel();
164        assert!(combined.should_stop());
165    }
166
167    #[test]
168    fn or_stop_is_send_sync() {
169        fn assert_send_sync<T: Send + Sync>() {}
170        assert_send_sync::<OrStop<crate::StopRef<'_>, crate::StopRef<'_>>>();
171    }
172
173    #[test]
174    fn or_stop_accessors() {
175        let a = StopSource::new();
176        let b = StopSource::new();
177        let combined = OrStop::new(a.as_ref(), b.as_ref());
178
179        assert!(!combined.first().should_stop());
180        assert!(!combined.second().should_stop());
181
182        a.cancel();
183        assert!(combined.first().should_stop());
184
185        let (first, second) = combined.into_inner();
186        assert!(first.should_stop());
187        assert!(!second.should_stop());
188    }
189
190    #[test]
191    fn or_stop_is_clone() {
192        let a = StopSource::new();
193        let b = StopSource::new();
194        let combined = OrStop::new(a.as_ref(), b.as_ref());
195        let combined2 = combined.clone();
196
197        a.cancel();
198        assert!(combined.should_stop());
199        assert!(combined2.should_stop());
200    }
201
202    #[test]
203    fn or_stop_is_copy() {
204        let a = StopSource::new();
205        let b = StopSource::new();
206        let combined = OrStop::new(a.as_ref(), b.as_ref());
207        let combined2 = combined; // Copy
208        let _ = combined; // Still valid
209
210        assert!(!combined2.should_stop());
211    }
212}