use async_trait::async_trait;
use tokio::sync::mpsc;
use crate::error::RejectError;
use crate::message::Message;
use crate::session_id::SessionId;
#[derive(Debug)]
pub enum ApplicationError {
RejectLogon(String),
Reject(RejectError),
UnsupportedMessageType,
}
#[derive(Debug)]
pub struct DoNotSend;
#[async_trait]
#[allow(clippy::wrong_self_convention)] pub trait Application: Send + Sync + 'static {
async fn on_create(&self, _session_id: &SessionId) {}
async fn on_logon(&self, _session_id: &SessionId) {}
async fn on_logout(&self, _session_id: &SessionId) {}
async fn to_admin(&self, _msg: &mut Message, _session_id: &SessionId) {}
async fn to_app(
&self,
_msg: &mut Message,
_session_id: &SessionId,
) -> Result<(), DoNotSend> {
Ok(())
}
async fn from_admin(
&self,
_msg: &Message,
_session_id: &SessionId,
) -> Result<(), ApplicationError> {
Ok(())
}
async fn from_app(
&self,
_msg: &Message,
_session_id: &SessionId,
) -> Result<(), ApplicationError> {
Ok(())
}
}
#[derive(Debug)]
pub enum SessionEvent {
Created(SessionId),
LoggedOn(SessionId),
LoggedOut(SessionId),
App(Message, SessionId),
Admin(Message, SessionId),
}
pub struct ChannelApplication {
tx: mpsc::UnboundedSender<SessionEvent>,
}
pub fn event_channel() -> (ChannelApplication, mpsc::UnboundedReceiver<SessionEvent>) {
let (tx, rx) = mpsc::unbounded_channel();
(ChannelApplication { tx }, rx)
}
#[async_trait]
impl Application for ChannelApplication {
async fn on_create(&self, session_id: &SessionId) {
let _ = self.tx.send(SessionEvent::Created(session_id.clone()));
}
async fn on_logon(&self, session_id: &SessionId) {
let _ = self.tx.send(SessionEvent::LoggedOn(session_id.clone()));
}
async fn on_logout(&self, session_id: &SessionId) {
let _ = self.tx.send(SessionEvent::LoggedOut(session_id.clone()));
}
async fn from_admin(
&self,
msg: &Message,
session_id: &SessionId,
) -> Result<(), ApplicationError> {
let _ = self.tx.send(SessionEvent::Admin(msg.clone(), session_id.clone()));
Ok(())
}
async fn from_app(
&self,
msg: &Message,
session_id: &SessionId,
) -> Result<(), ApplicationError> {
let _ = self.tx.send(SessionEvent::App(msg.clone(), session_id.clone()));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn event_channel_forwards_notifications() {
let (app, mut rx) = event_channel();
let sid = SessionId::new("FIX.4.4", "ME", "YOU");
app.on_logon(&sid).await;
let mut msg = Message::default();
msg.set(35, "D"); app.from_app(&msg, &sid).await.unwrap();
app.on_logout(&sid).await;
assert!(matches!(rx.recv().await, Some(SessionEvent::LoggedOn(id)) if id == sid));
assert!(matches!(rx.recv().await, Some(SessionEvent::App(_, id)) if id == sid));
assert!(matches!(rx.recv().await, Some(SessionEvent::LoggedOut(id)) if id == sid));
}
#[tokio::test]
async fn send_never_blocks_and_survives_dropped_receiver() {
let (app, rx) = event_channel();
let sid = SessionId::new("FIX.4.4", "ME", "YOU");
drop(rx); app.on_logon(&sid).await;
app.from_app(&Message::default(), &sid).await.unwrap();
}
}