1use std::future::Future;
22use std::marker::PhantomData;
23use std::pin::Pin;
24use std::sync::Arc;
25use std::task::{Context, Poll};
26use std::time::Duration;
27
28use bytes::Bytes;
29use futures::future::BoxFuture;
30use futures::stream::BoxStream;
31use futures::{Stream, StreamExt};
32use reqwest::Response;
33use serde::de::DeserializeOwned;
34
35use crate::error::{Error, Result};
36use crate::retry::MAX_RECONNECT_BACKOFF;
37
38pub(crate) type Reopen =
42 Arc<dyn Fn() -> BoxFuture<'static, Result<Response>> + Send + Sync + 'static>;
43
44type ByteStream = BoxStream<'static, std::result::Result<Bytes, reqwest::Error>>;
45type SleepFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
46type ReopenFuture = BoxFuture<'static, Result<Response>>;
47
48pub struct EventStream<T: DeserializeOwned> {
53 state: State,
54 buf: SseBuffer,
55 reopen: Option<Reopen>,
56 reconnect_attempt: u32,
57 _marker: PhantomData<fn() -> T>,
58}
59
60impl<T: DeserializeOwned> std::fmt::Debug for EventStream<T> {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("EventStream")
63 .field("reconnect_attempt", &self.reconnect_attempt)
64 .field("buffered", &self.buf.pending_len())
65 .field("state", &self.state.tag())
66 .finish()
67 }
68}
69
70enum State {
71 Reading(ByteStream),
73 Backoff(SleepFuture),
75 Reopening(ReopenFuture),
77 Done,
79}
80
81impl State {
82 fn tag(&self) -> &'static str {
83 match self {
84 State::Reading(_) => "reading",
85 State::Backoff(_) => "backoff",
86 State::Reopening(_) => "reopening",
87 State::Done => "done",
88 }
89 }
90}
91
92impl<T: DeserializeOwned> EventStream<T> {
93 #[allow(dead_code)] pub(crate) fn new(initial: Response, reopen: Reopen) -> Self {
98 Self {
99 state: State::Reading(initial.bytes_stream().boxed()),
100 buf: SseBuffer::default(),
101 reopen: Some(reopen),
102 reconnect_attempt: 0,
103 _marker: PhantomData,
104 }
105 }
106
107 #[cfg(test)]
110 pub(crate) fn from_bytes_stream(bytes: ByteStream) -> Self {
111 Self {
112 state: State::Reading(bytes),
113 buf: SseBuffer::default(),
114 reopen: None,
115 reconnect_attempt: 0,
116 _marker: PhantomData,
117 }
118 }
119
120 fn reconnect_delay(&self) -> Duration {
121 let base_ms = 100u64.saturating_mul(1u64 << self.reconnect_attempt.min(8));
123 let computed = Duration::from_millis(base_ms);
124 computed.min(MAX_RECONNECT_BACKOFF)
125 }
126}
127
128impl<T: DeserializeOwned> Stream for EventStream<T> {
129 type Item = Result<T>;
130
131 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
132 loop {
133 match self.buf.next_event() {
135 Some(SseEvent::Data(payload)) => {
136 let decoded: Result<T> = serde_json::from_slice(&payload)
137 .map_err(|e| Error::Stream(format!("malformed SSE payload: {e}")));
138 return Poll::Ready(Some(decoded));
139 }
140 Some(SseEvent::Done) => {
141 self.state = State::Done;
142 return Poll::Ready(None);
143 }
144 None => {}
145 }
146
147 let cur = std::mem::replace(&mut self.state, State::Done);
150 match cur {
151 State::Reading(mut s) => match s.poll_next_unpin(cx) {
152 Poll::Ready(Some(Ok(chunk))) => {
153 self.reconnect_attempt = 0;
154 self.buf.push(&chunk);
155 self.state = State::Reading(s);
156 continue;
157 }
158 Poll::Ready(Some(Err(e))) => {
159 let err: Error = e.into();
160 if err.is_transient() && self.reopen.is_some() {
161 self.reconnect_attempt = self.reconnect_attempt.saturating_add(1);
163 let delay = self.reconnect_delay();
164 self.state = State::Backoff(Box::pin(tokio::time::sleep(delay)));
165 continue;
166 }
167 self.state = State::Done;
168 return Poll::Ready(Some(Err(err)));
169 }
170 Poll::Ready(None) => {
171 if let Some(ev) = self.buf.finish() {
174 self.state = State::Done;
175 return match ev {
176 SseEvent::Data(payload) => {
177 let decoded: Result<T> = serde_json::from_slice(&payload)
178 .map_err(|e| {
179 Error::Stream(format!("malformed SSE payload: {e}"))
180 });
181 Poll::Ready(Some(decoded))
182 }
183 SseEvent::Done => Poll::Ready(None),
184 };
185 }
186 self.state = State::Done;
187 return Poll::Ready(None);
188 }
189 Poll::Pending => {
190 self.state = State::Reading(s);
191 return Poll::Pending;
192 }
193 },
194 State::Backoff(mut fut) => match fut.as_mut().poll(cx) {
195 Poll::Ready(()) => {
196 let reopen = self
197 .reopen
198 .clone()
199 .expect("Backoff state requires a reopen closure");
200 let f = (reopen)();
201 self.state = State::Reopening(f);
202 continue;
203 }
204 Poll::Pending => {
205 self.state = State::Backoff(fut);
206 return Poll::Pending;
207 }
208 },
209 State::Reopening(mut fut) => match fut.as_mut().poll(cx) {
210 Poll::Ready(Ok(resp)) => {
211 self.state = State::Reading(resp.bytes_stream().boxed());
212 continue;
213 }
214 Poll::Ready(Err(err)) => {
215 if err.is_transient() {
216 self.reconnect_attempt = self.reconnect_attempt.saturating_add(1);
218 let delay = self.reconnect_delay();
219 self.state = State::Backoff(Box::pin(tokio::time::sleep(delay)));
220 continue;
221 }
222 self.state = State::Done;
223 return Poll::Ready(Some(Err(err)));
224 }
225 Poll::Pending => {
226 self.state = State::Reopening(fut);
227 return Poll::Pending;
228 }
229 },
230 State::Done => {
231 self.state = State::Done;
232 return Poll::Ready(None);
233 }
234 }
235 }
236 }
237}
238
239#[derive(Debug, PartialEq, Eq)]
241enum SseEvent {
242 Data(Vec<u8>),
244 Done,
246}
247
248#[derive(Default)]
251struct SseBuffer {
252 pending: Vec<u8>,
254 current_data: Vec<Vec<u8>>,
257 has_data: bool,
259}
260
261impl SseBuffer {
262 fn pending_len(&self) -> usize {
263 self.pending.len()
264 }
265
266 fn push(&mut self, chunk: &[u8]) {
267 self.pending.extend_from_slice(chunk);
268 }
269
270 fn next_event(&mut self) -> Option<SseEvent> {
272 loop {
273 let idx = self.pending.iter().position(|&b| b == b'\n')?;
274 let mut line: Vec<u8> = self.pending.drain(..=idx).collect();
276 line.pop(); if line.last() == Some(&b'\r') {
278 line.pop();
279 }
280 if let Some(ev) = self.process_line(line) {
281 return Some(ev);
282 }
283 }
284 }
285
286 fn finish(&mut self) -> Option<SseEvent> {
289 if !self.pending.is_empty() {
290 let mut line = std::mem::take(&mut self.pending);
291 if line.last() == Some(&b'\r') {
292 line.pop();
293 }
294 if let Some(ev) = self.process_line(line) {
295 return Some(ev);
296 }
297 }
298 self.flush_event()
299 }
300
301 fn process_line(&mut self, line: Vec<u8>) -> Option<SseEvent> {
302 if line.is_empty() {
303 return self.flush_event();
304 }
305 if line.first() == Some(&b':') {
307 return None;
308 }
309 if let Some(rest) = strip_field(&line, b"data") {
311 self.current_data.push(rest);
312 self.has_data = true;
313 }
314 None
317 }
318
319 fn flush_event(&mut self) -> Option<SseEvent> {
320 if !self.has_data {
321 return None;
322 }
323 self.has_data = false;
324 let lines = std::mem::take(&mut self.current_data);
325 let mut payload: Vec<u8> = Vec::new();
327 for (i, l) in lines.iter().enumerate() {
328 if i > 0 {
329 payload.push(b'\n');
330 }
331 payload.extend_from_slice(l);
332 }
333 if payload == b"[DONE]" {
335 return Some(SseEvent::Done);
336 }
337 Some(SseEvent::Data(payload))
338 }
339}
340
341fn strip_field(line: &[u8], field: &[u8]) -> Option<Vec<u8>> {
344 if line.len() < field.len() + 1 {
345 return None;
346 }
347 if &line[..field.len()] != field {
348 return None;
349 }
350 if line[field.len()] != b':' {
351 return None;
352 }
353 let mut rest = &line[field.len() + 1..];
354 if rest.first() == Some(&b' ') {
355 rest = &rest[1..];
356 }
357 Some(rest.to_vec())
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use futures::stream;
364 use pretty_assertions::assert_eq;
365
366 fn drain_buffer(buf: &mut SseBuffer) -> Vec<SseEvent> {
367 let mut out = Vec::new();
368 while let Some(ev) = buf.next_event() {
369 out.push(ev);
370 }
371 if let Some(ev) = buf.finish() {
372 out.push(ev);
373 }
374 out
375 }
376
377 #[test]
378 fn parses_single_event() {
379 let mut b = SseBuffer::default();
380 b.push(b"data: {\"x\":1}\n\n");
381 let events = drain_buffer(&mut b);
382 assert_eq!(events, vec![SseEvent::Data(b"{\"x\":1}".to_vec())]);
383 }
384
385 #[test]
386 fn parses_done_terminator() {
387 let mut b = SseBuffer::default();
388 b.push(b"data: [DONE]\n\n");
389 let events = drain_buffer(&mut b);
390 assert_eq!(events, vec![SseEvent::Done]);
391 }
392
393 #[test]
394 fn ignores_comment_lines() {
395 let mut b = SseBuffer::default();
396 b.push(b": heartbeat\ndata: {\"a\":1}\n\n");
397 let events = drain_buffer(&mut b);
398 assert_eq!(events, vec![SseEvent::Data(b"{\"a\":1}".to_vec())]);
399 }
400
401 #[test]
402 fn joins_multi_line_data() {
403 let mut b = SseBuffer::default();
404 b.push(b"data: line1\ndata: line2\n\n");
405 let events = drain_buffer(&mut b);
406 assert_eq!(events, vec![SseEvent::Data(b"line1\nline2".to_vec())]);
407 }
408
409 #[test]
410 fn handles_crlf_line_endings() {
411 let mut b = SseBuffer::default();
412 b.push(b"data: {\"x\":1}\r\n\r\n");
413 let events = drain_buffer(&mut b);
414 assert_eq!(events, vec![SseEvent::Data(b"{\"x\":1}".to_vec())]);
415 }
416
417 #[test]
418 fn handles_chunk_boundaries() {
419 let mut b = SseBuffer::default();
420 b.push(b"data: {\"x");
421 assert!(b.next_event().is_none());
422 b.push(b"\":1}\n");
423 assert!(b.next_event().is_none());
425 b.push(b"\n");
426 let events = drain_buffer(&mut b);
427 assert_eq!(events, vec![SseEvent::Data(b"{\"x\":1}".to_vec())]);
428 }
429
430 #[test]
431 fn ignores_non_data_fields() {
432 let mut b = SseBuffer::default();
433 b.push(b"event: ping\nid: 42\nretry: 1000\ndata: {\"x\":1}\n\n");
434 let events = drain_buffer(&mut b);
435 assert_eq!(events, vec![SseEvent::Data(b"{\"x\":1}".to_vec())]);
436 }
437
438 #[test]
439 fn flushes_trailing_event_without_blank_line() {
440 let mut b = SseBuffer::default();
441 b.push(b"data: {\"x\":1}\n");
442 let events = drain_buffer(&mut b);
444 assert_eq!(events, vec![SseEvent::Data(b"{\"x\":1}".to_vec())]);
445 }
446
447 #[test]
448 fn handles_empty_data_payload() {
449 let mut b = SseBuffer::default();
450 b.push(b"data: \n\n");
451 let events = drain_buffer(&mut b);
452 assert_eq!(events, vec![SseEvent::Data(Vec::new())]);
453 }
454
455 #[derive(serde::Deserialize, Debug, PartialEq)]
456 struct Sample {
457 x: i32,
458 }
459
460 #[tokio::test]
461 async fn event_stream_yields_decoded_events_then_done() {
462 let chunks: Vec<std::result::Result<Bytes, reqwest::Error>> = vec![
463 Ok(Bytes::from_static(b"data: {\"x\":1}\n\n")),
464 Ok(Bytes::from_static(b"data: {\"x\":2}\n\n")),
465 Ok(Bytes::from_static(b"data: [DONE]\n\n")),
466 ];
467 let body: ByteStream = stream::iter(chunks).boxed();
468 let mut s: EventStream<Sample> = EventStream::from_bytes_stream(body);
469 let a = s.next().await.unwrap().unwrap();
470 let b = s.next().await.unwrap().unwrap();
471 assert_eq!(a, Sample { x: 1 });
472 assert_eq!(b, Sample { x: 2 });
473 assert!(s.next().await.is_none());
474 }
475
476 #[tokio::test]
477 async fn event_stream_surfaces_malformed_payload_as_error() {
478 let chunks: Vec<std::result::Result<Bytes, reqwest::Error>> =
479 vec![Ok(Bytes::from_static(b"data: not-json\n\n"))];
480 let body: ByteStream = stream::iter(chunks).boxed();
481 let mut s: EventStream<Sample> = EventStream::from_bytes_stream(body);
482 let item = s.next().await.unwrap();
483 assert!(matches!(item, Err(Error::Stream(_))));
484 }
485
486 #[tokio::test]
487 async fn event_stream_handles_split_event_across_chunks() {
488 let chunks: Vec<std::result::Result<Bytes, reqwest::Error>> = vec![
489 Ok(Bytes::from_static(b"data: {\"x")),
490 Ok(Bytes::from_static(b"\":7}\n\n")),
491 Ok(Bytes::from_static(b"data: [DONE]\n\n")),
492 ];
493 let body: ByteStream = stream::iter(chunks).boxed();
494 let mut s: EventStream<Sample> = EventStream::from_bytes_stream(body);
495 let a = s.next().await.unwrap().unwrap();
496 assert_eq!(a, Sample { x: 7 });
497 assert!(s.next().await.is_none());
498 }
499}