1use std::collections::VecDeque;
13use std::pin::Pin;
14use std::task::{Context, Poll};
15
16use futures_util::Stream;
17
18use crate::error::{SdkError, SdkResult};
19use crate::models::UpdateEvent;
20
21type ByteChunkStream = Pin<Box<dyn Stream<Item = Result<Vec<u8>, reqwest::Error>> + Send>>;
24
25pub struct UpdateEventStream {
39 inner: ByteChunkStream,
41 line_buf: String,
43 pending: VecDeque<UpdateEvent>,
45}
46
47impl UpdateEventStream {
48 pub fn new<S>(stream: S) -> Self
50 where
51 S: Stream<Item = Result<Vec<u8>, reqwest::Error>> + Send + 'static,
52 {
53 Self {
54 inner: Box::pin(stream),
55 line_buf: String::new(),
56 pending: VecDeque::new(),
57 }
58 }
59
60 fn drain_complete_dispatches(&mut self) {
62 while let Some(idx) = self.line_buf.find("\n\n") {
64 let dispatch: String = self.line_buf.drain(..idx + 2).collect();
66 if let Some(ev) = parse_dispatch(&dispatch) {
67 self.pending.push_back(ev);
68 }
69 }
70 }
71}
72
73impl Stream for UpdateEventStream {
74 type Item = SdkResult<UpdateEvent>;
75
76 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
77 let this = self.get_mut();
80
81 loop {
82 if let Some(ev) = this.pending.pop_front() {
84 return Poll::Ready(Some(Ok(ev)));
85 }
86
87 match this.inner.as_mut().poll_next(cx) {
89 Poll::Ready(Some(Ok(chunk))) => {
90 this.line_buf.push_str(&String::from_utf8_lossy(&chunk));
92 this.drain_complete_dispatches();
94 }
96 Poll::Ready(Some(Err(e))) => {
97 return Poll::Ready(Some(Err(SdkError::RequestError(e))));
98 }
99 Poll::Ready(None) => {
100 if !this.line_buf.is_empty() {
102 let rest: String = this.line_buf.drain(..).collect();
103 if let Some(ev) = parse_dispatch(&rest) {
104 this.pending.push_back(ev);
105 }
106 }
107 if let Some(ev) = this.pending.pop_front() {
108 return Poll::Ready(Some(Ok(ev)));
109 }
110 return Poll::Ready(None);
111 }
112 Poll::Pending => {
113 if let Some(ev) = this.pending.pop_front() {
115 return Poll::Ready(Some(Ok(ev)));
116 }
117 return Poll::Pending;
118 }
119 }
120 }
121 }
122}
123
124fn parse_dispatch(dispatch: &str) -> Option<UpdateEvent> {
133 let mut data_lines: Vec<String> = Vec::new();
134 let mut id: Option<u64> = None;
135 let mut _event_type: Option<String> = None;
136
137 for line in dispatch.lines() {
138 if line.is_empty() {
139 continue;
140 }
141 if let Some(_comment) = line.strip_prefix(':') {
143 continue;
144 }
145 if let Some(rest) = line.strip_prefix("data:") {
146 let rest = rest.strip_prefix(' ').unwrap_or(rest);
148 data_lines.push(rest.to_string());
149 } else if let Some(rest) = line.strip_prefix("id:") {
150 let rest = rest.strip_prefix(' ').unwrap_or(rest);
151 if let Ok(parsed) = rest.trim().parse::<u64>() {
152 id = Some(parsed);
153 }
154 } else if let Some(rest) = line.strip_prefix("event:") {
155 let rest = rest.strip_prefix(' ').unwrap_or(rest);
156 _event_type = Some(rest.to_string());
157 }
158 }
160
161 if data_lines.is_empty() {
162 return None;
163 }
164
165 let data = data_lines.join("\n");
166 match serde_json::from_str::<UpdateEvent>(&data) {
167 Ok(mut ev) => {
168 if let Some(id) = id {
170 ev.id = id;
171 }
172 Some(ev)
173 }
174 Err(_) => None,
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn parse_single_update_event() {
185 let frame = "id: 42\nevent: update\ndata: {\"software_id\":\"s1\",\"version\":\"1.2.0\",\"platform\":\"linux\",\"channel\":\"stable\",\"force_update\":false}\n\n";
186 let ev = parse_dispatch(frame).expect("should parse");
187 assert_eq!(ev.id, 42);
188 assert_eq!(ev.software_id, "s1");
189 assert_eq!(ev.version, "1.2.0");
190 assert_eq!(ev.platform, "linux");
191 assert_eq!(ev.channel, "stable");
192 assert!(!ev.force_update);
193 }
194
195 #[test]
196 fn parse_ignores_comments() {
197 assert!(parse_dispatch(": keepalive\n\n").is_none());
199 assert!(parse_dispatch(": lagged\n\n").is_none());
200 }
201
202 #[test]
203 fn parse_multi_line_data() {
204 let frame = "data: {\"software_id\":\"s2\",\ndata: \"version\":\"2.0\",\"platform\":\"win\",\"channel\":\"beta\",\"force_update\":true}\n\n";
205 let ev = parse_dispatch(frame).expect("should parse");
206 assert_eq!(ev.software_id, "s2");
207 assert_eq!(ev.version, "2.0");
208 assert!(ev.force_update);
209 }
210
211 #[test]
212 fn parse_skips_malformed() {
213 assert!(parse_dispatch("data: not-json\n\n").is_none());
215 }
216
217 #[test]
218 fn parse_id_overrides_json() {
219 let frame = "id: 99\ndata: {\"id\":5,\"software_id\":\"s\",\"version\":\"1\",\"platform\":\"p\",\"channel\":\"c\",\"force_update\":false}\n\n";
221 let ev = parse_dispatch(frame).expect("should parse");
222 assert_eq!(ev.id, 99);
223 }
224}