use serde::de::DeserializeOwned;
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServerEvent {
pub id: Option<String>,
pub event: String,
pub data: String,
}
impl ServerEvent {
pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
serde_json::from_str(&self.data).map_err(|source| Error::Decode {
status: 200,
source,
})
}
}
pub struct EventStream {
response: reqwest::Response,
buffer: Vec<u8>,
last_event_id: Option<String>,
finished: bool,
}
impl EventStream {
pub(crate) fn new(response: reqwest::Response, resumed_from: Option<String>) -> Self {
Self {
response,
buffer: Vec::new(),
last_event_id: resumed_from,
finished: false,
}
}
pub fn last_event_id(&self) -> Option<&str> {
self.last_event_id.as_deref()
}
pub async fn next(&mut self) -> Option<Result<ServerEvent>> {
loop {
if self.finished {
return None;
}
while let Some(frame) = take_frame(&mut self.buffer) {
let Some(event) = parse_frame(&frame) else {
continue;
};
if event.event == "authority.error" {
self.finished = true;
return Some(Err(Error::Stream(event.data)));
}
if event.id.is_some() {
self.last_event_id.clone_from(&event.id);
}
return Some(Ok(event));
}
match self.response.chunk().await {
Ok(Some(bytes)) => self
.buffer
.extend(bytes.iter().copied().filter(|byte| *byte != b'\r')),
Ok(None) => {
self.finished = true;
return None;
}
Err(source) => {
self.finished = true;
return Some(Err(Error::transport(source, false)));
}
}
}
}
}
fn take_frame(buffer: &mut Vec<u8>) -> Option<String> {
let end = buffer.windows(2).position(|pair| pair == b"\n\n")?;
let frame = String::from_utf8_lossy(&buffer[..end]).into_owned();
buffer.drain(..end + 2);
Some(frame)
}
fn parse_frame(frame: &str) -> Option<ServerEvent> {
let mut id = None;
let mut event = None;
let mut data: Vec<&str> = Vec::new();
for line in frame.split('\n') {
if line.starts_with(':') {
continue;
}
let (field, value) = line.split_once(':').unwrap_or((line, ""));
let value = value.strip_prefix(' ').unwrap_or(value);
match field {
"id" => id = Some(value.to_owned()),
"event" => event = Some(value.to_owned()),
"data" => data.push(value),
_ => {}
}
}
if data.is_empty() {
return None;
}
Some(ServerEvent {
id,
event: event.unwrap_or_else(|| "message".to_owned()),
data: data.join("\n"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frames_are_split_and_parsed() {
let mut buffer =
b": keep-alive\n\nid: c-2\nevent: state.committed\ndata: {\"a\":\ndata: 1}\n\nid: partial"
.to_vec();
assert_eq!(parse_frame(&take_frame(&mut buffer).unwrap()), None);
let event = parse_frame(&take_frame(&mut buffer).unwrap()).unwrap();
assert_eq!(event.id.as_deref(), Some("c-2"));
assert_eq!(event.event, "state.committed");
assert_eq!(event.data, "{\"a\":\n1}");
assert_eq!(take_frame(&mut buffer), None);
assert_eq!(buffer, b"id: partial");
}
}