Skip to content

Commit

Permalink
♻️ Use 'PhantomData<&'a ()>' to borrow session from SocketAcceptor / …
Browse files Browse the repository at this point in the history
…SocketInitiator

Make API much nicer
  • Loading branch information
arthurlm committed Oct 7, 2024
1 parent da9095a commit 4a179a1
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 57 deletions.
4 changes: 1 addition & 3 deletions quickfix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,7 @@ pub trait SessionContainer {
/// Borrow mutable session to the container.
///
/// Session is lookup using its ID.
fn with_session_mut<F, T>(&self, session_id: SessionId, f: F) -> Result<T, QuickFixError>
where
F: FnOnce(&mut Session) -> T;
fn session(&self, session_id: SessionId) -> Result<Session<'_>, QuickFixError>;
}

/// Convert object to FIX value.
Expand Down
26 changes: 17 additions & 9 deletions quickfix/src/session.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, marker::PhantomData};

use quickfix_ffi::{
FixSession_isLoggedOn, FixSession_logout, FixSession_lookup, FixSession_send,
Expand All @@ -17,42 +17,50 @@ pub fn send_to_target(msg: Message, session_id: &SessionId) -> Result<(), QuickF
}

/// FIX Session.
pub struct Session(pub(crate) FixSession_t);
pub struct Session<'a> {
pub(crate) inner: FixSession_t,
pub(crate) phantom_container: PhantomData<&'a ()>,
}

impl Session {
impl Session<'static> {
/// Find a session by its ID.
///
/// # Safety
///
/// Function is unsafe because there is no way to bind FIX session lifetime
/// to rust session lifetime.
///
/// Use `SessionContainer::with_session_mut` instead. It will give you a safe scope
/// Use `SessionContainer::session` instead. It will give you a safe scope
/// where session has been borrowed to the acceptor / initiator.
pub unsafe fn lookup(session_id: &SessionId) -> Result<Self, QuickFixError> {
match unsafe { FixSession_lookup(session_id.0) } {
Some(session) => Ok(Self(session)),
Some(inner) => Ok(Self {
inner,
phantom_container: PhantomData,
}),
None => Err(QuickFixError::from_last_error()),
}
}
}

impl Session<'_> {
/// Force session logout.
pub fn logout(&mut self) -> Result<(), QuickFixError> {
ffi_code_to_result(unsafe { FixSession_logout(self.0) })
ffi_code_to_result(unsafe { FixSession_logout(self.inner) })
}

/// Check if session is logged on.
pub fn is_logged_on(&mut self) -> Result<bool, QuickFixError> {
ffi_code_to_bool(unsafe { FixSession_isLoggedOn(self.0) })
ffi_code_to_bool(unsafe { FixSession_isLoggedOn(self.inner) })
}

/// Send message using current session.
pub fn send(&mut self, msg: Message) -> Result<bool, QuickFixError> {
ffi_code_to_bool(unsafe { FixSession_send(self.0, msg.0) })
ffi_code_to_bool(unsafe { FixSession_send(self.inner, msg.0) })
}
}

impl fmt::Debug for Session {
impl fmt::Debug for Session<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Session").finish()
}
Expand Down
20 changes: 9 additions & 11 deletions quickfix/src/socket_acceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use quickfix_ffi::{
use crate::{
utils::{ffi_code_to_bool, ffi_code_to_result},
Application, ApplicationCallback, ConnectionHandler, FfiMessageStoreFactory, LogCallback,
LogFactory, QuickFixError, Session, SessionContainer, SessionSettings,
LogFactory, QuickFixError, Session, SessionContainer, SessionId, SessionSettings,
};

/// Socket implementation of incoming connections handler.
Expand Down Expand Up @@ -95,19 +95,17 @@ where
S: FfiMessageStoreFactory,
L: LogCallback,
{
fn with_session_mut<F, T>(&self, session_id: crate::SessionId, f: F) -> Result<T, QuickFixError>
where
F: FnOnce(&mut Session) -> T,
{
let mut session = unsafe {
fn session(&self, session_id: SessionId) -> Result<Session<'_>, QuickFixError> {
unsafe {
FixSocketAcceptor_getSession(self.inner, session_id.0)
.map(Session)
.map(|inner| Session {
inner,
phantom_container: PhantomData,
})
.ok_or_else(|| {
QuickFixError::SessionNotFound(format!("No session found: {session_id:?}"))
})?
};

Ok(f(&mut session))
})
}
}
}

Expand Down
20 changes: 9 additions & 11 deletions quickfix/src/socket_initiator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use quickfix_ffi::{
use crate::{
utils::{ffi_code_to_bool, ffi_code_to_result},
Application, ApplicationCallback, ConnectionHandler, FfiMessageStoreFactory, LogCallback,
LogFactory, QuickFixError, Session, SessionContainer, SessionSettings,
LogFactory, QuickFixError, Session, SessionContainer, SessionId, SessionSettings,
};

/// Socket implementation of establishing connections handler.
Expand Down Expand Up @@ -96,19 +96,17 @@ where
S: FfiMessageStoreFactory,
L: LogCallback,
{
fn with_session_mut<F, T>(&self, session_id: crate::SessionId, f: F) -> Result<T, QuickFixError>
where
F: FnOnce(&mut Session) -> T,
{
let mut session = unsafe {
fn session(&self, session_id: SessionId) -> Result<Session<'_>, QuickFixError> {
unsafe {
FixSocketInitiator_getSession(self.inner, session_id.0)
.map(Session)
.map(|inner| Session {
inner,
phantom_container: PhantomData,
})
.ok_or_else(|| {
QuickFixError::SessionNotFound(format!("No session found: {session_id:?}"))
})?
};

Ok(f(&mut session))
})
}
}
}

Expand Down
44 changes: 21 additions & 23 deletions quickfix/tests/test_send_receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,44 +99,42 @@ fn test_full_fix_application() -> Result<(), QuickFixError> {
// Send a message from one app to the other using `SessionContainer` API.
// - Check first session are not mixable from sender / receiver.
assert_eq!(
socket_sender.with_session_mut(ServerType::Receiver.session_id(), |_session| {
unreachable!();
}),
Err(QuickFixError::SessionNotFound(
socket_sender
.session(ServerType::Receiver.session_id())
.unwrap_err(),
QuickFixError::SessionNotFound(
"No session found: SessionId(\"FIX.4.4:ME->THEIR\")".to_string()
))
)
);

assert_eq!(
socket_receiver.with_session_mut(ServerType::Sender.session_id(), |_session| {
unreachable!();
}),
Err(QuickFixError::SessionNotFound(
socket_receiver
.session(ServerType::Sender.session_id())
.unwrap_err(),
QuickFixError::SessionNotFound(
"No session found: SessionId(\"FIX.4.4:THEIR->ME\")".to_string()
))
)
);

// Then play with API 😎
socket_sender.with_session_mut(ServerType::Sender.session_id(), |session| {
let news = build_news("Hello", &[])?;
session.send(news)?;
let news = build_news("Hello", &[])?;
socket_sender
.session(ServerType::Sender.session_id())?
.send(news)?;

Ok::<_, QuickFixError>(())
})??;
thread::sleep(Duration::from_millis(50));

assert_eq!(sender.user_msg_count(), MsgCounter { sent: 2, recv: 1 });
assert_eq!(receiver.user_msg_count(), MsgCounter { sent: 1, recv: 2 });

socket_receiver.with_session_mut(ServerType::Receiver.session_id(), |session| {
let news = build_news(
"Anyone here",
&["This news have", "some content", "that is very interesting"],
)?;
session.send(news)?;
let news = build_news(
"Anyone here",
&["This news have", "some content", "that is very interesting"],
)?;
socket_receiver
.session(ServerType::Receiver.session_id())?
.send(news)?;

Ok::<_, QuickFixError>(())
})??;
thread::sleep(Duration::from_millis(50));

assert_eq!(sender.user_msg_count(), MsgCounter { sent: 2, recv: 2 });
Expand Down

0 comments on commit 4a179a1

Please sign in to comment.