Skip to main content

ironsbe_schema/
messages.rs

1//! Message definitions for SBE schemas.
2//!
3//! This module contains the data structures representing SBE message definitions
4//! including fields, groups, and variable-length data.
5
6use crate::types::Presence;
7
8/// Message definition.
9#[derive(Debug, Clone)]
10pub struct MessageDef {
11    /// Message name.
12    pub name: String,
13    /// Message template ID.
14    pub id: u16,
15    /// Block length (fixed portion size in bytes).
16    pub block_length: u16,
17    /// Semantic type.
18    pub semantic_type: Option<String>,
19    /// Description.
20    pub description: Option<String>,
21    /// Since version.
22    pub since_version: Option<u16>,
23    /// Deprecated since version.
24    pub deprecated: Option<u16>,
25    /// Fixed fields in the root block.
26    pub fields: Vec<FieldDef>,
27    /// Repeating groups.
28    pub groups: Vec<GroupDef>,
29    /// Variable-length data fields.
30    pub data_fields: Vec<DataFieldDef>,
31}
32
33impl MessageDef {
34    /// Creates a new message definition.
35    #[must_use]
36    pub fn new(name: String, id: u16, block_length: u16) -> Self {
37        Self {
38            name,
39            id,
40            block_length,
41            semantic_type: None,
42            description: None,
43            since_version: None,
44            deprecated: None,
45            fields: Vec::new(),
46            groups: Vec::new(),
47            data_fields: Vec::new(),
48        }
49    }
50
51    /// Adds a field to the message.
52    pub fn add_field(&mut self, field: FieldDef) {
53        self.fields.push(field);
54    }
55
56    /// Adds a group to the message.
57    pub fn add_group(&mut self, group: GroupDef) {
58        self.groups.push(group);
59    }
60
61    /// Adds a data field to the message.
62    pub fn add_data_field(&mut self, data_field: DataFieldDef) {
63        self.data_fields.push(data_field);
64    }
65
66    /// Returns true if the message has any repeating groups.
67    #[must_use]
68    pub fn has_groups(&self) -> bool {
69        !self.groups.is_empty()
70    }
71
72    /// Returns true if the message has any variable-length data.
73    #[must_use]
74    pub fn has_var_data(&self) -> bool {
75        !self.data_fields.is_empty()
76    }
77
78    /// Calculates the minimum encoded length (header + block).
79    #[must_use]
80    pub fn min_encoded_length(&self) -> usize {
81        8 + self.block_length as usize // MessageHeader + block
82    }
83}
84
85/// Field definition within a message or group.
86#[derive(Debug, Clone)]
87pub struct FieldDef {
88    /// Field name.
89    pub name: String,
90    /// Field ID (tag).
91    pub id: u16,
92    /// Type name (references a type definition).
93    pub type_name: String,
94    /// Offset within the block.
95    pub offset: usize,
96    /// Field presence.
97    pub presence: Presence,
98    /// Semantic type.
99    pub semantic_type: Option<String>,
100    /// Description.
101    pub description: Option<String>,
102    /// Since version.
103    pub since_version: Option<u16>,
104    /// Deprecated since version.
105    pub deprecated: Option<u16>,
106    /// Constant value (if presence is Constant).
107    pub value_ref: Option<String>,
108    /// Encoded length in bytes (resolved from type).
109    pub encoded_length: usize,
110}
111
112impl FieldDef {
113    /// Creates a new field definition.
114    #[must_use]
115    pub fn new(name: String, id: u16, type_name: String, offset: usize) -> Self {
116        Self {
117            name,
118            id,
119            type_name,
120            offset,
121            presence: Presence::Required,
122            semantic_type: None,
123            description: None,
124            since_version: None,
125            deprecated: None,
126            value_ref: None,
127            encoded_length: 0,
128        }
129    }
130
131    /// Returns true if the field is optional.
132    #[must_use]
133    pub fn is_optional(&self) -> bool {
134        self.presence == Presence::Optional
135    }
136
137    /// Returns true if the field has a constant value.
138    #[must_use]
139    pub fn is_constant(&self) -> bool {
140        self.presence == Presence::Constant
141    }
142
143    /// Returns the end offset (offset + length).
144    #[must_use]
145    pub fn end_offset(&self) -> usize {
146        self.offset + self.encoded_length
147    }
148}
149
150/// Repeating group definition.
151#[derive(Debug, Clone)]
152pub struct GroupDef {
153    /// Group name.
154    pub name: String,
155    /// Group ID.
156    pub id: u16,
157    /// Block length of each entry.
158    pub block_length: u16,
159    /// Dimension type (usually groupSizeEncoding).
160    pub dimension_type: String,
161    /// Description.
162    pub description: Option<String>,
163    /// Since version.
164    pub since_version: Option<u16>,
165    /// Deprecated since version.
166    pub deprecated: Option<u16>,
167    /// Fields within each group entry.
168    pub fields: Vec<FieldDef>,
169    /// Nested groups within each entry.
170    pub nested_groups: Vec<GroupDef>,
171    /// Variable-length data within each entry.
172    pub data_fields: Vec<DataFieldDef>,
173}
174
175impl GroupDef {
176    /// Creates a new group definition.
177    #[must_use]
178    pub fn new(name: String, id: u16, block_length: u16) -> Self {
179        Self {
180            name,
181            id,
182            block_length,
183            dimension_type: "groupSizeEncoding".to_string(),
184            description: None,
185            since_version: None,
186            deprecated: None,
187            fields: Vec::new(),
188            nested_groups: Vec::new(),
189            data_fields: Vec::new(),
190        }
191    }
192
193    /// Adds a field to the group.
194    pub fn add_field(&mut self, field: FieldDef) {
195        self.fields.push(field);
196    }
197
198    /// Adds a nested group.
199    pub fn add_nested_group(&mut self, group: GroupDef) {
200        self.nested_groups.push(group);
201    }
202
203    /// Adds a data field to the group.
204    pub fn add_data_field(&mut self, data_field: DataFieldDef) {
205        self.data_fields.push(data_field);
206    }
207
208    /// Returns true if the group has nested groups.
209    #[must_use]
210    pub fn has_nested_groups(&self) -> bool {
211        !self.nested_groups.is_empty()
212    }
213
214    /// Returns true if the group has variable-length data.
215    #[must_use]
216    pub fn has_var_data(&self) -> bool {
217        !self.data_fields.is_empty()
218    }
219
220    /// Returns the header size (typically 4 bytes for groupSizeEncoding).
221    #[must_use]
222    pub const fn header_size(&self) -> usize {
223        4 // blockLength (u16) + numInGroup (u16)
224    }
225}
226
227/// Variable-length data field definition.
228#[derive(Debug, Clone)]
229pub struct DataFieldDef {
230    /// Field name.
231    pub name: String,
232    /// Field ID.
233    pub id: u16,
234    /// Type name (e.g., "varDataEncoding").
235    pub type_name: String,
236    /// Description.
237    pub description: Option<String>,
238    /// Since version.
239    pub since_version: Option<u16>,
240    /// Deprecated since version.
241    pub deprecated: Option<u16>,
242}
243
244impl DataFieldDef {
245    /// Creates a new data field definition.
246    #[must_use]
247    pub fn new(name: String, id: u16, type_name: String) -> Self {
248        Self {
249            name,
250            id,
251            type_name,
252            description: None,
253            since_version: None,
254            deprecated: None,
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_message_def_creation() {
265        let mut msg = MessageDef::new("NewOrderSingle".to_string(), 1, 56);
266        msg.add_field(FieldDef::new(
267            "clOrdId".to_string(),
268            11,
269            "ClOrdId".to_string(),
270            0,
271        ));
272        msg.add_field(FieldDef::new(
273            "symbol".to_string(),
274            55,
275            "Symbol".to_string(),
276            20,
277        ));
278
279        assert_eq!(msg.name, "NewOrderSingle");
280        assert_eq!(msg.id, 1);
281        assert_eq!(msg.block_length, 56);
282        assert_eq!(msg.fields.len(), 2);
283        assert_eq!(msg.min_encoded_length(), 64); // 8 + 56
284    }
285
286    #[test]
287    fn test_field_def() {
288        let mut field = FieldDef::new("price".to_string(), 44, "decimal".to_string(), 30);
289        field.encoded_length = 9;
290        field.presence = Presence::Optional;
291
292        assert!(field.is_optional());
293        assert!(!field.is_constant());
294        assert_eq!(field.end_offset(), 39);
295    }
296
297    #[test]
298    fn test_group_def() {
299        let mut group = GroupDef::new("mdEntries".to_string(), 268, 31);
300        group.add_field(FieldDef::new(
301            "securityId".to_string(),
302            48,
303            "uint64".to_string(),
304            0,
305        ));
306        group.add_field(FieldDef::new(
307            "rptSeq".to_string(),
308            83,
309            "uint32".to_string(),
310            8,
311        ));
312
313        assert_eq!(group.name, "mdEntries");
314        assert_eq!(group.fields.len(), 2);
315        assert_eq!(group.header_size(), 4);
316        assert!(!group.has_nested_groups());
317        assert!(!group.has_var_data());
318    }
319
320    #[test]
321    fn test_data_field_def() {
322        let data = DataFieldDef::new("rawData".to_string(), 96, "varDataEncoding".to_string());
323        assert_eq!(data.name, "rawData");
324        assert_eq!(data.type_name, "varDataEncoding");
325    }
326
327    #[test]
328    fn test_message_with_groups_and_data() {
329        let mut msg = MessageDef::new("MarketDataRefresh".to_string(), 3, 16);
330        msg.add_group(GroupDef::new("mdEntries".to_string(), 268, 31));
331        msg.add_data_field(DataFieldDef::new(
332            "rawData".to_string(),
333            96,
334            "varDataEncoding".to_string(),
335        ));
336
337        assert!(msg.has_groups());
338        assert!(msg.has_var_data());
339    }
340}