cancel_this/triggers/
chain.rs

1use crate::{CancelNever, CancellationTrigger, DynamicCancellationTrigger};
2
3/// Implementation of [`CancellationTrigger`] which chains together several
4/// trigger implementations.
5///
6/// This is mostly used internally by [`crate::on_trigger`] to implement chaining of
7/// multiple cancellation scopes. However, it is still a normal [`CancellationTrigger`] and
8/// thus can be used to combine triggers manually as well.
9#[derive(Clone, Default)]
10pub struct CancelChain(Vec<DynamicCancellationTrigger>);
11
12impl CancellationTrigger for CancelChain {
13    fn is_cancelled(&self) -> bool {
14        // Should not really matter, but start checking from the "innermost" condition.
15        self.0.iter().rev().any(|t| t.is_cancelled())
16    }
17
18    fn type_name(&self) -> &'static str {
19        self.0
20            .iter()
21            .rev()
22            .find(|t| t.is_cancelled())
23            .map(|it| it.type_name())
24            .unwrap_or("CancelChain")
25    }
26}
27
28impl CancelChain {
29    /// Remove the first trigger in the chain.
30    pub fn pop(&mut self) -> Option<DynamicCancellationTrigger> {
31        self.0.pop()
32    }
33
34    /// Add a new cancellation trigger. The new chain starts with the given trigger
35    /// and continues with the already present ones.
36    pub fn push<T: CancellationTrigger + 'static>(&mut self, trigger: T) {
37        self.0.push(Box::new(trigger));
38    }
39
40    /// Make a copy of this trigger chain, but if the chain is empty or only has a single element,
41    /// replace it with a simplified trigger which does not need vector traversal.
42    pub fn clone_and_flatten(&self) -> DynamicCancellationTrigger {
43        if self.0.is_empty() {
44            Box::new(CancelNever)
45        } else if self.0.len() == 1 {
46            self.0[0].clone()
47        } else {
48            Box::new(self.clone())
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use crate::{CancelAtomic, CancelChain, CancelTimer, CancellationTrigger};
56    use std::time::Duration;
57
58    #[test]
59    fn chain_flattening() {
60        // Empty chain flattens to cancel never.
61        let mut chain = CancelChain::default();
62        assert_eq!(chain.type_name(), "CancelChain");
63        assert_eq!(chain.clone_and_flatten().type_name(), "CancelNever");
64
65        // Single element chain flattens to that element.
66        let trigger = CancelAtomic::new();
67        chain.push(trigger.clone());
68        assert_eq!(chain.type_name(), "CancelChain");
69        assert_eq!(chain.clone_and_flatten().type_name(), "CancelAtomic");
70
71        // Two-element chain flattens to chain.
72        let timer = CancelTimer::start(Duration::from_secs(1));
73        chain.push(timer);
74        assert_eq!(chain.type_name(), "CancelChain");
75        assert_eq!(chain.clone_and_flatten().type_name(), "CancelChain");
76
77        // Once canceled, the name resolves to the canceled trigger.
78        trigger.cancel();
79        assert_eq!(chain.type_name(), "CancelAtomic");
80        assert_eq!(chain.clone_and_flatten().type_name(), "CancelAtomic");
81    }
82}