1use std::{
2 collections::HashMap,
3 convert::{AsRef, TryFrom},
4 fmt,
5 ops::Deref,
6 str::FromStr,
7};
8
9use anyhow::{Context as ErrorContext, Result, anyhow, bail};
10use strum_macros::AsRefStr;
11use xmltree::{Element, XMLNode};
12
13trait XmlHelper {
14 fn get_attribute(&self, attribute: &str) -> Result<&str>;
15 fn get_child_element(&self, child: &str) -> Result<&Element>;
16 fn get_child_elements(&self) -> impl Iterator<Item = &Element>;
17}
18
19impl XmlHelper for Element {
20 fn get_attribute(&self, attribute: &str) -> Result<&str> {
21 self.attributes
22 .get(attribute)
23 .map(String::as_ref)
24 .ok_or_else(|| anyhow!("no `{}` attribute in `{}` element", attribute, self.name))
25 }
26
27 fn get_child_element(&self, child: &str) -> Result<&Element> {
28 self.get_child(child)
29 .ok_or_else(|| anyhow!("no `{}` child in `{}` element", child, self.name))
30 }
31
32 fn get_child_elements(&self) -> impl Iterator<Item = &Element> {
33 self.children.iter().filter_map(XMLNode::as_element)
34 }
35}
36
37#[derive(Debug, PartialEq)]
38pub struct Version {
39 major: u32,
40 minor: u32,
41 service_pack: u32,
42}
43
44impl Version {
45 fn from_xml(element: &Element) -> Result<Version> {
46 Ok(Version {
47 major: element
48 .get_attribute("major")?
49 .parse()
50 .context("Failed to parse `major` number")?,
51 minor: element
52 .get_attribute("minor")?
53 .parse()
54 .context("Failed to parse `minor` number")?,
55 service_pack: element
56 .get_attribute("servicepack")?
57 .parse()
58 .context("Failed to parse `servicepack` number")?,
59 })
60 }
61
62 pub fn major(&self) -> u32 {
63 self.major
64 }
65
66 pub fn minor(&self) -> u32 {
67 self.minor
68 }
69
70 pub fn service_pack(&self) -> u32 {
71 self.service_pack
72 }
73}
74
75impl fmt::Display for Version {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 if self.service_pack == 0 {
78 write!(f, "{}.{}", self.major, self.minor)
79 } else {
80 write!(f, "{}.{} SP{}", self.major, self.minor, self.service_pack)
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq)]
86pub enum MemberKind {
87 Component,
88 Field,
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub struct Member {
93 name: String,
94 required: bool,
95 kind: MemberKind,
96}
97
98impl Member {
99 fn from_xml(element: &Element) -> Result<Member> {
100 let name = element.get_attribute("name")?;
101 if !name.is_ascii() {
102 bail!("Non ASCII characters in member name: {}", name);
103 }
104 let required = deserialize_yes_no(element.get_attribute("required")?)?;
105 let kind = match element.name.as_ref() {
106 "field" => MemberKind::Field,
107 "component" | "group" => MemberKind::Component,
108 name => bail!("Unexpected member kind `{}`", name),
109 };
110 Ok(Member {
111 name: name.into(),
112 required,
113 kind,
114 })
115 }
116
117 pub fn name(&self) -> &str {
118 &self.name
119 }
120
121 pub fn required(&self) -> bool {
122 self.required
123 }
124
125 pub fn kind(&self) -> MemberKind {
126 self.kind
127 }
128}
129
130#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
131pub enum BasicType {
132 Amt,
133 Boolean,
134 Char,
135 Country,
136 Currency,
137 Data,
138 Exchange,
139 Float,
140 Int,
141 Language,
142 Length,
143 LocalMktDate,
144 MonthYear,
145 MultipleCharValue,
146 MultipleStringValue,
147 NumInGroup,
148 Percentage,
149 Price,
150 PriceOffset,
151 Qty,
152 SeqNum,
153 String,
154 TzTimeOnly,
155 TzTimestamp,
156 UtcDateOnly,
157 UtcTimeOnly,
158 UtcTimestamp,
159 XmlData,
160}
161
162impl TryFrom<&str> for BasicType {
163 type Error = anyhow::Error;
164
165 fn try_from(input: &str) -> Result<Self, Self::Error> {
166 match input {
167 "AMT" => Ok(BasicType::Amt),
168 "BOOLEAN" => Ok(BasicType::Boolean),
169 "CHAR" => Ok(BasicType::Char),
170 "COUNTRY" => Ok(BasicType::Country),
171 "CURRENCY" => Ok(BasicType::Currency),
172 "DATA" => Ok(BasicType::Data),
173 "EXCHANGE" => Ok(BasicType::Exchange),
174 "FLOAT" => Ok(BasicType::Float),
175 "INT" | "LONG" => Ok(BasicType::Int),
176 "LANGUAGE" => Ok(BasicType::Language),
177 "LENGTH" => Ok(BasicType::Length),
178 "LOCALMKTDATE" => Ok(BasicType::LocalMktDate),
179 "MONTHYEAR" => Ok(BasicType::MonthYear),
180 "MULTIPLECHARVALUE" => Ok(BasicType::MultipleCharValue),
181 "MULTIPLESTRINGVALUE" => Ok(BasicType::MultipleStringValue),
182 "NUMINGROUP" => Ok(BasicType::NumInGroup),
183 "PERCENTAGE" => Ok(BasicType::Percentage),
184 "PRICE" => Ok(BasicType::Price),
185 "PRICEOFFSET" => Ok(BasicType::PriceOffset),
186 "QTY" => Ok(BasicType::Qty),
187 "SEQNUM" => Ok(BasicType::SeqNum),
188 "STRING" => Ok(BasicType::String),
189 "TZTIMEONLY" => Ok(BasicType::TzTimeOnly),
190 "TZTIMESTAMP" => Ok(BasicType::TzTimestamp),
191 "UTCDATEONLY" => Ok(BasicType::UtcDateOnly),
192 "UTCTIMEONLY" => Ok(BasicType::UtcTimeOnly),
193 "UTCTIMESTAMP" => Ok(BasicType::UtcTimestamp),
194 "XMLDATA" => Ok(BasicType::XmlData),
195 other => Err(anyhow!("Unexpected type `{}`", other)),
196 }
197 }
198}
199
200impl FromStr for BasicType {
201 type Err = anyhow::Error;
202
203 fn from_str(s: &str) -> Result<Self, Self::Err> {
204 TryFrom::try_from(s)
205 }
206}
207
208#[derive(Clone, Debug, PartialEq)]
209pub struct Value {
210 value: String,
211 description: String,
212}
213
214impl Value {
215 fn from_xml(element: &Element) -> Result<Value> {
216 if element.name != "value" {
217 bail!("Expected `value` node, found `{}`", element.name);
218 }
219
220 let value = element.get_attribute("enum")?;
221 if !value.is_ascii() {
222 bail!("Non ASCII characters in enum value: {}", value);
223 }
224
225 let description = element.get_attribute("description")?;
226 if !description.is_ascii() {
227 bail!("Non ASCII characters in enum description: {}", description);
228 }
229
230 Ok(Value {
231 value: value.into(),
232 description: description.into(),
233 })
234 }
235
236 pub fn value(&self) -> &str {
237 &self.value
238 }
239
240 pub fn description(&self) -> &str {
241 &self.description
242 }
243}
244
245#[derive(Clone, Debug, PartialEq)]
246pub struct Field {
247 name: String,
248 number: u16,
249 type_: BasicType,
250 values: Option<Vec<Value>>,
251}
252
253impl Field {
254 fn from_xml(element: &Element) -> Result<Field> {
255 let values = element
256 .get_child_elements()
257 .map(Value::from_xml)
258 .collect::<Result<Vec<_>, _>>()?;
259 let name = element.get_attribute("name")?;
260 if !name.is_ascii() {
261 bail!("Non ASCII characters in field name: {}", name);
262 }
263 Ok(Field {
264 name: name.into(),
265 number: element.get_attribute("number")?.parse()?,
266 type_: element.get_attribute("type")?.parse()?,
267 values: if values.is_empty() {
268 None
269 } else {
270 Some(values)
271 },
272 })
273 }
274
275 pub fn name(&self) -> &str {
276 &self.name
277 }
278
279 pub fn number(&self) -> u16 {
280 self.number
281 }
282
283 pub fn type_(&self) -> BasicType {
284 self.type_
285 }
286
287 pub fn values(&self) -> Option<&[Value]> {
288 self.values.as_deref()
289 }
290}
291
292fn deserialize_yes_no(input: &str) -> Result<bool> {
293 match input {
294 "Y" | "YES" | "y" | "yes" => Ok(true),
295 "N" | "NO" | "n" | "no" => Ok(false),
296 unexpected => Err(anyhow!(
297 "parse yes/no failed, unexpected value `{}`",
298 unexpected
299 )),
300 }
301}
302
303#[derive(Debug, Clone, PartialEq)]
304pub struct Component {
305 name: String,
306 number_of_elements: Option<Member>,
307 members: Vec<Member>,
308}
309
310impl Component {
311 fn from_xml(element: &Element) -> Result<Component> {
312 if element.name != "component" {
313 bail!("Expected `component` node, found `{}`", element.name);
314 }
315
316 let name = element.get_attribute("name")?.to_owned();
317 if !name.is_ascii() {
318 bail!("Non ASCII characters in component name: {}", name);
319 }
320
321 let mut iter = element.get_child_elements().peekable();
322
323 let number_of_elements = if let Some(child) = iter.peek() {
324 if child.name == "group" {
325 let member = Member::from_xml(child)?;
326 iter = child.get_child_elements().peekable();
327 Some(member)
328 } else {
329 None
330 }
331 } else {
332 bail!("Empty member list in `{}` component", name)
333 };
334
335 let members = iter.map(Member::from_xml).collect::<Result<Vec<_>, _>>()?;
336
337 Ok(Component {
338 name,
339 number_of_elements,
340 members,
341 })
342 }
343
344 fn from_header_or_trailer(element: &Element) -> Result<(Component, Vec<Component>)> {
347 let name = match element.name.as_str() {
348 "header" => "Header".to_owned(),
349 "trailer" => "Trailer".to_owned(),
350 unexpected => bail!("Expected `header/trailer` node, found `{}`", unexpected),
351 };
352
353 let mut groups = Vec::new();
354 let mut members = Vec::new();
355 for member_element in element.get_child_elements() {
356 if member_element.name == "group" {
357 let group_name = member_element.get_attribute("name")?;
358 let group_name = if group_name.starts_with("No") && group_name.ends_with('s') {
359 format!("{}Grp", &group_name[2..group_name.len() - 1])
360 } else {
361 bail!("Malformed group name `{}`", group_name);
362 };
363 let number_of_elements = Some(Member::from_xml(member_element)?);
364 let group_members = member_element
365 .get_child_elements()
366 .map(Member::from_xml)
367 .collect::<Result<Vec<_>, _>>()?;
368 groups.push(Component {
369 name: group_name.clone(),
370 number_of_elements,
371 members: group_members,
372 });
373 let mut member_element = member_element.clone();
374 if let Some(name) = member_element.attributes.get_mut("name") {
375 *name = group_name;
376 }
377 members.push(Member::from_xml(&member_element)?);
378 } else {
379 members.push(Member::from_xml(member_element)?);
380 }
381 }
382
383 Ok((
384 Component {
385 name,
386 number_of_elements: None,
387 members,
388 },
389 groups,
390 ))
391 }
392
393 pub fn name(&self) -> &str {
394 &self.name
395 }
396
397 pub fn number_of_elements(&self) -> Option<&Member> {
398 self.number_of_elements.as_ref()
399 }
400
401 pub fn members(&self) -> &[Member] {
402 &self.members
403 }
404}
405
406#[derive(Clone, Copy, Debug, PartialEq)]
407pub enum MsgCat {
408 Admin,
409 App,
410}
411
412#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
413enum MsgTypeBuf {
414 Short([u8; 1]),
415 Long([u8; 2]),
416}
417
418#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
419pub struct MsgType(MsgTypeBuf);
420
421impl Deref for MsgType {
422 type Target = [u8];
423
424 fn deref(&self) -> &Self::Target {
425 match self {
426 MsgType(MsgTypeBuf::Short(b)) => b,
427 MsgType(MsgTypeBuf::Long(b)) => b,
428 }
429 }
430}
431
432impl FromStr for MsgType {
433 type Err = anyhow::Error;
434
435 fn from_str(s: &str) -> Result<Self, Self::Err> {
436 if !s.is_ascii() {
437 bail!("Non ASCII characters in message type: {}", s);
438 }
439
440 match s.as_bytes() {
441 [] => Err(anyhow!("MsgType empty")),
442 [b0 @ b'0'..=b'9' | b0 @ b'A'..=b'Z' | b0 @ b'a'..=b'z'] => {
443 Ok(MsgType(MsgTypeBuf::Short([*b0])))
444 }
445 [
446 b0 @ b'0'..=b'9' | b0 @ b'A'..=b'Z' | b0 @ b'a'..=b'z',
447 b1 @ b'0'..=b'9' | b1 @ b'A'..=b'Z' | b1 @ b'a'..=b'z',
448 ] => Ok(MsgType(MsgTypeBuf::Long([*b0, *b1]))),
449 [_] | [_, _] => Err(anyhow!("Incorrect MsgType value: {}", s)),
450 _ => Err(anyhow!("MsgType (`{}`) too long ({}`", s, s.len())),
451 }
452 }
453}
454
455impl FromStr for MsgCat {
456 type Err = anyhow::Error;
457
458 fn from_str(s: &str) -> Result<Self, Self::Err> {
459 match s {
460 "admin" => Ok(MsgCat::Admin),
461 "app" => Ok(MsgCat::App),
462 other => Err(anyhow!("Unknown message category `{}`", other)),
463 }
464 }
465}
466
467#[derive(Debug, PartialEq)]
468pub struct Message {
469 name: String,
470 msg_cat: MsgCat,
471 msg_type: MsgType,
472 members: Vec<Member>,
473}
474
475impl Message {
476 fn from_xml(element: &Element) -> Result<Message> {
477 if element.name != "message" {
478 bail!("Expected `message` node, found `{}`", element.name);
479 }
480
481 let name = element.get_attribute("name")?.to_owned();
482 if !name.is_ascii() {
483 bail!("Non ASCII characters in message name: {}", name);
484 }
485 let msg_cat = element.get_attribute("msgcat")?.parse()?;
486 let msg_type = element.get_attribute("msgtype")?.parse()?;
487
488 let members = element
489 .get_child_elements()
490 .map(Member::from_xml)
491 .collect::<Result<Vec<_>, _>>()?;
492
493 Ok(Message {
494 name,
495 msg_cat,
496 msg_type,
497 members,
498 })
499 }
500
501 pub fn name(&self) -> &str {
502 &self.name
503 }
504
505 pub fn msg_cat(&self) -> MsgCat {
506 self.msg_cat
507 }
508
509 pub fn msg_type(&self) -> MsgType {
510 self.msg_type
511 }
512
513 pub fn members(&self) -> &[Member] {
514 &self.members
515 }
516}
517
518#[derive(Debug, PartialEq)]
519pub struct Dictionary {
520 fix_version: Option<Version>,
521 fixt_version: Option<Version>,
522 header: Option<Component>,
523 trailer: Option<Component>,
524 messages: HashMap<MsgType, Message>,
525 flat_messages: HashMap<String, Message>,
526 components: Vec<Component>,
527 components_by_name: HashMap<String, Component>,
528 fields: HashMap<u16, Field>,
529 fields_by_name: HashMap<String, Field>,
530 reject_reason_overrides: HashMap<ParseRejectReason, String>,
531}
532
533impl Default for Dictionary {
534 fn default() -> Self {
535 Self::new(None)
536 }
537}
538
539impl Dictionary {
540 pub fn new(
541 optional_reject_reason_overrides: Option<HashMap<ParseRejectReason, String>>,
542 ) -> Dictionary {
543 Dictionary {
544 fixt_version: None,
545 fix_version: None,
546 header: None,
547 trailer: None,
548 messages: HashMap::new(),
549 flat_messages: HashMap::new(),
550 components: Vec::new(),
551 components_by_name: HashMap::new(),
552 fields: HashMap::new(),
553 fields_by_name: HashMap::new(),
554 reject_reason_overrides: optional_reject_reason_overrides.unwrap_or_default(),
555 }
556 }
557
558 pub fn process_legacy_fix_xml(&mut self, xml: &str) -> Result<()> {
559 let root = Element::parse(xml.as_bytes()).context("Failed to parse FIXT description")?;
560
561 let type_ = root.get_attribute("type")?;
562 if type_ != "FIX" {
563 bail!("Unexpected FIX XML description type `{}`", type_);
564 }
565
566 if self.fix_version.is_some() {
567 bail!("FIX XML already processed");
568 } else {
569 self.fix_version = Some(Version::from_xml(&root)?);
570 }
571
572 let (header, header_groups) =
573 Component::from_header_or_trailer(root.get_child_element("header")?)
574 .context("Failed to process FIX Header")?;
575 self.header = Some(header);
576 self.components.extend(header_groups);
577
578 let (trailer, trailer_groups) = Component::from_header_or_trailer(
579 root.get_child_element("trailer")
580 .context("Failed to process FIX trailer")?,
581 )?;
582 self.trailer = Some(trailer);
583 self.components.extend(trailer_groups);
584
585 self.process_common(&root)
586 }
587
588 pub fn process_fixt_xml(&mut self, xml: &str) -> Result<()> {
589 let root = Element::parse(xml.as_bytes()).context("Failed to parse FIXT description")?;
590
591 let type_ = root.get_attribute("type")?;
592 if type_ != "FIXT" {
593 bail!("Unexpected FIX XML description type `{}`", type_);
594 }
595
596 if self.fixt_version.is_some() {
597 bail!("FIXT XML already processed");
598 } else {
599 self.fixt_version = Some(Version::from_xml(&root)?);
600 }
601
602 let (header, header_groups) =
603 Component::from_header_or_trailer(root.get_child_element("header")?)
604 .context("Failed to process FIXT Header")?;
605 self.header = Some(header);
606 self.components.extend(header_groups);
607
608 let (trailer, trailer_groups) = Component::from_header_or_trailer(
609 root.get_child_element("trailer")
610 .context("Failed to process FIXT trailer")?,
611 )?;
612 self.trailer = Some(trailer);
613 self.components.extend(trailer_groups);
614
615 self.process_common(&root)
616 }
617
618 pub fn process_fix_xml(&mut self, xml: &str) -> Result<()> {
620 let root = Element::parse(xml.as_bytes()).context("Failed to parse FIX description")?;
621
622 let type_ = root.get_attribute("type")?;
623 if type_ != "FIX" {
624 bail!("Unexpected FIX XML description type `{}`", type_);
625 }
626
627 if self.fix_version.is_some() {
628 bail!("FIX XML already processed");
629 } else {
630 self.fix_version = Some(Version::from_xml(&root)?);
631 }
632
633 self.process_common(&root)
634 }
635
636 fn process_common(&mut self, root: &Element) -> Result<()> {
637 self.messages.extend(
638 root.get_child_element("messages")?
639 .get_child_elements()
640 .map(Message::from_xml)
641 .collect::<Result<Vec<_>, _>>()?
642 .into_iter()
643 .map(|m| (m.msg_type, m)),
644 );
645
646 self.components.extend(
647 root.get_child_element("components")?
648 .get_child_elements()
649 .map(Component::from_xml)
650 .collect::<Result<Vec<_>>>()?,
651 );
652 self.components_by_name.extend(
653 self.components
654 .iter()
655 .map(|c| (c.name().to_owned(), c.clone())),
656 );
657
658 self.fields.extend(
659 root.get_child_element("fields")?
660 .get_child_elements()
661 .map(Field::from_xml)
662 .collect::<Result<Vec<_>, _>>()?
663 .into_iter()
664 .map(|f| (f.number, f)),
665 );
666
667 let msg_type_field = self.fields.get_mut(&35).expect("MsgType field not defined");
669 msg_type_field.values = Some(
670 msg_type_field
671 .values
672 .take()
673 .expect("MsgType enum fields not defined")
674 .iter()
675 .filter(|v| {
676 self.messages
677 .contains_key(&MsgType::from_str(&v.value).expect("MsgType value error"))
678 })
679 .cloned()
680 .collect(),
681 );
682
683 self.fields_by_name.extend(
684 self.fields
685 .values()
686 .map(|f| (f.name().to_owned(), f.to_owned())),
687 );
688
689 Ok(())
690 }
691
692 pub fn fixt_version(&self) -> Option<&Version> {
693 self.fixt_version.as_ref()
694 }
695
696 pub fn fix_version(&self) -> Option<&Version> {
697 self.fix_version.as_ref()
698 }
699
700 pub fn header(&self) -> Result<&Component> {
701 self.header
702 .as_ref()
703 .ok_or_else(|| anyhow!("Missing header"))
704 }
705
706 pub fn trailer(&self) -> Result<&Component> {
707 self.trailer
708 .as_ref()
709 .ok_or_else(|| anyhow!("Missing trailer"))
710 }
711
712 pub fn components(&self) -> &[Component] {
713 &self.components
714 }
715
716 pub fn component(&self, name: &str) -> Option<&Component> {
717 self.components_by_name.get(name)
718 }
719
720 pub fn message(&self, name: &MsgType) -> Option<&Message> {
721 self.messages.get(name)
722 }
723
724 pub fn messages(&self) -> &HashMap<MsgType, Message> {
725 &self.messages
726 }
727
728 pub fn fields(&self) -> &HashMap<u16, Field> {
729 &self.fields
730 }
731
732 pub fn fields_by_name(&self) -> &HashMap<String, Field> {
733 &self.fields_by_name
734 }
735
736 pub fn reject_reason_overrides(&self) -> &HashMap<ParseRejectReason, String> {
737 &self.reject_reason_overrides
738 }
739}
740
741#[derive(Clone, Copy, Debug, Eq, PartialEq, strum_macros::EnumIter, AsRefStr, Hash)]
742pub enum ParseRejectReason {
743 ValueIsIncorrect,
744 TagSpecifiedWithoutAValue,
745 IncorrectDataFormatForValue,
746 TagAppearsMoreThanOnce,
747 TagSpecifiedOutOfRequiredOrder,
748 RequiredTagMissing,
749 IncorrectNumingroupCountForRepeatingGroup,
750 TagNotDefinedForThisMessageType,
751 UndefinedTag,
752 RepeatingGroupFieldsOutOfOrder,
753 InvalidTagNumber,
754 InvalidMsgtype,
755 SendingtimeAccuracyProblem,
756 CompidProblem,
757}
758
759#[cfg(test)]
760mod tests {
761
762 use std::str::FromStr;
763
764 use super::MsgType;
765
766 #[test]
767 fn parse_msg_type() {
768 assert!(MsgType::from_str("").is_err());
769 assert!(MsgType::from_str("\0").is_err());
770 assert!(MsgType::from_str("\0\0").is_err());
771 assert!(MsgType::from_str("\0\0\0").is_err());
772 assert!(MsgType::from_str("A").is_ok());
773 assert!(MsgType::from_str("AA").is_ok());
774 assert!(MsgType::from_str("AAA").is_err());
775 assert!(MsgType::from_str("\0A").is_err());
776 }
777}