diff --git a/quickfix-bind/include/quickfix_bind.h b/quickfix-bind/include/quickfix_bind.h index e5a54c9..c2eb475 100644 --- a/quickfix-bind/include/quickfix_bind.h +++ b/quickfix-bind/include/quickfix_bind.h @@ -62,6 +62,8 @@ extern "C" FixMessage_t *FixMessage_new(); int FixMessage_setField(const FixMessage_t *obj, int tag, const char *value); + const char *FixMessage_getField(const FixMessage_t *obj, int tag); + int FixMessage_removeField(const FixMessage_t *obj, int tag); int FixMessage_toBuffer(const FixMessage_t *obj, char *buffer, size_t length); void FixMessage_delete(FixMessage_t *obj); diff --git a/quickfix-bind/src/quickfix_bind.cpp b/quickfix-bind/src/quickfix_bind.cpp index 315a86d..d2f957e 100644 --- a/quickfix-bind/src/quickfix_bind.cpp +++ b/quickfix-bind/src/quickfix_bind.cpp @@ -379,7 +379,8 @@ extern "C" } } - int FixMessage_setField(const FixMessage_t *obj, int tag, const char *value) + int + FixMessage_setField(const FixMessage_t *obj, int tag, const char *value) { RETURN_VAL_IF_NULL(obj, ERRNO_INVAL); RETURN_VAL_IF_NULL(value, ERRNO_INVAL); @@ -396,7 +397,41 @@ extern "C" return 0; } - int FixMessage_toBuffer(const FixMessage_t *obj, char *buffer, size_t length) + const char * + FixMessage_getField(const FixMessage_t *obj, int tag) + { + RETURN_VAL_IF_NULL(obj, NULL); + + auto fix_obj = (FIX::Message *)(obj); + try + { + return fix_obj->getField(tag).c_str(); + } + catch (std::exception &ex) + { + return NULL; + } + } + + int + FixMessage_removeField(const FixMessage_t *obj, int tag) + { + RETURN_VAL_IF_NULL(obj, ERRNO_INVAL); + + auto fix_obj = (FIX::Message *)(obj); + try + { + fix_obj->removeField(tag); + } + catch (std::exception &ex) + { + return ERRNO_EXCEPTION; + } + return 0; + } + + int + FixMessage_toBuffer(const FixMessage_t *obj, char *buffer, size_t length) { if (length == 0) return 0; diff --git a/quickfix-ffi/src/lib.rs b/quickfix-ffi/src/lib.rs index 60443e8..60d0872 100644 --- a/quickfix-ffi/src/lib.rs +++ b/quickfix-ffi/src/lib.rs @@ -98,6 +98,10 @@ extern "C" { value: *const ffi::c_char, ) -> ffi::c_int; #[must_use] + pub fn FixMessage_getField(obj: FixMessage_t, tag: ffi::c_int) -> Option>; + #[must_use] + pub fn FixMessage_removeField(obj: FixMessage_t, tag: ffi::c_int) -> ffi::c_int; + #[must_use] pub fn FixMessage_toBuffer( obj: FixMessage_t, buffer: *mut ffi::c_char, diff --git a/quickfix/src/message.rs b/quickfix/src/message.rs index 608adc4..dbe2ce0 100644 --- a/quickfix/src/message.rs +++ b/quickfix/src/message.rs @@ -1,8 +1,14 @@ use std::ffi::CString; -use quickfix_ffi::{FixMessage_delete, FixMessage_new, FixMessage_setField, FixMessage_toBuffer}; +use quickfix_ffi::{ + FixMessage_delete, FixMessage_getField, FixMessage_new, FixMessage_removeField, + FixMessage_setField, FixMessage_toBuffer, +}; -use crate::{utils::read_buffer_to_string, QuickFixError}; +use crate::{ + utils::{read_buffer_to_string, read_checked_cstr}, + QuickFixError, +}; #[derive(Debug)] pub struct Message(pub(crate) quickfix_ffi::FixMessage_t); @@ -24,6 +30,17 @@ impl Message { } } + pub fn get_field(&self, tag: i32) -> Option { + unsafe { FixMessage_getField(self.0, tag) }.map(read_checked_cstr) + } + + pub fn remove_field(&self, tag: i32) -> Result<(), QuickFixError> { + match unsafe { FixMessage_removeField(self.0, tag) } { + 0 => Ok(()), + code => Err(QuickFixError::InvalidFunctionReturnCode(code)), + } + } + pub fn as_string(&self) -> Result { self.as_string_with_len(4096 /* 1 page */) } diff --git a/quickfix/src/session_id.rs b/quickfix/src/session_id.rs index e0a7ea8..64b6230 100644 --- a/quickfix/src/session_id.rs +++ b/quickfix/src/session_id.rs @@ -1,52 +1,28 @@ -use std::ffi::CStr; - use quickfix_ffi::{ FixSessionID_getBeginString, FixSessionID_getSenderCompID, FixSessionID_getSessionQualifier, FixSessionID_getTargetCompID, FixSessionID_isFIXT, }; +use crate::utils::read_checked_cstr; + #[derive(Debug)] pub struct SessionId(pub(crate) quickfix_ffi::FixSessionID_t); impl SessionId { pub fn get_begin_string(&self) -> Option { - match unsafe { FixSessionID_getBeginString(self.0) } { - Some(val) => { - let cstr = unsafe { CStr::from_ptr(val.as_ptr()) }; - Some(String::from_utf8_lossy(cstr.to_bytes()).to_string()) - } - None => None, - } + unsafe { FixSessionID_getBeginString(self.0) }.map(read_checked_cstr) } pub fn get_sender_comp_id(&self) -> Option { - match unsafe { FixSessionID_getSenderCompID(self.0) } { - Some(val) => { - let cstr = unsafe { CStr::from_ptr(val.as_ptr()) }; - Some(String::from_utf8_lossy(cstr.to_bytes()).to_string()) - } - None => None, - } + unsafe { FixSessionID_getSenderCompID(self.0) }.map(read_checked_cstr) } pub fn get_target_comp_id(&self) -> Option { - match unsafe { FixSessionID_getTargetCompID(self.0) } { - Some(val) => { - let cstr = unsafe { CStr::from_ptr(val.as_ptr()) }; - Some(String::from_utf8_lossy(cstr.to_bytes()).to_string()) - } - None => None, - } + unsafe { FixSessionID_getTargetCompID(self.0) }.map(read_checked_cstr) } pub fn get_session_qualifier(&self) -> Option { - match unsafe { FixSessionID_getSessionQualifier(self.0) } { - Some(val) => { - let cstr = unsafe { CStr::from_ptr(val.as_ptr()) }; - Some(String::from_utf8_lossy(cstr.to_bytes()).to_string()) - } - None => None, - } + unsafe { FixSessionID_getSessionQualifier(self.0) }.map(read_checked_cstr) } pub fn is_fixt(&self) -> bool { diff --git a/quickfix/src/utils.rs b/quickfix/src/utils.rs index 0ca819d..c640e97 100644 --- a/quickfix/src/utils.rs +++ b/quickfix/src/utils.rs @@ -1,4 +1,14 @@ +use std::{ + ffi::{self, CStr}, + ptr::NonNull, +}; + pub fn read_buffer_to_string(buffer: &[u8]) -> String { let null_index = buffer.iter().position(|x| *x == 0).unwrap_or(buffer.len()); String::from_utf8_lossy(&buffer[..null_index]).to_string() } + +pub fn read_checked_cstr(val: NonNull) -> String { + let cstr = unsafe { CStr::from_ptr(val.as_ptr()) }; + String::from_utf8_lossy(cstr.to_bytes()).to_string() +} diff --git a/quickfix/tests/test_messages.rs b/quickfix/tests/test_messages.rs index 0f24306..e8221ef 100644 --- a/quickfix/tests/test_messages.rs +++ b/quickfix/tests/test_messages.rs @@ -16,3 +16,44 @@ fn test_set_field() { Ok("9=14\u{1}42=foo\u{1}56=bar\u{1}10=162\u{1}") ); } + +#[test] +fn test_set_field_twice() { + let mut msg = Message::try_new().unwrap(); + + msg.set_field(42, "foo").unwrap(); + assert_eq!( + msg.as_string().as_deref(), + Ok("9=7\u{1}42=foo\u{1}10=150\u{1}") + ); + + msg.set_field(42, "bar").unwrap(); + assert_eq!( + msg.as_string().as_deref(), + Ok("9=7\u{1}42=bar\u{1}10=135\u{1}") + ); +} + +#[test] +fn test_get_field() { + let mut msg = Message::try_new().unwrap(); + assert_eq!(msg.get_field(42), None); + + msg.set_field(42, "hello world").unwrap(); + assert_eq!(msg.get_field(42).as_deref(), Some("hello world")); +} + +#[test] +fn test_remove_field() { + let mut msg = Message::try_new().unwrap(); + assert_eq!(msg.get_field(42), None); + + msg.remove_field(42).unwrap(); + assert_eq!(msg.get_field(42), None); + + msg.set_field(42, "hello world").unwrap(); + assert_eq!(msg.get_field(42).as_deref(), Some("hello world")); + + msg.remove_field(42).unwrap(); + assert_eq!(msg.get_field(42), None); +}