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>,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum SseField {
162 Data,
164 Event,
166 Id,
168 Blank,
170 Other,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct SseLine {
177 pub span: std::ops::Range<usize>,
179 pub content: std::ops::Range<usize>,
181 pub value: Option<std::ops::Range<usize>>,
183 pub field: SseField,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct RecordedSseEvent {
190 pub event: SseEvent,
192 pub end: usize,
194}
195
196#[derive(Debug, Default)]
198pub struct SseBatch {
199 pub events: Vec<RecordedSseEvent>,
201 pub lines: Vec<SseLine>,
203}
204
205const BOM: [u8; 3] = [0xef, 0xbb, 0xbf];
207
208#[derive(Debug, Default)]
210pub struct SseFramer {
211 buffer: Vec<u8>,
213 scanned: usize,
215 after_cr: bool,
218 ready: Vec<SseEvent>,
220 event_type: String,
221 data: String,
222 last_event_id: String,
223 retry: Option<u64>,
224 bom_prefix: usize,
226 bom_done: bool,
227 since_dispatch: usize,
229 offset: usize,
231}
232
233impl SseFramer {
234 pub fn new() -> Self {
236 Self::default()
237 }
238
239 pub fn push(&mut self, chunk: &[u8]) -> std::vec::Drain<'_, SseEvent> {
241 self.push_inner(chunk, None);
242 self.ready.drain(..)
243 }
244
245 pub fn push_recording(&mut self, chunk: &[u8]) -> SseBatch {
247 let mut batch = SseBatch::default();
248 self.push_inner(chunk, Some(&mut batch));
249 batch
250 }
251
252 pub fn finish_recording(&mut self) -> SseBatch {
255 let mut batch = SseBatch::default();
256 if self.bom_prefix > 0 {
257 self.buffer
258 .extend_from_slice(BOM.get(..self.bom_prefix).unwrap_or_default());
259 self.bom_prefix = 0;
260 self.bom_done = true;
261 }
262 if !self.buffer.is_empty() {
263 let buffer = std::mem::take(&mut self.buffer);
264 let start = self.offset;
265 self.offset = self.offset.saturating_add(buffer.len());
266 let (field, value) = self.line(&String::from_utf8_lossy(&buffer));
267 batch.lines.push(SseLine {
268 span: start..self.offset,
269 content: start..self.offset,
270 value: value.map(|at| start.saturating_add(at)..self.offset),
271 field,
272 });
273 }
274 self.dispatch();
275 if let Some(event) = self.ready.pop() {
276 batch.events.push(RecordedSseEvent {
277 event,
278 end: self.offset,
279 });
280 }
281 batch
282 }
283
284 fn push_inner(&mut self, chunk: &[u8], mut recording: Option<&mut SseBatch>) {
285 let mut chunk = self.strip_bom(chunk);
286 if self.after_cr
287 && let Some((&first, rest)) = chunk.split_first()
288 {
289 self.after_cr = false;
290 if first == b'\n' {
291 chunk = rest;
292 self.offset = self.offset.saturating_add(1);
293 if self.since_dispatch > 0 {
295 self.since_dispatch = self.since_dispatch.saturating_add(1);
296 }
297 }
298 }
299 self.buffer.extend_from_slice(chunk);
300 let buffer = std::mem::take(&mut self.buffer);
302 let mut rest = buffer.as_slice();
303 while let Some((line, next)) = terminated_line(rest, self.scanned) {
304 self.scanned = 0;
305 let start = self.offset;
306 self.offset = self.offset.saturating_add(rest.len() - next.len());
307 self.since_dispatch = self.since_dispatch.saturating_add(rest.len() - next.len());
308 self.after_cr = next.is_empty() && rest.last() == Some(&b'\r');
309 let (field, value) = self.line(&String::from_utf8_lossy(line));
310 if let Some(batch) = recording.as_deref_mut() {
311 batch.lines.push(SseLine {
312 span: start..self.offset,
313 content: start..start.saturating_add(line.len()),
314 value: value
315 .map(|at| start.saturating_add(at)..start.saturating_add(line.len())),
316 field,
317 });
318 if let Some(event) = self.ready.pop() {
319 batch.events.push(RecordedSseEvent {
320 event,
321 end: self.offset,
322 });
323 }
324 }
325 rest = next;
326 }
327 self.scanned = rest.len();
328 let consumed = buffer.len() - rest.len();
329 self.buffer = buffer;
330 self.buffer.drain(..consumed);
331 }
332
333 pub fn pending(&self) -> usize {
336 self.since_dispatch.saturating_add(self.buffer.len())
337 }
338
339 fn strip_bom<'a>(&mut self, chunk: &'a [u8]) -> &'a [u8] {
342 if self.bom_done {
343 return chunk;
344 }
345 let mut rest = chunk;
346 while let Some(&byte) = rest.first() {
347 if BOM.get(self.bom_prefix) != Some(&byte) {
348 let matched = std::mem::take(&mut self.bom_prefix);
350 self.bom_done = true;
351 self.buffer
352 .extend_from_slice(BOM.get(..matched).unwrap_or_default());
353 return rest;
354 }
355 self.bom_prefix += 1;
356 rest = rest.get(1..).unwrap_or_default();
357 if self.bom_prefix == BOM.len() {
358 self.offset = self.offset.saturating_add(BOM.len());
359 self.bom_prefix = 0;
360 self.bom_done = true;
361 return rest;
362 }
363 }
364 rest
365 }
366
367 fn line(&mut self, line: &str) -> (SseField, Option<usize>) {
369 if line.is_empty() {
370 self.dispatch();
371 return (SseField::Blank, None);
372 }
373 if line.starts_with(':') {
375 return (SseField::Other, None);
376 }
377 let (field, value, at) = match line.split_once(':') {
378 Some((field, value)) => {
379 let trimmed = value.strip_prefix(' ').unwrap_or(value);
380 (
381 field,
382 trimmed,
383 Some(field.len() + 1 + value.len() - trimmed.len()),
384 )
385 }
386 None => (line, "", None),
387 };
388 let field = match field {
389 "event" => {
390 self.event_type.clear();
391 self.event_type.push_str(value);
392 SseField::Event
393 }
394 "data" => {
395 self.data.push_str(value);
396 self.data.push('\n');
397 SseField::Data
398 }
399 "id" if !value.contains('\0') => {
401 self.last_event_id.clear();
402 self.last_event_id.push_str(value);
403 SseField::Id
404 }
405 "retry" if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) => {
406 self.retry = Some(value.parse().unwrap_or(u64::MAX));
408 SseField::Other
409 }
410 _ => SseField::Other,
411 };
412 (field, at)
413 }
414
415 fn dispatch(&mut self) {
418 self.since_dispatch = 0;
419 if self.data.is_empty() {
420 self.event_type.clear();
421 return;
422 }
423 self.data.pop();
424 let event = if self.event_type.is_empty() {
425 Cow::Borrowed("message")
426 } else {
427 Cow::Owned(std::mem::take(&mut self.event_type))
428 };
429 self.ready.push(SseEvent {
430 event,
431 data: std::mem::take(&mut self.data),
432 id: self.last_event_id.clone(),
433 retry: self.retry.take(),
434 });
435 self.event_type.clear();
436 }
437}
438
439#[derive(Debug, Default)]
442pub struct NdjsonFramer {
443 buffer: Vec<u8>,
444 scanned: usize,
446 ready: Vec<Vec<u8>>,
447}
448
449impl NdjsonFramer {
450 pub fn new() -> Self {
452 Self::default()
453 }
454
455 pub fn push(&mut self, chunk: &[u8]) -> std::vec::Drain<'_, Vec<u8>> {
458 self.buffer.extend_from_slice(chunk);
459 let mut rest = self.buffer.as_slice();
460 while let Some((line, end)) = split_line(rest, self.scanned, |byte| *byte == b'\n') {
461 self.scanned = 0;
462 let line = line.strip_suffix(b"\r").unwrap_or(line);
463 if !line.is_empty() {
464 self.ready.push(line.to_vec());
465 }
466 rest = end.get(1..).unwrap_or_default();
467 }
468 self.scanned = rest.len();
469 let consumed = self.buffer.len() - rest.len();
470 self.buffer.drain(..consumed);
471 self.ready.drain(..)
472 }
473
474 pub fn finish(&mut self) -> Option<Vec<u8>> {
477 let mut line = std::mem::take(&mut self.buffer);
478 if line.last() == Some(&b'\r') {
479 line.pop();
480 }
481 (!line.is_empty()).then_some(line)
482 }
483
484 pub fn pending(&self) -> usize {
486 self.buffer.len()
487 }
488}
489
490fn terminated_line(buffer: &[u8], scanned: usize) -> Option<(&[u8], &[u8])> {
496 let (line, end) = split_line(buffer, scanned, |byte| *byte == b'\n' || *byte == b'\r')?;
497 let rest = end.strip_prefix(b"\r\n").or_else(|| end.get(1..));
498 Some((line, rest.unwrap_or_default()))
499}
500
501fn split_line(buffer: &[u8], scanned: usize, ends: impl Fn(&u8) -> bool) -> Option<(&[u8], &[u8])> {
504 buffer.split_at_checked(scanned + buffer.iter().skip(scanned).position(ends)?)
505}
506
507#[cfg(test)]
508mod tests;