#[derive(Default)]
pub(crate) struct SseBuffer {
buf: Vec<u8>,
}
impl SseBuffer {
pub fn push(&mut self, chunk: &[u8]) {
self.buf.extend_from_slice(chunk);
}
pub fn next_segment(&mut self, delim: &[u8]) -> Option<String> {
let idx = self.buf.windows(delim.len()).position(|w| w == delim)?;
let segment: Vec<u8> = self.buf.drain(..idx + delim.len()).collect();
Some(String::from_utf8_lossy(&segment).into_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_character_split_across_chunks_survives_intact() {
let mut buf = SseBuffer::default();
buf.push(b"data: {\"t\":\"\xc3");
assert_eq!(buf.next_segment(b"\n"), None, "no complete line yet");
buf.push(b"\xa9\"}\n");
assert_eq!(buf.next_segment(b"\n").unwrap(), "data: {\"t\":\"é\"}\n");
assert_eq!(buf.next_segment(b"\n"), None);
}
#[test]
fn frames_split_on_the_two_byte_delimiter() {
let mut buf = SseBuffer::default();
buf.push(b"event: x\ndata: 1\n\ndata: 2\n\ntail");
assert_eq!(buf.next_segment(b"\n\n").unwrap(), "event: x\ndata: 1\n\n");
assert_eq!(buf.next_segment(b"\n\n").unwrap(), "data: 2\n\n");
assert_eq!(
buf.next_segment(b"\n\n"),
None,
"the tail waits for its delimiter"
);
}
#[test]
fn genuinely_invalid_utf8_degrades_to_replacement_rather_than_failing() {
let mut buf = SseBuffer::default();
buf.push(b"bad \xff byte\n");
let line = buf.next_segment(b"\n").unwrap();
assert!(line.contains('\u{FFFD}'));
assert!(line.starts_with("bad "));
}
}