use crate::outbound::OutboundMessage;
use async_trait::async_trait;
use ironfix_core::message::RawMessage;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SessionId {
pub begin_string: String,
pub sender_comp_id: String,
pub target_comp_id: String,
pub sender_sub_id: Option<String>,
pub target_sub_id: Option<String>,
}
impl SessionId {
#[must_use]
pub fn new(
begin_string: impl Into<String>,
sender_comp_id: impl Into<String>,
target_comp_id: impl Into<String>,
) -> Self {
Self {
begin_string: begin_string.into(),
sender_comp_id: sender_comp_id.into(),
target_comp_id: target_comp_id.into(),
sender_sub_id: None,
target_sub_id: None,
}
}
#[must_use]
pub fn with_sender_sub_id(mut self, sub_id: impl Into<String>) -> Self {
self.sender_sub_id = Some(sub_id.into());
self
}
#[must_use]
pub fn with_target_sub_id(mut self, sub_id: impl Into<String>) -> Self {
self.target_sub_id = Some(sub_id.into());
self
}
}
impl std::fmt::Display for SessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}->{}",
self.begin_string, self.sender_comp_id, self.target_comp_id
)
}
}
#[derive(Debug, Clone)]
pub struct RejectReason {
pub code: u32,
pub text: String,
pub ref_tag: Option<u32>,
}
impl RejectReason {
#[must_use]
pub fn new(code: u32, text: impl Into<String>) -> Self {
Self {
code,
text: text.into(),
ref_tag: None,
}
}
#[must_use]
pub const fn with_ref_tag(mut self, tag: u32) -> Self {
self.ref_tag = Some(tag);
self
}
}
#[async_trait]
pub trait Application: Send + Sync {
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, message: &mut OutboundMessage, session_id: &SessionId);
#[allow(clippy::wrong_self_convention)]
async fn from_admin(
&self,
message: &RawMessage<'_>,
session_id: &SessionId,
) -> Result<(), RejectReason>;
async fn to_app(&self, message: &mut OutboundMessage, session_id: &SessionId);
#[allow(clippy::wrong_self_convention)]
async fn from_app(
&self,
message: &RawMessage<'_>,
session_id: &SessionId,
) -> Result<(), RejectReason>;
}
#[derive(Debug, Default)]
pub struct NoOpApplication;
#[async_trait]
impl Application for NoOpApplication {
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, _message: &mut OutboundMessage, _session_id: &SessionId) {}
async fn from_admin(
&self,
_message: &RawMessage<'_>,
_session_id: &SessionId,
) -> Result<(), RejectReason> {
Ok(())
}
async fn to_app(&self, _message: &mut OutboundMessage, _session_id: &SessionId) {}
async fn from_app(
&self,
_message: &RawMessage<'_>,
_session_id: &SessionId,
) -> Result<(), RejectReason> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_id() {
let id = SessionId::new("FIX.4.4", "SENDER", "TARGET");
assert_eq!(id.begin_string, "FIX.4.4");
assert_eq!(id.sender_comp_id, "SENDER");
assert_eq!(id.target_comp_id, "TARGET");
assert_eq!(id.to_string(), "FIX.4.4:SENDER->TARGET");
}
#[test]
fn test_reject_reason() {
let reason = RejectReason::new(1, "Invalid tag").with_ref_tag(35);
assert_eq!(reason.code, 1);
assert_eq!(reason.text, "Invalid tag");
assert_eq!(reason.ref_tag, Some(35));
}
#[tokio::test]
async fn test_noop_application() {
let app = NoOpApplication;
let session_id = SessionId::new("FIX.4.4", "SENDER", "TARGET");
app.on_create(&session_id).await;
app.on_logon(&session_id).await;
app.on_logout(&session_id).await;
}
}