deribit_fix/message/
builder.rs

1//! FIX message parsing and construction
2//!
3//! This module provides functionality for creating, parsing, and manipulating
4//! FIX protocol messages used in communication with Deribit.
5
6use crate::error::{DeribitFixError, Result};
7use crate::model::message::FixMessage;
8use crate::model::types::MsgType;
9use chrono::{DateTime, Utc};
10
11/// Builder for constructing FIX messages
12pub struct MessageBuilder {
13    message: FixMessage,
14}
15
16impl MessageBuilder {
17    /// Create a new message builder
18    pub fn new() -> Self {
19        let mut message = FixMessage::new();
20
21        // Set standard fields
22        message.set_field(8, "FIX.4.4".to_string()); // BeginString
23
24        Self { message }
25    }
26
27    /// Set message type
28    pub fn msg_type(mut self, msg_type: MsgType) -> Self {
29        self.message.set_field(35, msg_type.as_str().to_string());
30        self
31    }
32
33    /// Set sender company ID
34    pub fn sender_comp_id(mut self, sender_comp_id: String) -> Self {
35        self.message.set_field(49, sender_comp_id);
36        self
37    }
38
39    /// Set target company ID
40    pub fn target_comp_id(mut self, target_comp_id: String) -> Self {
41        self.message.set_field(56, target_comp_id);
42        self
43    }
44
45    /// Set message sequence number
46    pub fn msg_seq_num(mut self, seq_num: u32) -> Self {
47        self.message.set_field(34, seq_num.to_string());
48        self
49    }
50
51    /// Set sending time
52    pub fn sending_time(mut self, time: DateTime<Utc>) -> Self {
53        let time_str = time.format("%Y%m%d-%H:%M:%S%.3f").to_string();
54        self.message.set_field(52, time_str);
55        self
56    }
57
58    /// Add a custom field
59    pub fn field(mut self, tag: u32, value: String) -> Self {
60        self.message.set_field(tag, value);
61        self
62    }
63
64    /// Build the message
65    pub fn build(mut self) -> Result<FixMessage> {
66        // Validate required fields
67        if !self.message.has_field(8) {
68            return Err(DeribitFixError::MessageConstruction(
69                "BeginString (8) is required".to_string(),
70            ));
71        }
72
73        if !self.message.has_field(35) {
74            return Err(DeribitFixError::MessageConstruction(
75                "MsgType (35) is required".to_string(),
76            ));
77        }
78
79        if !self.message.has_field(49) {
80            return Err(DeribitFixError::MessageConstruction(
81                "SenderCompID (49) is required".to_string(),
82            ));
83        }
84
85        if !self.message.has_field(56) {
86            return Err(DeribitFixError::MessageConstruction(
87                "TargetCompID (56) is required".to_string(),
88            ));
89        }
90
91        if !self.message.has_field(34) {
92            return Err(DeribitFixError::MessageConstruction(
93                "MsgSeqNum (34) is required".to_string(),
94            ));
95        }
96
97        if !self.message.has_field(52) {
98            // Set current time if not provided
99            let now = Utc::now();
100            let time_str = now.format("%Y%m%d-%H:%M:%S%.3f").to_string();
101            self.message.set_field(52, time_str);
102        }
103
104        // Calculate BodyLength (all fields except BeginString and BodyLength itself)
105        let body_length = self.calculate_body_length();
106        self.message.set_field(9, body_length.to_string());
107
108        // Calculate and set checksum
109        let checksum = self.message.calculate_checksum();
110        self.message.set_field(10, format!("{checksum:03}"));
111
112        // Generate raw message string with proper FIX field ordering:
113        // 1. BeginString (8) - first
114        // 2. BodyLength (9) - second
115        // 3. All other fields sorted by tag number
116        // 4. CheckSum (10) - last
117        let mut field_pairs: Vec<_> = self.message.fields.iter().collect();
118        field_pairs.sort_by_key(|(tag, _)| *tag);
119
120        let mut raw_parts = Vec::new();
121        let mut checksum_part = None;
122
123        // Add BeginString first if present
124        if let Some((_, value)) = field_pairs.iter().find(|(tag, _)| *tag == 8) {
125            raw_parts.push(format!("8={value}"));
126        }
127
128        // Add BodyLength second if present
129        if let Some((_, value)) = field_pairs.iter().find(|(tag, _)| *tag == 9) {
130            raw_parts.push(format!("9={value}"));
131        }
132
133        // Add all other fields except BeginString, BodyLength, and CheckSum
134        for (tag, value) in field_pairs {
135            if *tag == 10 {
136                // Save checksum for last
137                checksum_part = Some(format!("{tag}={value}"));
138            } else if *tag != 8 && *tag != 9 {
139                raw_parts.push(format!("{tag}={value}"));
140            }
141        }
142
143        // Add checksum at the end
144        if let Some(checksum) = checksum_part {
145            raw_parts.push(checksum);
146        }
147
148        self.message.raw_message = raw_parts.join("\x01") + "\x01";
149
150        Ok(self.message)
151    }
152
153    /// Calculate the body length for the FIX message
154    /// Body length includes all fields except BeginString (8), BodyLength (9), and CheckSum (10)
155    fn calculate_body_length(&self) -> usize {
156        let mut body_parts = Vec::new();
157
158        // Add all fields except BeginString (8), BodyLength (9), and CheckSum (10)
159        for (tag, value) in &self.message.fields {
160            if *tag != 8 && *tag != 9 && *tag != 10 {
161                body_parts.push(format!("{tag}={value}"));
162            }
163        }
164
165        // Join with SOH and add final SOH
166        let body_str = body_parts.join("\x01") + "\x01";
167        body_str.len()
168    }
169}
170
171impl Default for MessageBuilder {
172    fn default() -> Self {
173        Self::new()
174    }
175}