use std::{any::Any, ffi, marker::PhantomData, mem::ManuallyDrop, panic::catch_unwind};
use quickfix_ffi::{
FixApplicationCallbacks_t, FixApplication_delete, FixApplication_new, FixApplication_t,
FixMessage_t, FixSessionID_t,
};
use crate::{Message, QuickFixError, SessionId};
#[derive(Debug)]
pub enum MsgToAppError {
DoNotSend,
}
#[derive(Debug)]
pub enum MsgFromAdminError {
FieldNotFound,
IncorrectDataFormat,
IncorrectTagValue,
RejectLogon,
}
#[derive(Debug)]
pub enum MsgFromAppError {
FieldNotFound,
IncorrectDataFormat,
IncorrectTagValue,
UnsupportedMessageType,
}
trait AsFixCallbackCode {
fn as_callback_code(&self) -> i8;
}
fn callback_to_code<T: AsFixCallbackCode>(input: Result<Result<(), T>, Box<dyn Any + Send>>) -> i8 {
match input {
Err(_) => 0, Ok(Ok(())) => 0, Ok(Err(x)) => x.as_callback_code(), }
}
impl AsFixCallbackCode for MsgToAppError {
fn as_callback_code(&self) -> i8 {
quickfix_ffi::CALLBACK_RESULT_DO_NOT_SEND
}
}
impl AsFixCallbackCode for MsgFromAdminError {
fn as_callback_code(&self) -> i8 {
match self {
Self::FieldNotFound => quickfix_ffi::CALLBACK_RESULT_FIELD_NOT_FOUND,
Self::IncorrectDataFormat => quickfix_ffi::CALLBACK_RESULT_INCORRECT_DATA_FORMAT,
Self::IncorrectTagValue => quickfix_ffi::CALLBACK_RESULT_INCORRECT_TAG_VALUE,
Self::RejectLogon => quickfix_ffi::CALLBACK_RESULT_REJECT_LOGON,
}
}
}
impl AsFixCallbackCode for MsgFromAppError {
fn as_callback_code(&self) -> i8 {
match self {
Self::FieldNotFound => quickfix_ffi::CALLBACK_RESULT_FIELD_NOT_FOUND,
Self::IncorrectDataFormat => quickfix_ffi::CALLBACK_RESULT_INCORRECT_DATA_FORMAT,
Self::IncorrectTagValue => quickfix_ffi::CALLBACK_RESULT_INCORRECT_TAG_VALUE,
Self::UnsupportedMessageType => quickfix_ffi::CALLBACK_RESULT_UNSUPPORTED_MESSAGE_TYPE,
}
}
}
#[allow(unused_variables)]
pub trait ApplicationCallback {
fn on_create(&self, session: &SessionId) {}
fn on_logon(&self, session: &SessionId) {}
fn on_logout(&self, session: &SessionId) {}
fn on_msg_to_admin(&self, msg: &mut Message, session: &SessionId) {}
fn on_msg_to_app(&self, msg: &mut Message, session: &SessionId) -> Result<(), MsgToAppError> {
Ok(())
}
fn on_msg_from_admin(
&self,
msg: &Message,
session: &SessionId,
) -> Result<(), MsgFromAdminError> {
Ok(())
}
fn on_msg_from_app(&self, msg: &Message, session: &SessionId) -> Result<(), MsgFromAppError> {
Ok(())
}
}
#[derive(Debug)]
pub struct Application<'a, C: ApplicationCallback>(pub(crate) FixApplication_t, PhantomData<&'a C>);
impl<'a, C> Application<'a, C>
where
C: ApplicationCallback + 'static,
{
pub fn try_new(callbacks: &'a C) -> Result<Self, QuickFixError> {
match unsafe {
FixApplication_new(
callbacks as *const C as *const ffi::c_void,
&Self::CALLBACKS,
)
} {
Some(fix_application) => Ok(Self(fix_application, PhantomData)),
None => Err(QuickFixError::from_last_error()),
}
}
const CALLBACKS: FixApplicationCallbacks_t = FixApplicationCallbacks_t {
onCreate: Self::on_create,
onLogon: Self::on_logon,
onLogout: Self::on_logout,
toAdmin: Self::to_admin,
toApp: Self::to_app,
fromAdmin: Self::from_admin,
fromApp: Self::from_app,
};
extern "C" fn on_create(data: *const ffi::c_void, session: FixSessionID_t) {
let session_id = ManuallyDrop::new(SessionId(session));
let _ = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
this.on_create(&session_id);
});
}
extern "C" fn on_logon(data: *const ffi::c_void, session: FixSessionID_t) {
let session_id = ManuallyDrop::new(SessionId(session));
let _ = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
this.on_logon(&session_id);
});
}
extern "C" fn on_logout(data: *const ffi::c_void, session: FixSessionID_t) {
let session_id = ManuallyDrop::new(SessionId(session));
let _ = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
this.on_logout(&session_id);
});
}
extern "C" fn to_admin(data: *const ffi::c_void, msg: FixMessage_t, session: FixSessionID_t) {
let session_id = ManuallyDrop::new(SessionId(session));
let _ = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
let mut msg = ManuallyDrop::new(Message(msg));
this.on_msg_to_admin(&mut msg, &session_id);
});
}
extern "C" fn to_app(
data: *const ffi::c_void,
msg: FixMessage_t,
session: FixSessionID_t,
) -> i8 {
let session_id = ManuallyDrop::new(SessionId(session));
let output_code = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
let mut msg = ManuallyDrop::new(Message(msg));
this.on_msg_to_app(&mut msg, &session_id)
});
callback_to_code(output_code)
}
extern "C" fn from_admin(
data: *const ffi::c_void,
msg: FixMessage_t,
session: FixSessionID_t,
) -> i8 {
let msg = ManuallyDrop::new(Message(msg));
let session_id = ManuallyDrop::new(SessionId(session));
let output_code = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
this.on_msg_from_admin(&msg, &session_id)
});
callback_to_code(output_code)
}
extern "C" fn from_app(
data: *const ffi::c_void,
msg: FixMessage_t,
session: FixSessionID_t,
) -> i8 {
let msg = ManuallyDrop::new(Message(msg));
let session_id = ManuallyDrop::new(SessionId(session));
let output_code = catch_unwind(|| {
let this = unsafe { &*(data as *const C) };
this.on_msg_from_app(&msg, &session_id)
});
callback_to_code(output_code)
}
}
impl<C: ApplicationCallback> Drop for Application<'_, C> {
fn drop(&mut self) {
unsafe { FixApplication_delete(self.0) };
}
}