1use std::borrow::Cow;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Framing {
17 Sse,
19 Ndjson,
21 Whole,
23}
24
25impl Framing {
26 pub fn framer(self) -> Option<Framer> {
29 let lines = match self {
30 Self::Sse => Lines::Sse(SseFramer::new()),
31 Self::Ndjson => Lines::Ndjson(NdjsonFramer::new()),
32 Self::Whole => return None,
33 };
34 Some(Framer { lines })
35 }
36
37 pub fn split(&self, bytes: &[u8]) -> Vec<WireFrame> {
49 let Some(mut framer) = self.framer() else {
50 return (!bytes.is_empty())
51 .then(|| WireFrame::from_bytes(bytes.to_vec()))
52 .into_iter()
53 .collect();
54 };
55 let mut frames: Vec<WireFrame> = framer.push(bytes).collect();
56 frames.extend(framer.finish());
57 frames
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum WireFrame {
67 Text(String),
69 Bytes(Vec<u8>),
71}
72
73impl WireFrame {
74 pub fn as_str(&self) -> Cow<'_, str> {
76 match self {
77 Self::Text(text) => Cow::Borrowed(text),
78 Self::Bytes(bytes) => String::from_utf8_lossy(bytes),
79 }
80 }
81
82 pub fn as_bytes(&self) -> &[u8] {
84 match self {
85 Self::Text(text) => text.as_bytes(),
86 Self::Bytes(bytes) => bytes,
87 }
88 }
89
90 pub fn from_bytes(bytes: Vec<u8>) -> Self {
92 match String::from_utf8(bytes) {
93 Ok(text) => Self::Text(text),
94 Err(error) => Self::Bytes(error.into_bytes()),
95 }
96 }
97}
98
99#[derive(Debug)]
101pub struct Framer {
102 lines: Lines,
103}
104
105#[derive(Debug)]
106enum Lines {
107 Sse(SseFramer),
108 Ndjson(NdjsonFramer),
109}
110
111impl Framer {
112 pub fn push(&mut self, chunk: &[u8]) -> impl Iterator<Item = WireFrame> + '_ {
115 let (sse, ndjson) = match &mut self.lines {
116 Lines::Sse(framer) => (Some(framer.push(chunk)), None),
117 Lines::Ndjson(framer) => (None, Some(framer.push(chunk))),
118 };
119 let events = sse.into_iter().flatten();
120 let events = events.filter(|event| !event.data.trim().is_empty());
121 let lines = ndjson.into_iter().flatten().map(WireFrame::from_bytes);
122 events.map(|event| WireFrame::Text(event.data)).chain(lines)
123 }
124
125 pub fn finish(&mut self) -> Option<WireFrame> {
128 match &mut self.lines {
129 Lines::Sse(_) => None,
130 Lines::Ndjson(framer) => framer.finish().map(WireFrame::from_bytes),
131 }
132 }
133
134 pub fn pending(&self) -> usize {
137 match &self.lines {
138 Lines::Sse(framer) => framer.pending(),
139 Lines::Ndjson(framer) => framer.pending(),
140 }
141 }
142}
143
144#[derive(Debug, Clone, Default, PartialEq, Eq)]
146pub struct SseEvent {
147 pub event: Cow<'static, str>,
149 pub data: String,
151 pub id: String,
153 pub retry: Option<u64>,
156}
157
158const BOM: [u8; 3] = [0xef, 0xbb, 0xbf];
160
161#[derive(Debug, Default)]
163pub struct SseFramer {
164 buffer: Vec<u8>,
166 scanned: usize,
168 ready: Vec<SseEvent>,
170 event_type: String,
171 data: String,
172 last_event_id: String,
173 retry: Option<u64>,
174 bom_prefix: usize,
176 bom_done: bool,
177 since_dispatch: usize,
179}
180
181impl SseFramer {
182 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn push(&mut self, chunk: &[u8]) -> std::vec::Drain<'_, SseEvent> {
189 let chunk = self.strip_bom(chunk);
190 self.buffer.extend_from_slice(chunk);
191 let buffer = std::mem::take(&mut self.buffer);
193 let mut rest = buffer.as_slice();
194 while let Some((line, next)) = terminated_line(rest, self.scanned) {
195 self.scanned = 0;
196 self.since_dispatch = self.since_dispatch.saturating_add(rest.len() - next.len());
197 self.line(&String::from_utf8_lossy(line));
198 rest = next;
199 }
200 self.scanned = rest.len().saturating_sub(1);
203 let consumed = buffer.len() - rest.len();
204 self.buffer = buffer;
205 self.buffer.drain(..consumed);
206 self.ready.drain(..)
207 }
208
209 pub fn pending(&self) -> usize {
212 self.since_dispatch.saturating_add(self.buffer.len())
213 }
214
215 fn strip_bom<'a>(&mut self, chunk: &'a [u8]) -> &'a [u8] {
218 if self.bom_done {
219 return chunk;
220 }
221 let mut rest = chunk;
222 while let Some(&byte) = rest.first() {
223 if BOM.get(self.bom_prefix) != Some(&byte) {
224 let matched = std::mem::take(&mut self.bom_prefix);
226 self.bom_done = true;
227 self.buffer
228 .extend_from_slice(BOM.get(..matched).unwrap_or_default());
229 return rest;
230 }
231 self.bom_prefix += 1;
232 rest = rest.get(1..).unwrap_or_default();
233 if self.bom_prefix == BOM.len() {
234 self.bom_prefix = 0;
235 self.bom_done = true;
236 return rest;
237 }
238 }
239 rest
240 }
241
242 fn line(&mut self, line: &str) {
244 if line.is_empty() {
245 self.dispatch();
246 return;
247 }
248 if let Some(rest) = line.strip_prefix(':') {
250 let _ = rest;
251 return;
252 }
253 let (field, value) = match line.split_once(':') {
254 Some((field, value)) => (field, value.strip_prefix(' ').unwrap_or(value)),
255 None => (line, ""),
256 };
257 match field {
258 "event" => {
259 self.event_type.clear();
260 self.event_type.push_str(value);
261 }
262 "data" => {
263 self.data.push_str(value);
264 self.data.push('\n');
265 }
266 "id" if !value.contains('\0') => {
268 self.last_event_id.clear();
269 self.last_event_id.push_str(value);
270 }
271 "retry" if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) => {
272 self.retry = value.parse().ok();
273 }
274 _ => {}
275 }
276 }
277
278 fn dispatch(&mut self) {
281 self.since_dispatch = 0;
282 if self.data.is_empty() {
283 self.event_type.clear();
284 return;
285 }
286 self.data.pop();
287 let event = if self.event_type.is_empty() {
288 Cow::Borrowed("message")
289 } else {
290 Cow::Owned(std::mem::take(&mut self.event_type))
291 };
292 self.ready.push(SseEvent {
293 event,
294 data: std::mem::take(&mut self.data),
295 id: self.last_event_id.clone(),
296 retry: self.retry.take(),
297 });
298 self.event_type.clear();
299 }
300}
301
302#[derive(Debug, Default)]
305pub struct NdjsonFramer {
306 buffer: Vec<u8>,
307 scanned: usize,
309 ready: Vec<Vec<u8>>,
310}
311
312impl NdjsonFramer {
313 pub fn new() -> Self {
315 Self::default()
316 }
317
318 pub fn push(&mut self, chunk: &[u8]) -> std::vec::Drain<'_, Vec<u8>> {
321 self.buffer.extend_from_slice(chunk);
322 let mut rest = self.buffer.as_slice();
323 while let Some((line, end)) = split_line(rest, self.scanned, |byte| *byte == b'\n') {
324 self.scanned = 0;
325 let line = line.strip_suffix(b"\r").unwrap_or(line);
326 if !line.is_empty() {
327 self.ready.push(line.to_vec());
328 }
329 rest = end.get(1..).unwrap_or_default();
330 }
331 self.scanned = rest.len();
332 let consumed = self.buffer.len() - rest.len();
333 self.buffer.drain(..consumed);
334 self.ready.drain(..)
335 }
336
337 pub fn finish(&mut self) -> Option<Vec<u8>> {
339 let line = std::mem::take(&mut self.buffer);
340 (!line.is_empty()).then_some(line)
341 }
342
343 pub fn pending(&self) -> usize {
345 self.buffer.len()
346 }
347}
348
349fn terminated_line(buffer: &[u8], scanned: usize) -> Option<(&[u8], &[u8])> {
355 match split_line(buffer, scanned, |byte| *byte == b'\n' || *byte == b'\r')? {
356 (line, [b'\r', b'\n', rest @ ..]) => Some((line, rest)),
357 (_, [b'\r'] | []) => None,
358 (line, [_, rest @ ..]) => Some((line, rest)),
359 }
360}
361
362fn split_line(buffer: &[u8], scanned: usize, ends: impl Fn(&u8) -> bool) -> Option<(&[u8], &[u8])> {
365 buffer.split_at_checked(scanned + buffer.iter().skip(scanned).position(ends)?)
366}
367
368#[cfg(test)]
369mod tests;