Skip to main content

rust_elm/
cmd.rs

1use crate::effect::Effect;
2
3/// Commands returned from `update` — zero or more effects to run.
4#[derive(Debug, Clone, PartialEq, Default)]
5pub enum Cmd<M> {
6    #[default]
7    None,
8    Single(Effect<M>),
9    Batch(Vec<Cmd<M>>),
10}
11
12impl<M> Cmd<M> {
13    pub fn none() -> Self {
14        Self::None
15    }
16
17    pub fn single(effect: Effect<M>) -> Self {
18        Self::Single(effect)
19    }
20
21    pub fn batch(cmds: impl IntoIterator<Item = Cmd<M>>) -> Self {
22        let cmds: Vec<_> = cmds.into_iter().collect();
23        if cmds.is_empty() {
24            Self::None
25        } else if cmds.len() == 1 {
26            cmds.into_iter().next().unwrap()
27        } else {
28            Self::Batch(cmds)
29        }
30    }
31
32    pub fn map<N>(self, f: fn(M) -> N) -> Cmd<N>
33    where
34        M: Send + 'static,
35        N: Send + 'static,
36    {
37        match self {
38            Self::None => Cmd::None,
39            Self::Single(e) => {
40                let mapped = e.map(f);
41                if matches!(mapped, Effect::None) {
42                    Cmd::None
43                } else {
44                    Cmd::Single(mapped)
45                }
46            }
47            Self::Batch(cmds) => Cmd::Batch(cmds.into_iter().map(|c| c.map(f)).collect()),
48        }
49    }
50
51    pub fn into_effects(self) -> Vec<Effect<M>> {
52        match self {
53            Self::None => vec![],
54            Self::Single(e) => vec![e],
55            Self::Batch(cmds) => cmds.into_iter().flat_map(Cmd::into_effects).collect(),
56        }
57    }
58
59    pub fn is_none(&self) -> bool {
60        matches!(self, Self::None)
61    }
62
63    pub fn effect_count(&self) -> usize {
64        match self {
65            Self::None => 0,
66            Self::Single(_) => 1,
67            Self::Batch(cmds) => cmds.iter().map(|c| c.effect_count()).sum(),
68        }
69    }
70}
71
72#[cfg(test)]
73#[allow(dead_code)]
74mod tests {
75    use super::*;
76    use crate::effect::Effect;
77
78    #[test]
79    fn batch_flattens_and_map_preserves_structure() {
80        #[derive(Clone, Copy, PartialEq, Eq, Debug)]
81        enum Msg {
82            A,
83            B,
84        }
85
86        let cmd = Cmd::batch([
87            Cmd::single(Effect::<Msg>::none()),
88            Cmd::none(),
89            Cmd::single(Effect::<Msg>::none()),
90        ]);
91        assert_eq!(cmd.into_effects().len(), 2);
92
93        let mapped = Cmd::<Msg>::single(Effect::<Msg>::none()).map(|m| match m {
94            Msg::A => 1,
95            Msg::B => 2,
96        });
97        assert!(mapped.is_none());
98    }
99}