1use crate::error::{Error, Result};
27use crate::response::Response;
28use crate::stream::{BodySender, Disconnected};
29use serde::Serialize;
30
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
33pub struct Event {
34 event: Option<String>,
35 id: Option<String>,
36 retry_ms: Option<u64>,
37 data: Option<String>,
38 comment: Option<String>,
39}
40
41impl Event {
42 pub fn data(data: impl Into<String>) -> Self {
44 Self {
45 data: Some(data.into()),
46 ..Self::default()
47 }
48 }
49
50 pub fn json<T: Serialize>(value: &T) -> Result<Self> {
52 let data = serde_json::to_string(value)
53 .map_err(|e| Error::internal(format!("SSE JSON: {}", e)))?;
54 Ok(Self::data(data))
55 }
56
57 pub fn comment(text: impl Into<String>) -> Self {
59 Self {
60 comment: Some(text.into()),
61 ..Self::default()
62 }
63 }
64
65 pub fn event(mut self, name: impl Into<String>) -> Self {
67 self.event = Some(name.into());
68 self
69 }
70
71 pub fn id(mut self, id: impl Into<String>) -> Self {
73 self.id = Some(id.into());
74 self
75 }
76
77 pub fn retry(mut self, millis: u64) -> Self {
79 self.retry_ms = Some(millis);
80 self
81 }
82
83 pub fn encode(&self) -> String {
85 fn single_line(s: &str) -> String {
86 s.chars().filter(|c| *c != '\n' && *c != '\r').collect()
87 }
88 let mut out = String::new();
89 if let Some(c) = &self.comment {
90 for line in c.lines() {
91 out.push_str(": ");
92 out.push_str(line);
93 out.push('\n');
94 }
95 }
96 if let Some(e) = &self.event {
97 out.push_str(&format!("event: {}\n", single_line(e)));
98 }
99 if let Some(id) = &self.id {
100 out.push_str(&format!("id: {}\n", single_line(id)));
101 }
102 if let Some(ms) = self.retry_ms {
103 out.push_str(&format!("retry: {}\n", ms));
104 }
105 if let Some(d) = &self.data {
106 let mut any = false;
108 for line in d.split('\n') {
109 any = true;
110 out.push_str("data: ");
111 out.push_str(line.trim_end_matches('\r'));
112 out.push('\n');
113 }
114 debug_assert!(any);
115 }
116 out.push('\n');
117 out
118 }
119}
120
121#[derive(Clone)]
123pub struct SseSender {
124 inner: BodySender,
125}
126
127impl SseSender {
128 pub async fn send(&self, event: Event) -> std::result::Result<(), Disconnected> {
129 self.inner.send(event.encode()).await
130 }
131
132 pub fn is_closed(&self) -> bool {
133 self.inner.is_closed()
134 }
135
136 pub async fn closed(&self) {
138 self.inner.closed().await
139 }
140}
141
142pub fn sse() -> (SseSender, Response) {
145 let (tx, res) = Response::stream();
146 let res = res
147 .with_header("content-type", "text/event-stream; charset=utf-8")
148 .with_header("cache-control", "no-cache")
149 .with_header("x-accel-buffering", "no");
150 (SseSender { inner: tx }, res)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn encodes_fields_in_order_and_ends_with_a_blank_line() {
159 let e = Event::data("hello").event("greeting").id("7").retry(1500);
160 assert_eq!(
161 e.encode(),
162 "event: greeting\nid: 7\nretry: 1500\ndata: hello\n\n"
163 );
164 }
165
166 #[test]
167 fn multiline_data_becomes_multiple_data_lines() {
168 assert_eq!(
169 Event::data("a\nb\r\nc").encode(),
170 "data: a\ndata: b\ndata: c\n\n"
171 );
172 assert_eq!(Event::data("").encode(), "data: \n\n");
173 assert_eq!(
174 Event::data("trailing\n").encode(),
175 "data: trailing\ndata: \n\n"
176 );
177 }
178
179 #[test]
180 fn newlines_cannot_inject_fields() {
181 let e = Event::data("x").event("a\nretry: 1").id("1\r\ndata: evil");
182 let wire = e.encode();
183 assert_eq!(wire, "event: aretry: 1\nid: 1data: evil\ndata: x\n\n");
184 assert_eq!(wire.matches("\n\n").count(), 1);
185 }
186
187 #[test]
188 fn comments_and_json() {
189 assert_eq!(Event::comment("keep-alive").encode(), ": keep-alive\n\n");
190 assert_eq!(Event::comment("a\nb").encode(), ": a\n: b\n\n");
191 let e = Event::json(&serde_json::json!({ "up": true })).unwrap();
192 assert_eq!(e.encode(), "data: {\"up\":true}\n\n");
193 }
194}