use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Overflow {
Stop,
Skip,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Frame {
Line(Vec<u8>),
TooLarge,
End,
Truncated(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Step {
pub consumed: usize,
pub frame: Option<Frame>,
}
#[derive(Debug, Clone)]
pub struct FrameDecoder {
limit: usize,
overflow: Overflow,
partial: Vec<u8>,
discarding: bool,
}
impl FrameDecoder {
pub fn new(limit: usize, overflow: Overflow) -> Self {
Self {
limit,
overflow,
partial: Vec::new(),
discarding: false,
}
}
pub fn set_limit(&mut self, limit: usize) {
self.limit = limit;
}
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.partial.is_empty() && !self.discarding
}
pub fn feed(&mut self, available: &[u8]) -> Step {
let newline = available.iter().position(|byte| *byte == b'\n');
let take = newline.map_or(available.len(), |index| index + 1);
if !self.discarding {
if self.partial.len().saturating_add(take) > self.limit {
match self.overflow {
Overflow::Stop => {
return Step {
consumed: 0,
frame: Some(Frame::TooLarge),
};
}
Overflow::Skip => {
self.discarding = true;
self.partial.clear();
}
}
} else {
self.partial.extend_from_slice(&available[..take]);
}
}
let frame = newline.map(|_| {
if std::mem::take(&mut self.discarding) {
Frame::TooLarge
} else {
let mut line = std::mem::take(&mut self.partial);
line.pop();
Frame::Line(line)
}
});
Step {
consumed: take,
frame,
}
}
pub fn finish(&mut self) -> Frame {
if std::mem::take(&mut self.discarding) {
Frame::TooLarge
} else if self.partial.is_empty() {
Frame::End
} else {
Frame::Truncated(std::mem::take(&mut self.partial))
}
}
}
pub fn trim_line(mut line: Vec<u8>, max_bytes: usize) -> Option<Vec<u8>> {
while matches!(line.last(), Some(b'\n' | b'\r')) {
line.pop();
}
(line.len() <= max_bytes).then_some(line)
}
pub fn encode_frame(message: &impl Serialize) -> serde_json::Result<Vec<u8>> {
let mut bytes = serde_json::to_vec(message)?;
bytes.push(b'\n');
Ok(bytes)
}
#[cfg(test)]
mod tests;