Skip to main content

rust_elm/core/
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 mut cmds: Vec<_> = cmds.into_iter().collect();
23        match cmds.len() {
24            0 => Self::None,
25            1 => cmds.pop().map_or(Self::None, |single| single),
26            _ => Self::Batch(cmds),
27        }
28    }
29
30    pub fn map<N>(self, f: fn(M) -> N) -> Cmd<N>
31    where
32        M: Send + 'static,
33        N: Send + 'static,
34    {
35        match self {
36            Self::None => Cmd::None,
37            Self::Single(e) => {
38                let mapped = e.map(f);
39                if matches!(mapped, Effect::None) {
40                    Cmd::None
41                } else {
42                    Cmd::Single(mapped)
43                }
44            }
45            Self::Batch(cmds) => Cmd::Batch(cmds.into_iter().map(|c| c.map(f)).collect()),
46        }
47    }
48
49    pub fn into_effects(self) -> Vec<Effect<M>> {
50        match self {
51            Self::None => vec![],
52            Self::Single(e) => vec![e],
53            Self::Batch(cmds) => cmds.into_iter().flat_map(Cmd::into_effects).collect(),
54        }
55    }
56
57    pub fn is_none(&self) -> bool {
58        matches!(self, Self::None)
59    }
60
61    pub fn effect_count(&self) -> usize {
62        match self {
63            Self::None => 0,
64            Self::Single(_) => 1,
65            Self::Batch(cmds) => cmds.iter().map(|c| c.effect_count()).sum(),
66        }
67    }
68}
69
70#[cfg(test)]
71#[allow(dead_code)]
72mod tests {
73    use super::*;
74    use crate::effect::Effect;
75
76    #[test]
77    fn batch_flattens_and_map_preserves_structure() {
78        #[derive(Clone, Copy, PartialEq, Eq, Debug)]
79        enum Msg {
80            A,
81            B,
82        }
83
84        let cmd = Cmd::batch([
85            Cmd::single(Effect::<Msg>::none()),
86            Cmd::none(),
87            Cmd::single(Effect::<Msg>::none()),
88        ]);
89        assert_eq!(cmd.into_effects().len(), 2);
90
91        let mapped = Cmd::<Msg>::single(Effect::<Msg>::none()).map(|m| match m {
92            Msg::A => 1,
93            Msg::B => 2,
94        });
95        assert!(mapped.is_none());
96    }
97}