#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SseEvent {
pub event_type: Option<String>,
pub data: String,
pub id: Option<String>,
pub retry_ms: Option<u32>,
}
#[derive(Debug, Default)]
pub struct SseExport {
pub events: Vec<SseEvent>,
pub endpoint: String,
}
pub fn new_sse_export(endpoint: &str) -> SseExport {
SseExport {
events: Vec::new(),
endpoint: endpoint.to_owned(),
}
}
pub fn sse_add_data(
export: &mut SseExport,
data: &str,
event_type: Option<&str>,
id: Option<&str>,
) {
export.events.push(SseEvent {
event_type: event_type.map(str::to_owned),
data: data.to_owned(),
id: id.map(str::to_owned),
retry_ms: None,
});
}
pub fn sse_keep_alive(export: &mut SseExport) {
export.events.push(SseEvent {
event_type: None,
data: String::new(),
id: None,
retry_ms: None,
});
}
pub fn sse_event_count(export: &SseExport) -> usize {
export.events.len()
}
pub fn events_of_type(export: &SseExport, event_type: &str) -> usize {
export
.events
.iter()
.filter(|e| e.event_type.as_deref() == Some(event_type))
.count()
}
pub fn total_sse_bytes(export: &SseExport) -> usize {
export.events.iter().map(|e| e.data.len()).sum()
}
pub fn render_sse_event(ev: &SseEvent) -> String {
let mut out = String::new();
if let Some(id) = &ev.id {
out.push_str(&format!("id: {}\n", id));
}
if let Some(et) = &ev.event_type {
out.push_str(&format!("event: {}\n", et));
}
if let Some(r) = ev.retry_ms {
out.push_str(&format!("retry: {}\n", r));
}
for line in ev.data.lines() {
out.push_str(&format!("data: {}\n", line));
}
out.push('\n');
out
}
pub fn sse_export_to_json(export: &SseExport) -> String {
format!(
r#"{{"endpoint":"{}", "event_count":{}, "total_bytes":{}}}"#,
export.endpoint,
sse_event_count(export),
total_sse_bytes(export)
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_export_is_empty() {
let e = new_sse_export("/events");
assert_eq!(sse_event_count(&e), 0);
}
#[test]
fn add_data_increments_count() {
let mut e = new_sse_export("/events");
sse_add_data(&mut e, "hello", None, None);
assert_eq!(sse_event_count(&e), 1);
}
#[test]
fn keep_alive_increments_count() {
let mut e = new_sse_export("/events");
sse_keep_alive(&mut e);
assert_eq!(sse_event_count(&e), 1);
}
#[test]
fn total_bytes_counted() {
let mut e = new_sse_export("/events");
sse_add_data(&mut e, "hello", None, None);
assert_eq!(total_sse_bytes(&e), 5);
}
#[test]
fn events_of_type_counted() {
let mut e = new_sse_export("/events");
sse_add_data(&mut e, "d", Some("mesh"), None);
sse_add_data(&mut e, "d", Some("pose"), None);
assert_eq!(events_of_type(&e, "mesh"), 1);
}
#[test]
fn render_event_contains_data_prefix() {
let ev = SseEvent {
event_type: None,
data: "test".into(),
id: None,
retry_ms: None,
};
assert!(render_sse_event(&ev).contains("data: test"));
}
#[test]
fn render_event_contains_event_type() {
let ev = SseEvent {
event_type: Some("mesh".into()),
data: "x".into(),
id: None,
retry_ms: None,
};
assert!(render_sse_event(&ev).contains("event: mesh"));
}
#[test]
fn json_contains_endpoint() {
let e = new_sse_export("/my/stream");
assert!(sse_export_to_json(&e).contains("/my/stream"));
}
#[test]
fn event_id_stored() {
let mut e = new_sse_export("/events");
sse_add_data(&mut e, "v", None, Some("42"));
assert_eq!(e.events[0].id.as_deref(), Some("42"));
}
}