easyfix_dictionary/
lib.rs

1use std::{
2    collections::HashMap,
3    convert::{AsRef, TryFrom},
4    fmt,
5    ops::Deref,
6    str::FromStr,
7};
8
9use anyhow::{anyhow, bail, Context as ErrorContext, Result};
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    // All groups are defined as separate component with one member - the group itself.
345    // Except header (and possibly trailer) which has at least one group (`Hops`) defined inside.
346    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            [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'] => {
446                Ok(MsgType(MsgTypeBuf::Long([*b0, *b1])))
447            }
448            [_] | [_, _] => Err(anyhow!("Incorrect MsgType value: {}", s)),
449            _ => Err(anyhow!("MsgType (`{}`) too long ({}`", s, s.len())),
450        }
451    }
452}
453
454impl FromStr for MsgCat {
455    type Err = anyhow::Error;
456
457    fn from_str(s: &str) -> Result<Self, Self::Err> {
458        match s {
459            "admin" => Ok(MsgCat::Admin),
460            "app" => Ok(MsgCat::App),
461            other => Err(anyhow!("Unknown message category `{}`", other)),
462        }
463    }
464}
465
466#[derive(Debug, PartialEq)]
467pub struct Message {
468    name: String,
469    msg_cat: MsgCat,
470    msg_type: MsgType,
471    members: Vec<Member>,
472}
473
474impl Message {
475    fn from_xml(element: &Element) -> Result<Message> {
476        if element.name != "message" {
477            bail!("Expected `message` node, found `{}`", element.name);
478        }
479
480        let name = element.get_attribute("name")?.to_owned();
481        if !name.is_ascii() {
482            bail!("Non ASCII characters in message name: {}", name);
483        }
484        let msg_cat = element.get_attribute("msgcat")?.parse()?;
485        let msg_type = element.get_attribute("msgtype")?.parse()?;
486
487        let members = element
488            .get_child_elements()
489            .map(Member::from_xml)
490            .collect::<Result<Vec<_>, _>>()?;
491
492        Ok(Message {
493            name,
494            msg_cat,
495            msg_type,
496            members,
497        })
498    }
499
500    pub fn name(&self) -> &str {
501        &self.name
502    }
503
504    pub fn msg_cat(&self) -> MsgCat {
505        self.msg_cat
506    }
507
508    pub fn msg_type(&self) -> MsgType {
509        self.msg_type
510    }
511
512    pub fn members(&self) -> &[Member] {
513        &self.members
514    }
515}
516
517#[derive(Debug, PartialEq)]
518pub struct Dictionary {
519    fix_version: Option<Version>,
520    fixt_version: Option<Version>,
521    header: Option<Component>,
522    trailer: Option<Component>,
523    messages: HashMap<MsgType, Message>,
524    flat_messages: HashMap<String, Message>,
525    components: Vec<Component>,
526    components_by_name: HashMap<String, Component>,
527    fields: HashMap<u16, Field>,
528    fields_by_name: HashMap<String, Field>,
529    reject_reason_overrides: HashMap<ParseRejectReason, String>,
530}
531
532impl Default for Dictionary {
533    fn default() -> Self {
534        Self::new(None)
535    }
536}
537
538impl Dictionary {
539    pub fn new(
540        optional_reject_reason_overrides: Option<HashMap<ParseRejectReason, String>>,
541    ) -> Dictionary {
542        Dictionary {
543            fixt_version: None,
544            fix_version: None,
545            header: None,
546            trailer: None,
547            messages: HashMap::new(),
548            flat_messages: HashMap::new(),
549            components: Vec::new(),
550            components_by_name: HashMap::new(),
551            fields: HashMap::new(),
552            fields_by_name: HashMap::new(),
553            reject_reason_overrides: optional_reject_reason_overrides.unwrap_or_default(),
554        }
555    }
556
557    pub fn process_legacy_fix_xml(&mut self, xml: &str) -> Result<()> {
558        let root = Element::parse(xml.as_bytes()).context("Failed to parse FIXT description")?;
559
560        let type_ = root.get_attribute("type")?;
561        if type_ != "FIX" {
562            bail!("Unexpected FIX XML description type `{}`", type_);
563        }
564
565        if self.fix_version.is_some() {
566            bail!("FIX XML already processed");
567        } else {
568            self.fix_version = Some(Version::from_xml(&root)?);
569        }
570
571        let (header, header_groups) =
572            Component::from_header_or_trailer(root.get_child_element("header")?)
573                .context("Failed to process FIX Header")?;
574        self.header = Some(header);
575        self.components.extend(header_groups);
576
577        let (trailer, trailer_groups) = Component::from_header_or_trailer(
578            root.get_child_element("trailer")
579                .context("Failed to process FIX trailer")?,
580        )?;
581        self.trailer = Some(trailer);
582        self.components.extend(trailer_groups);
583
584        self.process_common(&root)
585    }
586
587    pub fn process_fixt_xml(&mut self, xml: &str) -> Result<()> {
588        let root = Element::parse(xml.as_bytes()).context("Failed to parse FIXT description")?;
589
590        let type_ = root.get_attribute("type")?;
591        if type_ != "FIXT" {
592            bail!("Unexpected FIX XML description type `{}`", type_);
593        }
594
595        if self.fixt_version.is_some() {
596            bail!("FIXT XML already processed");
597        } else {
598            self.fixt_version = Some(Version::from_xml(&root)?);
599        }
600
601        let (header, header_groups) =
602            Component::from_header_or_trailer(root.get_child_element("header")?)
603                .context("Failed to process FIXT Header")?;
604        self.header = Some(header);
605        self.components.extend(header_groups);
606
607        let (trailer, trailer_groups) = Component::from_header_or_trailer(
608            root.get_child_element("trailer")
609                .context("Failed to process FIXT trailer")?,
610        )?;
611        self.trailer = Some(trailer);
612        self.components.extend(trailer_groups);
613
614        self.process_common(&root)
615    }
616
617    // TODO: Allow adding different FIX versions
618    pub fn process_fix_xml(&mut self, xml: &str) -> Result<()> {
619        let root = Element::parse(xml.as_bytes()).context("Failed to parse FIX description")?;
620
621        let type_ = root.get_attribute("type")?;
622        if type_ != "FIX" {
623            bail!("Unexpected FIX XML description type `{}`", type_);
624        }
625
626        if self.fix_version.is_some() {
627            bail!("FIX XML already processed");
628        } else {
629            self.fix_version = Some(Version::from_xml(&root)?);
630        }
631
632        self.process_common(&root)
633    }
634
635    fn process_common(&mut self, root: &Element) -> Result<()> {
636        self.messages.extend(
637            root.get_child_element("messages")?
638                .get_child_elements()
639                .map(Message::from_xml)
640                .collect::<Result<Vec<_>, _>>()?
641                .into_iter()
642                .map(|m| (m.msg_type, m)),
643        );
644
645        self.components.extend(
646            root.get_child_element("components")?
647                .get_child_elements()
648                .map(Component::from_xml)
649                .collect::<Result<Vec<_>>>()?,
650        );
651        self.components_by_name.extend(
652            self.components
653                .iter()
654                .map(|c| (c.name().to_owned(), c.clone())),
655        );
656
657        self.fields.extend(
658            root.get_child_element("fields")?
659                .get_child_elements()
660                .map(Field::from_xml)
661                .collect::<Result<Vec<_>, _>>()?
662                .into_iter()
663                .map(|f| (f.number, f)),
664        );
665
666        // XXX: Drop MsgType values which does not match Messages list
667        let msg_type_field = self.fields.get_mut(&35).expect("MsgType field not defined");
668        msg_type_field.values = Some(
669            msg_type_field
670                .values
671                .take()
672                .expect("MsgType enum fields not defined")
673                .iter()
674                .filter(|v| {
675                    self.messages
676                        .contains_key(&MsgType::from_str(&v.value).expect("MsgType value error"))
677                })
678                .cloned()
679                .collect(),
680        );
681
682        self.fields_by_name.extend(
683            self.fields
684                .values()
685                .map(|f| (f.name().to_owned(), f.to_owned())),
686        );
687
688        Ok(())
689    }
690
691    pub fn fixt_version(&self) -> Option<&Version> {
692        self.fixt_version.as_ref()
693    }
694
695    pub fn fix_version(&self) -> Option<&Version> {
696        self.fix_version.as_ref()
697    }
698
699    pub fn header(&self) -> Result<&Component> {
700        self.header
701            .as_ref()
702            .ok_or_else(|| anyhow!("Missing header"))
703    }
704
705    pub fn trailer(&self) -> Result<&Component> {
706        self.trailer
707            .as_ref()
708            .ok_or_else(|| anyhow!("Missing trailer"))
709    }
710
711    pub fn components(&self) -> &[Component] {
712        &self.components
713    }
714
715    pub fn component(&self, name: &str) -> Option<&Component> {
716        self.components_by_name.get(name)
717    }
718
719    pub fn message(&self, name: &MsgType) -> Option<&Message> {
720        self.messages.get(name)
721    }
722
723    pub fn messages(&self) -> &HashMap<MsgType, Message> {
724        &self.messages
725    }
726
727    pub fn fields(&self) -> &HashMap<u16, Field> {
728        &self.fields
729    }
730
731    pub fn fields_by_name(&self) -> &HashMap<String, Field> {
732        &self.fields_by_name
733    }
734
735    pub fn reject_reason_overrides(&self) -> &HashMap<ParseRejectReason, String> {
736        &self.reject_reason_overrides
737    }
738}
739
740#[derive(Clone, Copy, Debug, Eq, PartialEq, strum_macros::EnumIter, AsRefStr, Hash)]
741pub enum ParseRejectReason {
742    ValueIsIncorrect,
743    TagSpecifiedWithoutAValue,
744    IncorrectDataFormatForValue,
745    TagAppearsMoreThanOnce,
746    TagSpecifiedOutOfRequiredOrder,
747    RequiredTagMissing,
748    IncorrectNumingroupCountForRepeatingGroup,
749    TagNotDefinedForThisMessageType,
750    UndefinedTag,
751    RepeatingGroupFieldsOutOfOrder,
752    InvalidTagNumber,
753    InvalidMsgtype,
754    SendingtimeAccuracyProblem,
755    CompidProblem,
756}
757
758#[cfg(test)]
759mod tests {
760
761    use std::str::FromStr;
762
763    use super::MsgType;
764
765    #[test]
766    fn parse_msg_type() {
767        assert!(MsgType::from_str("").is_err());
768        assert!(MsgType::from_str("\0").is_err());
769        assert!(MsgType::from_str("\0\0").is_err());
770        assert!(MsgType::from_str("\0\0\0").is_err());
771        assert!(MsgType::from_str("A").is_ok());
772        assert!(MsgType::from_str("AA").is_ok());
773        assert!(MsgType::from_str("AAA").is_err());
774        assert!(MsgType::from_str("\0A").is_err());
775    }
776}