1use std::time::Duration;
2
3#[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 pub fn map_msg(inner: Sub<()>, map: fn(()) -> M) -> Self {
60 Self::MapMsg {
61 inner: Box::new(inner),
62 map,
63 }
64 }
65
66 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}