easyfix_dictionary/
lib.rs

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