Skip to main content

quickfix_tokio/
field_map.rs

1//! An ordered collection of FIX fields, preserving wire order and allowing
2//! duplicate tags (as required for repeating groups).
3
4use crate::error::ConversionError;
5use crate::message::Tag;
6use crate::value::{FixDecode, FixEncode};
7
8/// A typed FIX field: a marker type carrying its tag number and value type.
9/// Implementations are code-generated from the spec XMLs (see the `fix44`
10/// module and the `generate-fix` binary).
11pub trait Field {
12    const TAG: Tag;
13    type Value: FixEncode + FixDecode;
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct TagValue {
18    pub tag: Tag,
19    pub value: Vec<u8>,
20}
21
22/// Ordered multi-map of FIX fields.
23///
24/// Fields are kept in insertion (wire) order. `set` replaces the first
25/// occurrence of a tag in place; `push` always appends, which is what
26/// repeating-group construction requires.
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct FieldMap {
29    fields: Vec<TagValue>,
30}
31
32impl FieldMap {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    pub fn len(&self) -> usize {
38        self.fields.len()
39    }
40
41    pub fn is_empty(&self) -> bool {
42        self.fields.is_empty()
43    }
44
45    pub fn clear(&mut self) {
46        self.fields.clear();
47    }
48
49    pub fn contains(&self, tag: Tag) -> bool {
50        self.fields.iter().any(|f| f.tag == tag)
51    }
52
53    /// Raw bytes of the first occurrence of `tag`.
54    pub fn get_raw(&self, tag: Tag) -> Option<&[u8]> {
55        self.fields.iter().find(|f| f.tag == tag).map(|f| f.value.as_slice())
56    }
57
58    /// Decode the first occurrence of `tag` as `T`.
59    pub fn get<T: FixDecode>(&self, tag: Tag) -> Result<T, ConversionError> {
60        let raw = self.get_raw(tag).ok_or(ConversionError::FieldNotFound { tag })?;
61        T::decode(tag, raw)
62    }
63
64    /// Decode the first occurrence of `tag` as `T`, or `None` when absent.
65    pub fn get_opt<T: FixDecode>(&self, tag: Tag) -> Result<Option<T>, ConversionError> {
66        match self.get_raw(tag) {
67            Some(raw) => T::decode(tag, raw).map(Some),
68            None => Ok(None),
69        }
70    }
71
72    pub fn get_string(&self, tag: Tag) -> Result<String, ConversionError> {
73        self.get::<String>(tag)
74    }
75
76    // ----- typed accessors (see [`Field`]) -----
77
78    pub fn get_field<F: Field>(&self) -> Result<F::Value, ConversionError> {
79        self.get::<F::Value>(F::TAG)
80    }
81
82    pub fn get_field_opt<F: Field>(&self) -> Result<Option<F::Value>, ConversionError> {
83        self.get_opt::<F::Value>(F::TAG)
84    }
85
86    pub fn set_field<F: Field>(&mut self, value: F::Value) {
87        self.set(F::TAG, value);
88    }
89
90    pub fn has_field<F: Field>(&self) -> bool {
91        self.contains(F::TAG)
92    }
93
94    /// Replace the first occurrence of `tag` (keeping its position), or append.
95    pub fn set(&mut self, tag: Tag, value: impl FixEncode) {
96        let mut buf = Vec::new();
97        value.encode(&mut buf);
98        self.set_raw(tag, buf);
99    }
100
101    pub fn set_raw(&mut self, tag: Tag, value: Vec<u8>) {
102        match self.fields.iter_mut().find(|f| f.tag == tag) {
103            Some(f) => f.value = value,
104            None => self.fields.push(TagValue { tag, value }),
105        }
106    }
107
108    /// Append a field regardless of whether the tag already exists.
109    pub fn push(&mut self, tag: Tag, value: impl FixEncode) {
110        let mut buf = Vec::new();
111        value.encode(&mut buf);
112        self.fields.push(TagValue { tag, value: buf });
113    }
114
115    /// Remove all occurrences of `tag`. Returns true if anything was removed.
116    pub fn remove(&mut self, tag: Tag) -> bool {
117        let before = self.fields.len();
118        self.fields.retain(|f| f.tag != tag);
119        self.fields.len() != before
120    }
121
122    pub fn iter(&self) -> impl Iterator<Item = &TagValue> {
123        self.fields.iter()
124    }
125
126    pub(crate) fn fields(&self) -> &[TagValue] {
127        &self.fields
128    }
129
130    pub(crate) fn push_tag_value(&mut self, tv: TagValue) {
131        self.fields.push(tv);
132    }
133
134    pub(crate) fn take_fields(&mut self) -> Vec<TagValue> {
135        std::mem::take(&mut self.fields)
136    }
137
138    pub(crate) fn set_fields(&mut self, fields: Vec<TagValue>) {
139        self.fields = fields;
140    }
141
142    /// Total serialized size of these fields: `tag=value<SOH>` for each.
143    pub fn wire_len(&self) -> usize {
144        self.fields
145            .iter()
146            .map(|f| dec_len(f.tag) + 1 + f.value.len() + 1)
147            .sum()
148    }
149
150    /// Serialize fields in stored order as `tag=value<SOH>`.
151    pub fn write_to(&self, buf: &mut Vec<u8>) {
152        for f in &self.fields {
153            write_tag_value(buf, f.tag, &f.value);
154        }
155    }
156
157    // ----- repeating groups -----
158
159    /// Read the repeating group counted by `template.num_tag`.
160    ///
161    /// Group instances are split on `template.delimiter()`. Any tag that is
162    /// not in the template's member set terminates the group section.
163    pub fn read_groups(&self, template: &GroupTemplate) -> Result<Vec<FieldMap>, ConversionError> {
164        let Some(pos) = self.fields.iter().position(|f| f.tag == template.num_tag) else {
165            return Ok(Vec::new());
166        };
167        let declared: usize = self.get(template.num_tag)?;
168
169        let mut groups: Vec<FieldMap> = Vec::new();
170        for f in &self.fields[pos + 1..] {
171            if f.tag == template.delimiter() {
172                groups.push(FieldMap::new());
173            } else if groups.is_empty() || !template.is_member(f.tag) {
174                break;
175            }
176            match groups.last_mut() {
177                Some(g) => g.push_tag_value(f.clone()),
178                // Member tag before the first delimiter: malformed group.
179                None => {
180                    return Err(ConversionError::InvalidValue {
181                        tag: f.tag,
182                        value: String::from_utf8_lossy(&f.value).into_owned(),
183                    });
184                }
185            }
186        }
187
188        if groups.len() != declared {
189            return Err(ConversionError::InvalidValue {
190                tag: template.num_tag,
191                value: declared.to_string(),
192            });
193        }
194        Ok(groups)
195    }
196
197    /// Append repeating-group instances, setting/updating the count field.
198    ///
199    /// Instances must have the delimiter tag as their first field; members are
200    /// appended verbatim in the order given.
201    pub fn write_groups(&mut self, template: &GroupTemplate, groups: &[FieldMap]) {
202        self.set(template.num_tag, groups.len());
203        for g in groups {
204            debug_assert_eq!(
205                g.fields.first().map(|f| f.tag),
206                Some(template.delimiter()),
207                "group instance must start with its delimiter tag"
208            );
209            for f in &g.fields {
210                self.fields.push(f.clone());
211            }
212        }
213    }
214}
215
216/// Describes a repeating group: its NumInGroup counter tag and its member
217/// tags in required order. The first member tag is the delimiter.
218///
219/// Members must include the tags of any nested groups (counter and members);
220/// nested instances stay flat inside each returned `FieldMap` and can be
221/// split further with the nested group's own template.
222#[derive(Debug, Clone)]
223pub struct GroupTemplate {
224    pub num_tag: Tag,
225    pub member_tags: Vec<Tag>,
226}
227
228impl GroupTemplate {
229    pub fn new(num_tag: Tag, member_tags: Vec<Tag>) -> Self {
230        assert!(!member_tags.is_empty(), "group template needs at least a delimiter tag");
231        Self { num_tag, member_tags }
232    }
233
234    pub fn delimiter(&self) -> Tag {
235        self.member_tags[0]
236    }
237
238    pub fn is_member(&self, tag: Tag) -> bool {
239        self.member_tags.contains(&tag)
240    }
241}
242
243pub(crate) fn write_tag_value(buf: &mut Vec<u8>, tag: Tag, value: &[u8]) {
244    buf.extend_from_slice(tag.to_string().as_bytes());
245    buf.push(b'=');
246    buf.extend_from_slice(value);
247    buf.push(crate::message::SOH);
248}
249
250fn dec_len(v: crate::message::Tag) -> usize {
251    let (mut v, mut n) = if v < 0 { (-(v as i64), 2usize) } else { (v as i64, 1) };
252    while v >= 10 {
253        v /= 10;
254        n += 1;
255    }
256    n
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn set_replaces_push_appends() {
265        let mut fm = FieldMap::new();
266        fm.set(55, "TSLA");
267        fm.set(54, '1');
268        fm.set(55, "AAPL");
269        assert_eq!(fm.len(), 2);
270        assert_eq!(fm.get_string(55).unwrap(), "AAPL");
271
272        fm.push(55, "MSFT");
273        assert_eq!(fm.len(), 3);
274        // get returns first occurrence
275        assert_eq!(fm.get_string(55).unwrap(), "AAPL");
276    }
277
278    #[test]
279    fn group_roundtrip() {
280        // NoMDEntryTypes-style group: 267 counts, members [269]
281        let tpl = GroupTemplate::new(267, vec![269]);
282        let mut body = FieldMap::new();
283        body.set(262, "REQ1");
284
285        let mut g1 = FieldMap::new();
286        g1.push(269, '0');
287        let mut g2 = FieldMap::new();
288        g2.push(269, '1');
289        body.write_groups(&tpl, &[g1, g2]);
290
291        assert_eq!(body.get::<usize>(267).unwrap(), 2);
292        let groups = body.read_groups(&tpl).unwrap();
293        assert_eq!(groups.len(), 2);
294        assert_eq!(groups[0].get::<char>(269).unwrap(), '0');
295        assert_eq!(groups[1].get::<char>(269).unwrap(), '1');
296    }
297
298    #[test]
299    fn group_count_mismatch_errors() {
300        let tpl = GroupTemplate::new(267, vec![269]);
301        let mut body = FieldMap::new();
302        body.set(267, 3usize);
303        body.push(269, '0');
304        assert!(body.read_groups(&tpl).is_err());
305    }
306
307    #[test]
308    fn group_terminates_on_foreign_tag() {
309        let tpl = GroupTemplate::new(267, vec![269, 270]);
310        let mut body = FieldMap::new();
311        body.set(267, 1usize);
312        body.push(269, '0');
313        body.push(270, "101.5");
314        body.push(58, "trailing field, not a member");
315        let groups = body.read_groups(&tpl).unwrap();
316        assert_eq!(groups.len(), 1);
317        assert_eq!(groups[0].len(), 2);
318    }
319}