Skip to main content

quickfix/
message.rs

1use std::{ffi::CString, fmt, mem::ManuallyDrop};
2
3use quickfix_ffi::{
4    FixMessage_addGroup, FixMessage_copy, FixMessage_copyGroup, FixMessage_copyHeader,
5    FixMessage_copyTrailer, FixMessage_delete, FixMessage_fromString, FixMessage_getField,
6    FixMessage_getGroupRef, FixMessage_getHeaderRef, FixMessage_getStringLen,
7    FixMessage_getTrailerRef, FixMessage_new, FixMessage_readString, FixMessage_removeField,
8    FixMessage_setField, FixMessage_t,
9};
10
11use crate::{
12    group::Group,
13    header::Header,
14    trailer::Trailer,
15    utils::{ffi_code_to_result, read_checked_cstr},
16    FieldMap, IntoFixValue, QuickFixError,
17};
18
19/// Base class for all FIX messages.
20pub struct Message(pub(crate) FixMessage_t);
21
22impl Message {
23    /// Create new empty struct.
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Try create new struct from raw text message.
29    pub fn try_from_text(text: &str) -> Result<Self, QuickFixError> {
30        let ffi_text = CString::new(text)?;
31        unsafe { FixMessage_fromString(ffi_text.as_ptr()) }
32            .map(Self)
33            .ok_or_else(QuickFixError::from_last_error)
34    }
35
36    /// Try reading underlying struct buffer as a vector of bytes.
37    ///
38    /// # Performances
39    ///
40    /// Do not use this method in latency sensitive code.
41    ///
42    /// String will be generated twice in C++ code:
43    /// - Once for getting a safe buffer length.
44    /// - Then to copy buffer to rust "memory".
45    pub fn to_fix_raw_bytes(&self) -> Result<Vec<u8>, QuickFixError> {
46        unsafe {
47            // Prepare output buffer
48            let buffer_len = FixMessage_getStringLen(self.0)
49                .try_into()
50                .map_err(|_err| QuickFixError::from_last_error())?;
51
52            // Allocate buffer on rust side
53            let mut buffer = vec![0_u8; buffer_len as usize];
54            assert_eq!(buffer.len(), buffer_len as usize);
55
56            // Read text
57            ffi_code_to_result(FixMessage_readString(
58                self.0,
59                buffer.as_mut_ptr().cast(),
60                buffer_len,
61            ))?;
62
63            Ok(buffer)
64        }
65    }
66
67    /// Try reading underlying struct buffer as a FIX string.
68    ///
69    /// # Performances
70    ///
71    /// Do not use this method in latency sensitive code.
72    ///
73    /// String will be generated twice in C++ code:
74    /// - Once for getting a safe buffer length.
75    /// - Then to copy buffer to rust "memory".
76    pub fn to_fix_string(&self) -> Result<String, QuickFixError> {
77        let buffer = self.to_fix_raw_bytes()?;
78
79        // Convert to String
80        //
81        // NOTE: Here, I deliberately made the choice to drop C weird string / invalid UTF8 string
82        //       content. If this happen, there is not so much we can do about ...
83        //       Returning no error is sometime nicer, than an incomprehensible error.
84        let text = CString::from_vec_with_nul(buffer).unwrap_or_default();
85        Ok(text.to_string_lossy().into())
86    }
87
88    /// Clone struct header part.
89    ///
90    /// # Panic
91    ///
92    /// When memory allocation fail in C++ library.
93    pub fn clone_header(&self) -> Header {
94        unsafe { FixMessage_copyHeader(self.0) }
95            .map(Header)
96            .expect("Fail to allocate new Header")
97    }
98
99    /// Read struct header part.
100    ///
101    /// # Panic
102    ///
103    /// When struct pointer cannot be read from `FIX::Message`. This is
104    /// something that could not be theoretically possible.
105    pub fn with_header<T, F>(&self, f: F) -> T
106    where
107        F: FnOnce(&Header) -> T,
108    {
109        let ptr =
110            unsafe { FixMessage_getHeaderRef(self.0) }.expect("Fail to get ptr on message header");
111
112        let obj = ManuallyDrop::new(Header(ptr));
113        f(&obj)
114    }
115
116    /// Read or write struct header part.
117    ///
118    /// # Panic
119    ///
120    /// When struct pointer cannot be read from `FIX::Message`. This is
121    /// something that could not be theoretically possible.
122    pub fn with_header_mut<T, F>(&mut self, f: F) -> T
123    where
124        F: FnOnce(&mut Header) -> T,
125    {
126        let ptr =
127            unsafe { FixMessage_getHeaderRef(self.0) }.expect("Fail to get ptr on message header");
128
129        let mut obj = ManuallyDrop::new(Header(ptr));
130        f(&mut obj)
131    }
132
133    /// Clone struct trailer part.
134    ///
135    /// # Panic
136    ///
137    /// When memory allocation fail in C++ library.
138    pub fn clone_trailer(&self) -> Trailer {
139        unsafe { FixMessage_copyTrailer(self.0) }
140            .map(Trailer)
141            .expect("Fail to allocate new Trailer")
142    }
143
144    /// Read struct trailer part.
145    ///
146    /// # Panic
147    ///
148    /// When struct pointer cannot be read from `FIX::Message`. This is
149    /// something that could not be theoretically possible.
150    pub fn with_trailer<T, F>(&self, f: F) -> T
151    where
152        F: FnOnce(&Trailer) -> T,
153    {
154        let ptr = unsafe { FixMessage_getTrailerRef(self.0) }
155            .expect("Fail to get ptr on message trailer");
156
157        let obj = ManuallyDrop::new(Trailer(ptr));
158        f(&obj)
159    }
160
161    /// Read or write struct trailer part.
162    ///
163    /// # Panic
164    ///
165    /// When struct pointer cannot be read from `FIX::Message`. This is
166    /// something that could not be theoretically possible.
167    pub fn with_trailer_mut<T, F>(&mut self, f: F) -> T
168    where
169        F: FnOnce(&mut Trailer) -> T,
170    {
171        let ptr = unsafe { FixMessage_getTrailerRef(self.0) }
172            .expect("Fail to get ptr on message trailer");
173
174        let mut obj = ManuallyDrop::new(Trailer(ptr));
175        f(&mut obj)
176    }
177
178    /// Read struct group part for a given tag and group index.
179    pub fn with_group<T, F>(&self, index: i32, tag: i32, f: F) -> Option<T>
180    where
181        F: FnOnce(&Group) -> T,
182    {
183        if let Some(ptr) = unsafe { FixMessage_getGroupRef(self.0, index, tag) } {
184            let obj = ManuallyDrop::new(Group(ptr));
185            Some(f(&obj))
186        } else {
187            None
188        }
189    }
190
191    /// Read or write struct group part for a given tag and group index.
192    pub fn with_group_mut<T, F>(&mut self, index: i32, tag: i32, f: F) -> Option<T>
193    where
194        F: FnOnce(&mut Group) -> T,
195    {
196        if let Some(ptr) = unsafe { FixMessage_getGroupRef(self.0, index, tag) } {
197            let mut obj = ManuallyDrop::new(Group(ptr));
198            Some(f(&mut obj))
199        } else {
200            None
201        }
202    }
203}
204
205impl FieldMap for Message {
206    fn get_field(&self, tag: i32) -> Option<String> {
207        unsafe { FixMessage_getField(self.0, tag) }.map(read_checked_cstr)
208    }
209
210    fn set_field<V: IntoFixValue>(&mut self, tag: i32, value: V) -> Result<(), QuickFixError> {
211        let fix_value = value.into_fix_value()?;
212        ffi_code_to_result(unsafe { FixMessage_setField(self.0, tag, fix_value.as_ptr()) })
213    }
214
215    fn remove_field(&mut self, tag: i32) -> Result<(), QuickFixError> {
216        ffi_code_to_result(unsafe { FixMessage_removeField(self.0, tag) })
217    }
218
219    fn add_group(&mut self, group: &Group) -> Result<(), QuickFixError> {
220        ffi_code_to_result(unsafe { FixMessage_addGroup(self.0, group.0) })?;
221        Ok(())
222    }
223
224    fn clone_group(&self, index: i32, tag: i32) -> Option<Group> {
225        unsafe { FixMessage_copyGroup(self.0, index, tag) }.map(Group)
226    }
227}
228
229impl Clone for Message {
230    fn clone(&self) -> Self {
231        Self(unsafe { FixMessage_copy(self.0) }.expect("Fail to clone Message"))
232    }
233}
234
235impl fmt::Debug for Message {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        let mut printer = f.debug_tuple("Message");
238
239        if let Ok(txt) = self.to_fix_string() {
240            printer.field(&txt.replace(1 as char, "|"));
241        }
242
243        printer.finish()
244    }
245}
246
247impl Default for Message {
248    fn default() -> Self {
249        unsafe { FixMessage_new() }
250            .map(Self)
251            .expect("Fail to allocate new Message")
252    }
253}
254
255impl Drop for Message {
256    fn drop(&mut self) {
257        unsafe { FixMessage_delete(self.0) }
258    }
259}