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 subs: Vec<_> = subs.into_iter().collect();
80 if subs.is_empty() {
81 Self::None
82 } else if subs.len() == 1 {
83 subs.into_iter().next().unwrap()
84 } else {
85 Self::Batch(subs)
86 }
87 }
88
89 pub fn id(&self) -> Option<u64> {
90 match self {
91 Self::None => None,
92 Self::Tick { id, .. } | Self::Stream { id, .. } | Self::WebSocket { id, .. } => {
93 Some(*id)
94 }
95 Self::MapMsg { inner, .. } => inner.id(),
96 Self::Batch(items) => items.first().and_then(Sub::id),
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn zero() -> i32 {
106 0
107 }
108
109 fn unit() {}
110
111 fn ping(_: ()) -> i32 {
112 7
113 }
114
115 #[test]
116 fn tick_has_stable_id() {
117 let sub = Sub::<i32>::tick(42, Duration::from_secs(1), zero);
118 assert_eq!(sub.id(), Some(42));
119 }
120
121 #[test]
122 fn map_msg_wraps_unit_tick() {
123 let sub = Sub::map_msg(Sub::tick(1, Duration::from_millis(1), unit), ping);
124 assert_eq!(sub.id(), Some(1));
125 let Sub::MapMsg { map, .. } = sub else {
126 panic!("expected map_msg");
127 };
128 assert_eq!(map(()), 7);
129 }
130}