#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SseFrame {
pub event: Option<String>,
pub data: String,
}
impl SseFrame {
pub fn data(data: impl Into<String>) -> Self {
Self {
event: None,
data: data.into(),
}
}
pub fn event(event: impl Into<String>, data: impl Into<String>) -> Self {
Self {
event: Some(event.into()),
data: data.into(),
}
}
pub fn encode(&self) -> String {
let mut encoded = String::new();
if let Some(event) = &self.event {
encoded.push_str("event: ");
encoded.push_str(event);
encoded.push('\n');
}
for line in self.data.lines() {
encoded.push_str("data: ");
encoded.push_str(line);
encoded.push('\n');
}
encoded.push('\n');
encoded
}
}
#[derive(Debug, Default)]
pub struct SseDecoder {
buf: String,
utf8: super::utf8::Utf8StreamDecoder,
limits: SseLimits,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SseLimits {
pub max_frame_bytes: usize,
pub max_buffer_bytes: usize,
}
impl Default for SseLimits {
fn default() -> Self {
Self {
max_frame_bytes: 1024 * 1024,
max_buffer_bytes: 8 * 1024 * 1024,
}
}
}
impl SseDecoder {
pub fn new() -> Self {
Self::default()
}
pub fn with_limits(limits: SseLimits) -> Self {
Self {
limits,
..Self::default()
}
}
pub fn push(
&mut self,
chunk: &[u8],
) -> Result<Vec<SseFrame>, crate::transform::TransformError> {
self.push_inner(chunk)
}
fn push_inner(
&mut self,
chunk: &[u8],
) -> Result<Vec<SseFrame>, crate::transform::TransformError> {
let pending = self.buf.len().saturating_add(chunk.len());
self.check_limit("buffer", self.limits.max_buffer_bytes, pending)?;
self.utf8.decode_into(chunk, &mut self.buf);
self.check_limit("buffer", self.limits.max_buffer_bytes, self.buf.len())?;
if self.buf.contains('\r') {
self.buf = self.buf.replace("\r\n", "\n");
}
let mut frames = Vec::new();
let mut cursor = 0;
while let Some(pos) = self.buf[cursor..].find("\n\n") {
let end = cursor + pos + 2;
self.check_limit("frame", self.limits.max_frame_bytes, end - cursor)?;
if let Some(frame) = parse_frame(&self.buf[cursor..end]) {
frames.push(frame);
}
cursor = end;
}
if cursor > 0 {
self.buf.drain(..cursor);
}
self.check_limit("frame", self.limits.max_frame_bytes, self.buf.len())?;
Ok(frames)
}
pub fn finish(&mut self) -> Result<Option<SseFrame>, crate::transform::TransformError> {
self.finish_inner()
}
fn finish_inner(&mut self) -> Result<Option<SseFrame>, crate::transform::TransformError> {
self.utf8.flush(&mut self.buf);
self.check_limit("frame", self.limits.max_frame_bytes, self.buf.len())?;
let raw = std::mem::take(&mut self.buf);
Ok(parse_frame(&raw))
}
fn check_limit(
&self,
limit: &'static str,
max_bytes: usize,
actual_bytes: usize,
) -> Result<(), crate::transform::TransformError> {
if actual_bytes <= max_bytes {
Ok(())
} else {
Err(crate::transform::TransformError::StreamLimitExceeded {
limit,
max_bytes,
actual_bytes,
})
}
}
}
fn parse_frame(raw: &str) -> Option<SseFrame> {
let mut event = None;
let mut data_lines: Vec<&str> = Vec::new();
for line in raw.lines() {
if let Some(rest) = line.strip_prefix("event:") {
event = Some(rest.trim_start().to_owned());
} else if let Some(rest) = line.strip_prefix("data:") {
data_lines.push(rest.strip_prefix(' ').unwrap_or(rest));
}
}
if data_lines.is_empty() {
return None;
}
Some(SseFrame {
event,
data: data_lines.join("\n"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_split_across_chunks() {
let mut d = SseDecoder::new();
assert!(d.push(b"event: ping\nda").unwrap().is_empty());
let frames = d.push(b"ta: {\"a\":1}\n\n: comment\ndata: x").unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].event.as_deref(), Some("ping"));
assert_eq!(frames[0].data, "{\"a\":1}");
assert_eq!(d.finish().unwrap().unwrap().data, "x");
}
#[test]
fn crlf_and_multiline_data() {
let mut d = SseDecoder::new();
let frames = d.push(b"data: l1\r\ndata: l2\r\n\r\n").unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "l1\nl2");
}
#[test]
fn multibyte_char_split_across_chunks() {
let mut d = SseDecoder::new();
let bytes = "data: 汉字\n\n".as_bytes();
assert!(d.push(&bytes[..7]).unwrap().is_empty()); let frames = d.push(&bytes[7..]).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "汉字");
}
#[test]
fn rejects_oversized_frame_and_buffer() {
let limits = SseLimits {
max_frame_bytes: 16,
max_buffer_bytes: 32,
};
let mut d = SseDecoder::with_limits(limits);
assert!(d.push(b"data: 12345678901\n\n").is_err());
let mut d = SseDecoder::with_limits(limits);
assert!(d.push(&[b'x'; 33]).is_err());
}
}