use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
pub async fn read_line<R: tokio::io::AsyncRead + Unpin>(
reader: &mut BufReader<R>,
) -> Option<Result<String, std::io::Error>> {
loop {
let mut buf = String::new();
match reader.read_line(&mut buf).await {
Err(e) => return Some(Err(e)),
Ok(0) => return None, Ok(_) => {
let trimmed = buf.trim_end_matches(['\n', '\r']);
if !trimmed.is_empty() {
return Some(Ok(trimmed.to_owned()));
}
}
}
}
}
pub async fn write_line<W: tokio::io::AsyncWrite + Unpin>(
writer: &mut W,
value: &serde_json::Value,
) -> Result<(), std::io::Error> {
let serialized = serde_json::to_string(value)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
write_raw_line(writer, &serialized).await
}
pub async fn write_raw_line<W: tokio::io::AsyncWrite + Unpin>(
writer: &mut W,
line: &str,
) -> Result<(), std::io::Error> {
writer.write_all(line.as_bytes()).await?;
writer.write_all(b"\n").await
}
pub struct FrameReader<R> {
inner: tokio::io::Lines<BufReader<R>>,
}
impl<R: tokio::io::AsyncRead + Unpin> FrameReader<R> {
pub fn new(reader: BufReader<R>) -> Self {
Self {
inner: reader.lines(),
}
}
pub async fn next_line(&mut self) -> Option<Result<String, std::io::Error>> {
loop {
match self.inner.next_line().await {
Err(e) => return Some(Err(e)),
Ok(None) => return None,
Ok(Some(line)) => {
if !line.trim().is_empty() {
return Some(Ok(line));
}
}
}
}
}
}