use anyhow::Result;
use bytes::{Bytes, BytesMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FramerKind {
#[default]
Ndjson,
}
impl FramerKind {
pub fn into_framer(self) -> NdjsonFramer {
match self {
FramerKind::Ndjson => NdjsonFramer,
}
}
}
pub trait Framer: Send + Sync {
fn write_message(&self, payload: &[u8], out: &mut Vec<u8>);
fn next_message(&self, buf: &mut BytesMut) -> Result<Option<Bytes>>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NdjsonFramer;
impl Framer for NdjsonFramer {
fn write_message(&self, payload: &[u8], out: &mut Vec<u8>) {
out.extend_from_slice(payload);
out.push(b'\n');
}
fn next_message(&self, buf: &mut BytesMut) -> Result<Option<Bytes>> {
let Some(pos) = buf.iter().position(|&b| b == b'\n') else {
return Ok(None);
};
if pos > 0 && buf[pos - 1] == b'\r' {
let mut line = buf.split_to(pos + 1);
let payload = line.split_to(pos - 1).freeze();
Ok(Some(payload))
} else {
let mut line = buf.split_to(pos + 1);
let payload = line.split_to(pos).freeze();
Ok(Some(payload))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ndjson_roundtrip_single_and_multi() {
let framer = NdjsonFramer;
let mut out = Vec::new();
framer.write_message(br#"{"a":1}"#, &mut out);
framer.write_message(br#"{"b":2}"#, &mut out);
let mut buf = BytesMut::from(out.as_slice());
let m1 = framer.next_message(&mut buf).unwrap().expect("msg1");
let m2 = framer.next_message(&mut buf).unwrap().expect("msg2");
assert_eq!(&m1[..], br#"{"a":1}"#);
assert_eq!(&m2[..], br#"{"b":2}"#);
assert!(framer.next_message(&mut buf).unwrap().is_none());
assert!(buf.is_empty());
}
#[test]
fn ndjson_incomplete_buffer() {
let framer = NdjsonFramer;
let mut buf = BytesMut::from(&b"{\"partial\":tru"[..]);
assert!(framer.next_message(&mut buf).unwrap().is_none());
assert_eq!(&buf[..], b"{\"partial\":tru");
buf.extend_from_slice(b"e}\n");
let m = framer.next_message(&mut buf).unwrap().expect("complete");
assert_eq!(&m[..], br#"{"partial":true}"#);
assert!(buf.is_empty());
}
#[test]
fn ndjson_empty_payload_line() {
let framer = NdjsonFramer;
let mut buf = BytesMut::from(&b"\n"[..]);
let m = framer.next_message(&mut buf).unwrap().expect("empty line");
assert!(m.is_empty());
}
#[test]
fn ndjson_crlf() {
let framer = NdjsonFramer;
let mut buf = BytesMut::from(&b"hello\r\n"[..]);
let m = framer.next_message(&mut buf).unwrap().expect("crlf");
assert_eq!(&m[..], b"hello");
}
}