use super::model;
use std::io::ErrorKind;
use tokio::io::{self, AsyncRead, AsyncWrite};
use tokio_util::codec::{Decoder, Encoder, LinesCodec};
pub type Framed<T> = tokio_util::codec::Framed<T, Codec>;
pub fn framed<T>(inner: T) -> Framed<T>
where
T: AsyncRead + AsyncWrite,
{
tokio_util::codec::Framed::new(inner, Codec::default())
}
pub fn framed_with_max_length<T>(inner: T, max_length: usize) -> Framed<T>
where
T: AsyncRead + AsyncWrite,
{
tokio_util::codec::Framed::new(inner, Codec::new_with_max_length(max_length))
}
#[derive(Default)]
pub struct Codec {
inner: LinesCodec,
}
impl Codec {
pub fn new_with_max_length(max_length: usize) -> Codec {
Codec {
inner: LinesCodec::new_with_max_length(max_length),
}
}
}
impl Decoder for Codec {
type Item = model::Message;
type Error = io::Error;
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
self.inner
.decode(src)
.map_err(|e| io::Error::new(ErrorKind::Other, e))? .as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
}
}
impl Encoder<model::Message> for Codec {
type Error = io::Error;
fn encode(
&mut self,
item: model::Message,
dst: &mut bytes::BytesMut,
) -> Result<(), Self::Error> {
self.inner
.encode(serde_json::to_string(&item)?.as_str(), dst)
.map_err(|e| io::Error::new(ErrorKind::Other, e))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::model::{Message, Notification, Request};
use bytes::BytesMut;
use proptest::{prelude::Just, prop_oneof, proptest, strategy::Strategy};
proptest! {
#[test]
fn encoding_a_message_then_decoding_it_yields_the_same_message(initial_message in mk_message()) {
let mut message_as_bytes = BytesMut::default();
let mut codec = Codec::default();
codec.encode(initial_message.clone(), &mut message_as_bytes)?;
let message = codec.decode(&mut message_as_bytes)?;
assert_eq!(message, Some(initial_message));
}
}
fn mk_message() -> impl Strategy<Value = Message> {
prop_oneof![
Just(Message::Request {
request: Request::List
}),
Just(Message::Request {
request: Request::Shutdown
}),
Just(Message::Request {
request: Request::Mount { containers: vec!() },
}),
Just(Message::Notification {
notification: Notification::Shutdown
}),
]
}
}