deribit_fix/model/
message.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6use crate::DeribitFixError;
7use crate::model::types::MsgType;
8use std::str::FromStr;
9
10/// FIX message representation
11#[derive(Clone)]
12pub struct FixMessage {
13    /// FIX message fields as (tag, value) pairs
14    pub fields: Vec<(u32, String)>,
15    /// Raw message string
16    pub raw_message: String,
17}
18
19impl FixMessage {
20    /// Create a new empty FIX message
21    pub fn new() -> Self {
22        Self {
23            fields: Vec::new(),
24            raw_message: String::new(),
25        }
26    }
27
28    /// Parse a FIX message from a string
29    pub fn parse(raw_message: &str) -> crate::Result<Self> {
30        let mut fields = Vec::new();
31        for part in raw_message.split('\x01').filter(|s| !s.is_empty()) {
32            let mut pair = part.splitn(2, '=');
33            if let (Some(tag_str), Some(value)) = (pair.next(), pair.next()) {
34                if let Ok(tag) = tag_str.parse::<u32>() {
35                    fields.push((tag, value.to_string()));
36                } else {
37                    return Err(DeribitFixError::MessageParsing(format!(
38                        "Invalid tag: {tag_str}"
39                    )));
40                }
41            } else {
42                return Err(DeribitFixError::MessageParsing(format!(
43                    "Invalid field: {part}"
44                )));
45            }
46        }
47
48        Ok(Self {
49            fields,
50            raw_message: raw_message.to_string(),
51        })
52    }
53
54    /// Get a field value by tag
55    pub fn get_field(&self, tag: u32) -> Option<&String> {
56        self.fields.iter().find(|(t, _)| *t == tag).map(|(_, v)| v)
57    }
58
59    /// Set a field value
60    pub fn set_field(&mut self, tag: u32, value: String) {
61        if let Some(field) = self.fields.iter_mut().find(|(t, _)| *t == tag) {
62            field.1 = value;
63        } else {
64            self.fields.push((tag, value));
65        }
66    }
67
68    /// Get message type
69    pub fn msg_type(&self) -> Option<MsgType> {
70        self.get_field(35).and_then(|s| s.parse().ok())
71    }
72
73    /// Get sender company ID
74    pub fn sender_comp_id(&self) -> Option<&String> {
75        self.get_field(49)
76    }
77
78    /// Get target company ID
79    pub fn target_comp_id(&self) -> Option<&String> {
80        self.get_field(56)
81    }
82
83    /// Get message sequence number
84    pub fn msg_seq_num(&self) -> Option<u32> {
85        self.get_field(34)?.parse().ok()
86    }
87
88    /// Check if a field exists
89    pub fn has_field(&self, tag: u32) -> bool {
90        self.fields.iter().any(|(t, _)| *t == tag)
91    }
92
93    /// Calculate checksum for the message
94    pub fn calculate_checksum(&self) -> u8 {
95        // Build message string without checksum field (tag 10), sorted by tag number
96        let mut field_pairs: Vec<_> = self.fields.iter().collect();
97        field_pairs.sort_by_key(|(tag, _)| *tag);
98
99        let mut message_parts = Vec::new();
100
101        // Add all fields except checksum (tag 10)
102        for (tag, value) in field_pairs {
103            if *tag != 10 {
104                // Exclude checksum field
105                message_parts.push(format!("{tag}={value}"));
106            }
107        }
108
109        let message_str = message_parts.join("\x01") + "\x01";
110        let bytes = message_str.as_bytes();
111        let mut checksum: u32 = 0;
112
113        // Sum all bytes in the message
114        for &byte in bytes {
115            checksum += byte as u32;
116        }
117
118        (checksum % 256) as u8
119    }
120}
121
122impl FromStr for FixMessage {
123    type Err = DeribitFixError;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        Self::parse(s)
127    }
128}
129
130impl Default for FixMessage {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136impl std::fmt::Display for FixMessage {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "{}", self.raw_message)
139    }
140}
141
142impl std::fmt::Debug for FixMessage {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        // Create a readable version by replacing SOH (\x01) with " | "
145        let mut readable_message = self.raw_message.replace('\x01', " | ");
146        // Remove trailing separator if present
147        if readable_message.ends_with(" | ") {
148            readable_message.truncate(readable_message.len() - 3);
149        }
150
151        // Get common FIX field names for better readability
152        let mut field_descriptions = Vec::new();
153        for (tag, value) in &self.fields {
154            let field_name = match *tag {
155                8 => "BeginString",
156                9 => "BodyLength",
157                35 => "MsgType",
158                49 => "SenderCompID",
159                56 => "TargetCompID",
160                34 => "MsgSeqNum",
161                52 => "SendingTime",
162                10 => "CheckSum",
163                11 => "ClOrdID",
164                37 => "OrderID",
165                38 => "OrderQty",
166                39 => "OrdStatus",
167                40 => "OrdType",
168                44 => "Price",
169                54 => "Side",
170                55 => "Symbol",
171                59 => "TimeInForce",
172                95 => "SecureDataLen",
173                96 => "SecureData",
174                98 => "EncryptMethod",
175                108 => "HeartBtInt",
176                553 => "Username",
177                554 => "Password",
178                584 => "MassStatusReqID",
179                585 => "MassStatusReqType",
180                710 => "PosReqID",
181                721 => "PosMaintRptID",
182                _ => "Unknown",
183            };
184            field_descriptions.push(format!("{field_name}({tag})={value}"));
185        }
186
187        f.debug_struct("FixMessage")
188            .field("fields", &field_descriptions)
189            .field("readable_message", &readable_message)
190            .finish()
191    }
192}