Skip to main content

rust_elm/core/
sub.rs

1use std::time::Duration;
2
3/// Subscription descriptions — interpreted in [`crate::subscription`] / [`crate::runtime`] (requires `runtime` feature).
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Sub<M> {
6    None,
7    Tick {
8        id: u64,
9        every: Duration,
10        produce: fn() -> M,
11    },
12    Stream {
13        id: u64,
14        name: &'static str,
15        every: Duration,
16        produce: fn() -> M,
17    },
18    WebSocket {
19        id: u64,
20        url: &'static str,
21        every: Duration,
22        produce: fn() -> M,
23    },
24    MapMsg {
25        inner: Box<Sub<()>>,
26        map: fn(()) -> M,
27    },
28    Batch(Vec<Sub<M>>),
29}
30
31impl<M> Sub<M> {
32    pub fn none() -> Self {
33        Self::None
34    }
35
36    pub fn tick(id: u64, every: Duration, produce: fn() -> M) -> Self {
37        Self::Tick { id, every, produce }
38    }
39
40    pub fn stream(id: u64, name: &'static str, every: Duration, produce: fn() -> M) -> Self {
41        Self::Stream {
42            id,
43            name,
44            every,
45            produce,
46        }
47    }
48
49    pub fn websocket(id: u64, url: &'static str, every: Duration, produce: fn() -> M) -> Self {
50        Self::WebSocket {
51            id,
52            url,
53            every,
54            produce,
55        }
56    }
57
58    /// Wrap a `Sub<()>` and map each firing into `M` (Elm `Sub.map`).
59    pub fn map_msg(inner: Sub<()>, map: fn(()) -> M) -> Self {
60        Self::MapMsg {
61            inner: Box::new(inner),
62            map,
63        }
64    }
65
66    /// Map batched subscription outputs. For leaf ticks/streams, use explicit `produce` fns or
67    /// [`Self::map_msg`] at the target action type.
68    pub fn map<N>(self, _f: fn(M) -> N) -> Sub<N> {
69        match self {
70            Self::None => Sub::None,
71            Self::Batch(items) => Sub::Batch(items.into_iter().map(|s| s.map(_f)).collect()),
72            Self::MapMsg { .. } | Self::Tick { .. } | Self::Stream { .. } | Self::WebSocket { .. } => {
73                Sub::None
74            }
75        }
76    }
77
78    pub fn batch(subs: impl IntoIterator<Item = Sub<M>>) -> Self {
79        let mut subs: Vec<_> = subs.into_iter().collect();
80        match subs.len() {
81            0 => Self::None,
82            1 => subs.pop().map_or(Self::None, |single| single),
83            _ => Self::Batch(subs),
84        }
85    }
86
87    pub fn id(&self) -> Option<u64> {
88        match self {
89            Self::None => None,
90            Self::Tick { id, .. } | Self::Stream { id, .. } | Self::WebSocket { id, .. } => {
91                Some(*id)
92            }
93            Self::MapMsg { inner, .. } => inner.id(),
94            Self::Batch(items) => items.first().and_then(Sub::id),
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    fn zero() -> i32 {
104        0
105    }
106
107    fn unit() {}
108
109    fn ping(_: ()) -> i32 {
110        7
111    }
112
113    #[test]
114    fn tick_has_stable_id() {
115        let sub = Sub::<i32>::tick(42, Duration::from_secs(1), zero);
116        assert_eq!(sub.id(), Some(42));
117    }
118
119    #[test]
120    fn map_msg_wraps_unit_tick() {
121        let sub = Sub::map_msg(Sub::tick(1, Duration::from_millis(1), unit), ping);
122        assert_eq!(sub.id(), Some(1));
123        let Sub::MapMsg { map, .. } = sub else {
124            panic!("expected map_msg");
125        };
126        assert_eq!(map(()), 7);
127    }
128}