moq_net/model/
subscription.rs1use std::{task::Poll, time::Duration};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct Subscription {
12 pub priority: u8,
14 pub ordered: bool,
18 pub latency_max: Duration,
26 pub group_start: Option<u64>,
34 pub group_end: Option<u64>,
41}
42
43impl Default for Subscription {
44 fn default() -> Self {
45 Self {
46 priority: 0,
47 ordered: false,
48 latency_max: Duration::ZERO,
49 group_start: None,
50 group_end: None,
51 }
52 }
53}
54
55impl Subscription {
56 pub fn with_priority(mut self, priority: u8) -> Self {
58 self.priority = priority;
59 self
60 }
61
62 pub fn with_ordered(mut self, ordered: bool) -> Self {
65 self.ordered = ordered;
66 self
67 }
68
69 pub fn with_latency_max(mut self, latency_max: Duration) -> Self {
72 self.latency_max = latency_max;
73 self
74 }
75
76 pub fn with_group_start(mut self, group_start: impl Into<Option<u64>>) -> Self {
78 self.group_start = group_start.into();
79 self
80 }
81
82 pub fn with_group_end(mut self, group_end: impl Into<Option<u64>>) -> Self {
84 self.group_end = group_end.into();
85 self
86 }
87
88 pub(super) fn poll_combined(&self, combined: &Option<Subscription>) -> Poll<Subscription> {
92 let Some(combined) = combined else {
93 return Poll::Ready(self.clone());
94 };
95
96 let merged = Subscription {
97 priority: self.priority.max(combined.priority),
98 ordered: self.ordered && combined.ordered,
100 latency_max: self.latency_max.max(combined.latency_max),
101 group_start: min_some(self.group_start, combined.group_start),
102 group_end: max_unbounded(self.group_end, combined.group_end),
103 };
104
105 if &merged != combined {
106 return Poll::Ready(merged);
107 }
108
109 Poll::Pending
110 }
111}
112
113pub(super) fn min_some(a: Option<u64>, b: Option<u64>) -> Option<u64> {
115 match (a, b) {
116 (Some(a), Some(b)) => Some(a.min(b)),
117 (Some(a), None) | (None, Some(a)) => Some(a),
118 (None, None) => None,
119 }
120}
121
122fn max_unbounded(a: Option<u64>, b: Option<u64>) -> Option<u64> {
123 match (a, b) {
124 (Some(a), Some(b)) => Some(a.max(b)),
125 (None, _) | (_, None) => None,
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn combine(subscriptions: &[Subscription]) -> Option<Subscription> {
134 let mut combined = None;
135 for sub in subscriptions {
136 if let Poll::Ready(merged) = sub.poll_combined(&combined) {
137 combined = Some(merged);
138 }
139 }
140 combined
141 }
142
143 #[test]
144 fn combined_ordered_stays_ordered_for_multiple_ordered_viewers() {
145 let subscription = Subscription::default().with_ordered(true);
146
147 let combined = combine(&[subscription.clone(), subscription.clone(), subscription]).unwrap();
148
149 assert!(combined.ordered);
150 }
151
152 #[test]
153 fn combined_any_unordered_viewer_disables_ordered() {
154 let unordered = Subscription::default().with_ordered(false);
155 let ordered = Subscription::default().with_ordered(true);
156
157 let combined = combine(&[unordered, ordered]).unwrap();
158
159 assert!(!combined.ordered);
160 }
161
162 #[test]
163 fn combined_group_start_uses_earliest_explicit_start() {
164 let live = Subscription::default().with_group_start(None);
165 let catchup = Subscription::default().with_group_start(10);
166 let older_catchup = Subscription::default().with_group_start(5);
167
168 let combined = combine(&[live, catchup, older_catchup]).unwrap();
169
170 assert_eq!(combined.group_start, Some(5));
171 }
172
173 #[test]
174 fn combined_group_end_keeps_live_subscription_unbounded() {
175 let live = Subscription::default().with_group_end(None);
176 let bounded = Subscription::default().with_group_end(10);
177
178 let combined = combine(&[live, bounded]).unwrap();
179
180 assert_eq!(combined.group_end, None);
181 }
182
183 #[test]
184 fn combined_group_end_uses_latest_bounded_end() {
185 let early = Subscription::default().with_group_end(10);
186 let late = Subscription::default().with_group_end(20);
187
188 let combined = combine(&[early, late]).unwrap();
189
190 assert_eq!(combined.group_end, Some(20));
191 }
192}