use serde_json::Value;
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
pub const DEFAULT_MAX_MESSAGE_BYTES: usize = 1024 * 1024;
pub const MAX_FRAME_BYTES_LIMIT: usize = 64 * 1024 * 1024;
#[derive(Debug, thiserror::Error)]
pub enum FrameError {
#[error("frame exceeds {0} bytes")]
Oversized(usize),
#[error("invalid json: {0}")]
Invalid(serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}
pub async fn write_frame<W: AsyncWrite + Unpin>(
writer: &mut W,
value: &Value,
) -> std::io::Result<()> {
let mut bytes = serde_json::to_vec(value).map_err(std::io::Error::other)?;
bytes.push(b'\n');
writer.write_all(&bytes).await?;
writer.flush().await
}
pub async fn read_frame<R: AsyncRead + Unpin>(
reader: &mut BufReader<R>,
max_bytes: usize,
) -> std::io::Result<Option<Result<Value, FrameError>>> {
let mut line = Vec::new();
loop {
let (chunk, complete) = {
let available = reader.fill_buf().await?;
if available.is_empty() {
return Ok(if line.is_empty() {
None
} else {
Some(serde_json::from_slice::<Value>(&line).map_err(FrameError::Invalid))
});
}
let take = available
.iter()
.position(|byte| *byte == b'\n')
.map(|pos| pos + 1)
.unwrap_or(available.len());
(available[..take].to_vec(), available[take - 1] == b'\n')
};
line.extend_from_slice(&chunk);
reader.consume(chunk.len());
if line.len() > max_bytes {
if complete {
return Ok(Some(Err(FrameError::Oversized(line.len()))));
}
loop {
let (take, complete) = {
let available = reader.fill_buf().await?;
if available.is_empty() {
return Ok(Some(Err(FrameError::Oversized(line.len()))));
}
let take = available
.iter()
.position(|byte| *byte == b'\n')
.map(|pos| pos + 1)
.unwrap_or(available.len());
(take, available[take - 1] == b'\n')
};
reader.consume(take);
if complete {
return Ok(Some(Err(FrameError::Oversized(line.len()))));
}
}
}
if complete {
while line.last() == Some(&b'\n') || line.last() == Some(&b'\r') {
line.pop();
}
if line.is_empty() {
continue;
}
return Ok(Some(
serde_json::from_slice::<Value>(&line).map_err(FrameError::Invalid),
));
}
}
}