hotfix_dictionary/
field.rs

1use crate::{Datatype, Dictionary, FixDatatype, TagU32};
2use smartstring::alias::String as SmartString;
3
4pub trait IsFieldDefinition {
5    /// Returns the FIX tag associated with `self`.
6    fn tag(&self) -> TagU32;
7
8    /// Returns the official, ASCII, human-readable name associated with `self`.
9    fn name(&self) -> &str;
10
11    /// Returns the field location of `self`.
12    fn location(&self) -> FieldLocation;
13}
14
15/// A field is the most granular message structure abstraction. It carries a
16/// specific business meaning as described by the FIX specifications. The data
17/// domain of a [`Field`] is either a [`Datatype`] or a "code set", i.e.
18/// enumeration.
19#[derive(Debug, Copy, Clone)]
20pub struct Field<'a>(&'a Dictionary, &'a FieldData);
21
22/// A field is identified by a unique tag number and a name. Each field in a
23/// message is associated with a value.
24#[derive(Clone, Debug)]
25pub struct FieldData {
26    /// A human readable string representing the name of the field.
27    pub(crate) name: SmartString,
28    /// **Primary key.** A positive integer representing the unique
29    /// identifier for this field type.
30    pub(crate) tag: u32,
31    /// The datatype of the field.
32    pub(crate) data_type_name: SmartString,
33    /// The associated data field. If given, this field represents the length of
34    /// the referenced data field
35    pub(crate) associated_data_tag: Option<usize>,
36    pub(crate) value_restrictions: Option<Vec<FieldEnumData>>,
37    /// Indicates whether the field is required in an XML message.
38    pub(crate) required: bool,
39    pub(crate) description: Option<String>,
40}
41
42#[derive(Clone, Debug)]
43pub(crate) struct FieldEnumData {
44    pub(crate) value: String,
45    pub(crate) description: String,
46}
47
48/// A limitation imposed on the value of a specific FIX [`Field`].  Also known as
49/// "code set".
50#[derive(Debug)]
51#[allow(dead_code)]
52pub struct FieldEnum<'a>(&'a Dictionary, &'a FieldEnumData);
53
54impl<'a> FieldEnum<'a> {
55    /// Returns the string representation of this field variant.
56    pub fn value(&self) -> &str {
57        &self.1.value[..]
58    }
59
60    /// Returns the documentation description for `self`.
61    pub fn description(&self) -> &str {
62        &self.1.description[..]
63    }
64}
65
66impl<'a> Field<'a> {
67    pub fn new(dictionary: &'a Dictionary, field_data: &'a FieldData) -> Self {
68        Self(dictionary, field_data)
69    }
70
71    pub fn doc_url_onixs(&self, version: &str) -> String {
72        let v = match version {
73            "FIX.4.0" => "4.0",
74            "FIX.4.1" => "4.1",
75            "FIX.4.2" => "4.2",
76            "FIX.4.3" => "4.3",
77            "FIX.4.4" => "4.4",
78            "FIX.5.0" => "5.0",
79            "FIX.5.0SP1" => "5.0.SP1",
80            "FIX.5.0SP2" => "5.0.SP2",
81            "FIXT.1.1" => "FIXT.1.1",
82            s => s,
83        };
84        format!(
85            "https://www.onixs.biz/fix-dictionary/{}/tagNum_{}.html",
86            v,
87            self.1.tag.to_string().as_str()
88        )
89    }
90
91    pub fn is_num_in_group(&self) -> bool {
92        fn nth_char_is_uppercase(s: &str, i: usize) -> bool {
93            s.chars().nth(i).map(|c| c.is_ascii_uppercase()) == Some(true)
94        }
95
96        self.fix_datatype().base_type() == FixDatatype::NumInGroup
97            || self.name().ends_with("Len")
98            || (self.name().starts_with("No") && nth_char_is_uppercase(self.name(), 2))
99    }
100
101    /// Returns the [`FixDatatype`] of `self`.
102    pub fn fix_datatype(&self) -> FixDatatype {
103        self.data_type().basetype()
104    }
105
106    /// Returns the name of `self`. Field names are unique across each FIX
107    /// [`Dictionary`].
108    pub fn name(&self) -> &str {
109        self.1.name.as_str()
110    }
111
112    /// Returns the numeric tag of `self`. Field tags are unique across each FIX
113    /// [`Dictionary`].
114    pub fn tag(&self) -> TagU32 {
115        TagU32::new(self.1.tag).unwrap()
116    }
117
118    /// In case this field allows any value, it returns `None`; otherwise; it
119    /// returns an [`Iterator`] of all allowed values.
120    pub fn enums(&self) -> Option<impl Iterator<Item = FieldEnum<'_>>> {
121        self.1
122            .value_restrictions
123            .as_ref()
124            .map(move |v| v.iter().map(move |f| FieldEnum(self.0, f)))
125    }
126
127    /// Returns the [`Datatype`] of `self`.
128    pub fn data_type(&self) -> Datatype<'_> {
129        self.0
130            .datatype_by_name(self.1.data_type_name.as_str())
131            .unwrap()
132    }
133
134    pub fn data_tag(&self) -> Option<TagU32> {
135        self.1
136            .associated_data_tag
137            .map(|tag| TagU32::new(tag as u32).unwrap())
138    }
139
140    pub fn required_in_xml_messages(&self) -> bool {
141        self.1.required
142    }
143
144    pub fn description(&self) -> Option<&str> {
145        self.1.description.as_deref()
146    }
147}
148
149impl<'a> IsFieldDefinition for Field<'a> {
150    fn tag(&self) -> TagU32 {
151        TagU32::new(self.1.tag).expect("Invalid FIX tag (0)")
152    }
153
154    fn name(&self) -> &str {
155        self.1.name.as_str()
156    }
157
158    fn location(&self) -> FieldLocation {
159        FieldLocation::Body // FIXME
160    }
161}
162
163/// The expected location of a field within a FIX message (i.e. header, body, or trailer).
164#[derive(Debug, Copy, Clone, PartialEq, Eq)]
165pub enum FieldLocation {
166    /// The field is located inside the "Standard Header".
167    Header,
168    /// This field is located inside the body of the FIX message.
169    Body,
170    /// This field is located inside the "Standard Trailer".
171    Trailer,
172}