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::{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    // 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            [
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    // TODO: Allow adding different FIX versions
619    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        // XXX: Drop MsgType values which does not match Messages list
668        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}