1use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub enum Version {
22 Fix40,
24 Fix41,
26 Fix42,
28 Fix43,
30 Fix44,
32 Fix50,
34 Fix50Sp1,
36 Fix50Sp2,
38 Fixt11,
40}
41
42impl Version {
43 #[must_use]
45 pub const fn begin_string(&self) -> &'static str {
46 match self {
47 Self::Fix40 => "FIX.4.0",
48 Self::Fix41 => "FIX.4.1",
49 Self::Fix42 => "FIX.4.2",
50 Self::Fix43 => "FIX.4.3",
51 Self::Fix44 => "FIX.4.4",
52 Self::Fix50 | Self::Fix50Sp1 | Self::Fix50Sp2 | Self::Fixt11 => "FIXT.1.1",
53 }
54 }
55
56 #[must_use]
58 pub const fn appl_ver_id(&self) -> Option<&'static str> {
59 match self {
60 Self::Fix50 => Some("7"),
61 Self::Fix50Sp1 => Some("8"),
62 Self::Fix50Sp2 => Some("9"),
63 _ => None,
64 }
65 }
66
67 #[must_use]
69 pub const fn uses_fixt(&self) -> bool {
70 matches!(
71 self,
72 Self::Fix50 | Self::Fix50Sp1 | Self::Fix50Sp2 | Self::Fixt11
73 )
74 }
75}
76
77impl std::fmt::Display for Version {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "{}", self.begin_string())
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum FieldType {
86 Int,
88 Length,
90 SeqNum,
92 NumInGroup,
94 TagNum,
96 DayOfMonth,
98 Float,
100 Qty,
102 Price,
104 PriceOffset,
106 Amt,
108 Percentage,
110 Char,
112 Boolean,
114 String,
116 MultipleCharValue,
118 MultipleStringValue,
120 Country,
122 Currency,
124 Exchange,
126 MonthYear,
128 UtcTimestamp,
130 UtcTimeOnly,
132 UtcDateOnly,
134 LocalMktDate,
136 LocalMktTime,
138 TzTimeOnly,
140 TzTimestamp,
142 Data,
144 XmlData,
146 Language,
148 Pattern,
150 Tenor,
152 Reserved,
154}
155
156impl FieldType {
157 #[must_use]
159 pub const fn is_numeric(&self) -> bool {
160 matches!(
161 self,
162 Self::Int
163 | Self::Length
164 | Self::SeqNum
165 | Self::NumInGroup
166 | Self::TagNum
167 | Self::DayOfMonth
168 | Self::Float
169 | Self::Qty
170 | Self::Price
171 | Self::PriceOffset
172 | Self::Amt
173 | Self::Percentage
174 )
175 }
176}
177
178impl std::str::FromStr for FieldType {
179 type Err = std::convert::Infallible;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
186 Ok(match s.to_uppercase().as_str() {
187 "INT" => Self::Int,
188 "LENGTH" => Self::Length,
189 "SEQNUM" => Self::SeqNum,
190 "NUMINGROUP" => Self::NumInGroup,
191 "TAGNUM" => Self::TagNum,
192 "DAYOFMONTH" => Self::DayOfMonth,
193 "FLOAT" => Self::Float,
194 "QTY" | "QUANTITY" => Self::Qty,
195 "PRICE" => Self::Price,
196 "PRICEOFFSET" => Self::PriceOffset,
197 "AMT" | "AMOUNT" => Self::Amt,
198 "PERCENTAGE" => Self::Percentage,
199 "CHAR" => Self::Char,
200 "BOOLEAN" => Self::Boolean,
201 "STRING" => Self::String,
202 "MULTIPLECHARVALUE" => Self::MultipleCharValue,
203 "MULTIPLESTRINGVALUE" => Self::MultipleStringValue,
204 "COUNTRY" => Self::Country,
205 "CURRENCY" => Self::Currency,
206 "EXCHANGE" => Self::Exchange,
207 "MONTHYEAR" => Self::MonthYear,
208 "UTCTIMESTAMP" => Self::UtcTimestamp,
209 "UTCTIMEONLY" => Self::UtcTimeOnly,
210 "UTCDATEONLY" => Self::UtcDateOnly,
211 "LOCALMKTDATE" => Self::LocalMktDate,
212 "LOCALMKTTIME" => Self::LocalMktTime,
213 "TZTIMEONLY" => Self::TzTimeOnly,
214 "TZTIMESTAMP" => Self::TzTimestamp,
215 "DATA" => Self::Data,
216 "XMLDATA" => Self::XmlData,
217 "LANGUAGE" => Self::Language,
218 "PATTERN" => Self::Pattern,
219 "TENOR" => Self::Tenor,
220 _ => Self::String,
221 })
222 }
223}
224
225impl FieldType {
226 #[must_use]
228 pub const fn is_timestamp(&self) -> bool {
229 matches!(
230 self,
231 Self::UtcTimestamp
232 | Self::UtcTimeOnly
233 | Self::UtcDateOnly
234 | Self::LocalMktDate
235 | Self::LocalMktTime
236 | Self::TzTimeOnly
237 | Self::TzTimestamp
238 )
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct FieldDef {
245 pub tag: u32,
247 pub name: String,
249 pub field_type: FieldType,
251 pub values: Option<HashMap<String, String>>,
253 pub description: Option<String>,
255}
256
257impl FieldDef {
258 #[must_use]
265 pub fn new(tag: u32, name: impl Into<String>, field_type: FieldType) -> Self {
266 Self {
267 tag,
268 name: name.into(),
269 field_type,
270 values: None,
271 description: None,
272 }
273 }
274
275 #[must_use]
277 pub fn with_values(mut self, values: HashMap<String, String>) -> Self {
278 self.values = Some(values);
279 self
280 }
281
282 #[must_use]
284 pub fn with_description(mut self, description: impl Into<String>) -> Self {
285 self.description = Some(description.into());
286 self
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct FieldRef {
293 pub tag: u32,
295 pub name: String,
297 pub required: bool,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct GroupDef {
304 pub count_tag: u32,
306 pub name: String,
308 pub delimiter_tag: u32,
310 pub fields: Vec<FieldRef>,
312 pub groups: Vec<GroupDef>,
314 pub required: bool,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ComponentDef {
321 pub name: String,
323 pub fields: Vec<FieldRef>,
325 pub groups: Vec<GroupDef>,
327 pub components: Vec<String>,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct MessageDef {
334 pub msg_type: String,
336 pub name: String,
338 pub category: MessageCategory,
340 pub fields: Vec<FieldRef>,
342 pub groups: Vec<GroupDef>,
344 pub components: Vec<String>,
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350pub enum MessageCategory {
351 Admin,
353 App,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct Dictionary {
360 pub version: Version,
362 pub fields: HashMap<u32, FieldDef>,
364 pub fields_by_name: HashMap<String, u32>,
366 pub messages: HashMap<String, MessageDef>,
368 pub components: HashMap<String, ComponentDef>,
370 pub header: Vec<FieldRef>,
372 pub trailer: Vec<FieldRef>,
374}
375
376impl Dictionary {
377 #[must_use]
382 pub fn new(version: Version) -> Self {
383 Self {
384 version,
385 fields: HashMap::new(),
386 fields_by_name: HashMap::new(),
387 messages: HashMap::new(),
388 components: HashMap::new(),
389 header: Vec::new(),
390 trailer: Vec::new(),
391 }
392 }
393
394 pub fn add_field(&mut self, field: FieldDef) {
396 self.fields_by_name.insert(field.name.clone(), field.tag);
397 self.fields.insert(field.tag, field);
398 }
399
400 pub fn add_message(&mut self, message: MessageDef) {
402 self.messages.insert(message.msg_type.clone(), message);
403 }
404
405 pub fn add_component(&mut self, component: ComponentDef) {
407 self.components.insert(component.name.clone(), component);
408 }
409
410 #[must_use]
412 pub fn get_field(&self, tag: u32) -> Option<&FieldDef> {
413 self.fields.get(&tag)
414 }
415
416 #[must_use]
418 pub fn get_field_by_name(&self, name: &str) -> Option<&FieldDef> {
419 self.fields_by_name
420 .get(name)
421 .and_then(|tag| self.fields.get(tag))
422 }
423
424 #[must_use]
426 pub fn get_message(&self, msg_type: &str) -> Option<&MessageDef> {
427 self.messages.get(msg_type)
428 }
429
430 #[must_use]
432 pub fn get_component(&self, name: &str) -> Option<&ComponentDef> {
433 self.components.get(name)
434 }
435
436 pub fn fields(&self) -> impl Iterator<Item = &FieldDef> {
438 self.fields.values()
439 }
440
441 pub fn messages(&self) -> impl Iterator<Item = &MessageDef> {
443 self.messages.values()
444 }
445
446 pub fn components(&self) -> impl Iterator<Item = &ComponentDef> {
448 self.components.values()
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn test_version_begin_string() {
458 assert_eq!(Version::Fix42.begin_string(), "FIX.4.2");
459 assert_eq!(Version::Fix44.begin_string(), "FIX.4.4");
460 assert_eq!(Version::Fix50Sp2.begin_string(), "FIXT.1.1");
461 }
462
463 #[test]
464 fn test_version_appl_ver_id() {
465 assert_eq!(Version::Fix44.appl_ver_id(), None);
466 assert_eq!(Version::Fix50.appl_ver_id(), Some("7"));
467 assert_eq!(Version::Fix50Sp2.appl_ver_id(), Some("9"));
468 }
469
470 #[test]
471 fn test_field_type_from_str() {
472 assert_eq!("INT".parse::<FieldType>().unwrap(), FieldType::Int);
473 assert_eq!("STRING".parse::<FieldType>().unwrap(), FieldType::String);
474 assert_eq!(
475 "UTCTIMESTAMP".parse::<FieldType>().unwrap(),
476 FieldType::UtcTimestamp
477 );
478 assert_eq!("unknown".parse::<FieldType>().unwrap(), FieldType::String);
479 }
480
481 #[test]
482 fn test_field_type_is_numeric() {
483 assert!(FieldType::Int.is_numeric());
484 assert!(FieldType::Price.is_numeric());
485 assert!(!FieldType::String.is_numeric());
486 }
487
488 #[test]
489 fn test_dictionary_field_operations() {
490 let mut dict = Dictionary::new(Version::Fix44);
491 let field = FieldDef::new(35, "MsgType", FieldType::String);
492 dict.add_field(field);
493
494 assert!(dict.get_field(35).is_some());
495 assert!(dict.get_field_by_name("MsgType").is_some());
496 assert!(dict.get_field(999).is_none());
497 }
498}