use std::collections::VecDeque;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use async_trait::async_trait;
use derive_builder::Builder;
use crate::connector::Connector;
use crate::muxed::Muxed;
#[derive(Debug, Clone, PartialEq, Builder)]
pub struct MockRequest<Addr, Req, Resp, Ctx, E> {
to: Addr,
req: Req,
ctx: Option<Ctx>,
resp: Result<(Resp, Ctx), E>,
delay: Option<Duration>,
}
impl<Addr, Req, Resp, Ctx, E> MockRequest<Addr, Req, Resp, Ctx, E> {
pub fn new(to: Addr, req: Req, resp: Result<(Resp, Ctx), E>) -> Self {
MockRequest {
to,
req,
resp,
ctx: None,
delay: None,
}
}
pub fn with_context(mut self, ctx: Ctx) -> Self {
self.ctx = Some(ctx);
self
}
}
#[derive(Debug, Clone, PartialEq, Builder)]
pub struct MockResponse<Addr, Resp, Ctx, E> {
to: Addr,
resp: Resp,
err: Option<E>,
ctx: Option<Ctx>,
}
impl<Addr, Resp, Ctx, E> MockResponse<Addr, Resp, Ctx, E> {
pub fn new(to: Addr, resp: Resp, err: Option<E>) -> Self {
MockResponse {
to,
resp,
err,
ctx: None,
}
}
pub fn with_error(mut self, err: E) -> Self {
self.err = Some(err);
self
}
pub fn with_context(mut self, ctx: Ctx) -> Self {
self.ctx = Some(ctx);
self
}
}
pub type MockTransaction<Addr, Req, Resp, Ctx, E> =
Muxed<MockRequest<Addr, Req, Resp, Ctx, E>, MockResponse<Addr, Resp, Ctx, E>>;
impl<Addr, Req, Resp, Ctx, E> MockTransaction<Addr, Req, Resp, Ctx, E> {
pub fn request(
to: Addr, req: Req, resp: Result<(Resp, Ctx), E>,
) -> MockTransaction<Addr, Req, Resp, Ctx, E> {
Muxed::Request(MockRequest::new(to, req, resp))
}
pub fn response(to: Addr, resp: Resp, err: Option<E>) -> MockTransaction<Addr, Req, Resp, Ctx, E> {
Muxed::Response(MockResponse::new(to, resp, err))
}
}
pub struct MockConnector<Addr, Req, Resp, E, Ctx> {
transactions: Arc<Mutex<VecDeque<MockTransaction<Addr, Req, Resp, Ctx, E>>>>,
_ctx: PhantomData<Ctx>,
}
impl<Addr, Req, Resp, E, Ctx> Clone for MockConnector<Addr, Req, Resp, E, Ctx> {
fn clone(&self) -> Self {
MockConnector {
transactions: self.transactions.clone(),
_ctx: PhantomData,
}
}
}
impl<Addr, Req, Resp, E, Ctx> MockConnector<Addr, Req, Resp, E, Ctx>
where
Addr: PartialEq + Debug + Send + 'static,
Req: PartialEq + Debug + Send + 'static,
Resp: PartialEq + Debug + Send + 'static,
E: PartialEq + Debug + Send + 'static,
Ctx: Clone + PartialEq + Debug + Send + 'static,
{
pub fn new() -> MockConnector<Addr, Req, Resp, E, Ctx> {
MockConnector {
transactions: Arc::new(Mutex::new(VecDeque::new())),
_ctx: PhantomData,
}
}
pub fn expect<T>(&mut self, transactions: T) -> Self
where
T: Into<VecDeque<MockTransaction<Addr, Req, Resp, Ctx, E>>>,
{
*self.transactions.lock().unwrap() = transactions.into();
self.clone()
}
pub fn finalise(&mut self) {
let transactions: Vec<_> = self.transactions.lock().unwrap().drain(..).collect();
let expectations = Vec::<MockTransaction<Addr, Req, Resp, Ctx, E>>::new();
assert_eq!(
expectations, transactions,
"not all transactions have been evaluated"
);
}
}
#[async_trait]
impl<Id, Addr, Req, Resp, E, Ctx> Connector<Id, Addr, Req, Resp, E, Ctx>
for MockConnector<Addr, Req, Resp, E, Ctx>
where
Id: PartialEq + Debug + Send + 'static,
Addr: PartialEq + Debug + Send + 'static,
Req: PartialEq + Debug + Send + 'static,
Resp: PartialEq + Debug + Send + 'static,
E: PartialEq + Debug + Send + 'static,
Ctx: Clone + PartialEq + Debug + Send + 'static,
{
async fn request(
&mut self, ctx: Ctx, _id: Id, addr: Addr, req: Req,
) -> Result<Resp, E> {
let mut transactions = self.transactions.lock().unwrap();
let transaction = transactions.pop_front().expect(&format!(
"request error, no more transactions available (request: {:?})",
req
));
let request = transaction.req().expect("expected request");
assert_eq!(request.to, addr, "destination mismatch");
assert_eq!(request.req, req, "request mismatch");
if let Some(c) = request.ctx {
assert_eq!(c, ctx, "context mismatch");
}
match request.resp {
Ok(r) => Ok(r.0),
Err(e) => Err(e),
}
}
async fn respond(
&mut self, ctx: Ctx, _id: Id, addr: Addr, resp: Resp,
) -> Result<(), E> {
let mut transactions = self.transactions.lock().unwrap();
let transaction = transactions.pop_front().expect(&format!(
"response error, no more transactions available (response: {:?})",
resp
));
let response = transaction.resp().expect("expected response");
assert_eq!(response.to, addr, "destination mismatch");
assert_eq!(response.resp, resp, "request mismatch");
if let Some(c) = response.ctx {
assert_eq!(c, ctx, "context mismatch");
}
match response.err {
Some(e) => Err(e),
None => Ok(()),
}
}
}