Skip to main content

truefix_core/
field_map.rs

1//! An ordered collection of fields and nested repeating groups.
2
3use crate::field::Field;
4use crate::group::Group;
5
6/// One member of a [`FieldMap`]: either a plain field or a repeating group.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub(crate) enum Member {
9    /// A plain field.
10    Field(Field),
11    /// A repeating group: its count tag and ordered entries.
12    Group {
13        /// The NoXxx count tag.
14        count_tag: u32,
15        /// The group entries (each a `FieldMap`).
16        entries: Vec<FieldMap>,
17        /// NEW-22 (feature 009): the count declared on the wire, if decoded from one and it
18        /// didn't match `entries.len()` — see `Group::declared_count`'s doc.
19        declared_count: Option<i64>,
20    },
21}
22
23/// An ordered map of FIX fields (and nested groups), preserving wire order.
24#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct FieldMap {
26    members: Vec<Member>,
27}
28
29impl FieldMap {
30    /// Create an empty field map.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Append a field, preserving insertion order (used by the decoder).
36    pub fn add_field(&mut self, field: Field) {
37        self.members.push(Member::Field(field));
38    }
39
40    /// Set a top-level field, replacing an existing one with the same tag (in place, preserving
41    /// its position), else appending. If more than one field with this tag is already present
42    /// (NEW-81, feature 009 -- e.g. because `add_field` was used to push duplicates directly,
43    /// bypassing this method's usual dedup), every stale copy is removed, leaving only the new
44    /// value at the first occurrence's position.
45    pub fn set(&mut self, field: Field) {
46        let tag = field.tag();
47        let mut replaced = false;
48        self.members.retain_mut(|m| {
49            let Member::Field(existing) = m else {
50                return true;
51            };
52            if existing.tag() != tag {
53                return true;
54            }
55            if replaced {
56                return false;
57            }
58            *existing = field.clone();
59            replaced = true;
60            true
61        });
62        if !replaced {
63            self.members.push(Member::Field(field));
64        }
65    }
66
67    /// Get the first top-level field with `tag`.
68    pub fn get(&self, tag: u32) -> Option<&Field> {
69        self.members.iter().find_map(|m| match m {
70            Member::Field(f) if f.tag() == tag => Some(f),
71            _ => None,
72        })
73    }
74
75    /// Returns `true` if a top-level field with `tag` is present.
76    pub fn contains(&self, tag: u32) -> bool {
77        self.get(tag).is_some()
78    }
79
80    /// Append a repeating group.
81    pub fn add_group(&mut self, group: Group) {
82        let (count_tag, entries, declared_count) = group.into_parts();
83        self.members.push(Member::Group {
84            count_tag,
85            entries,
86            declared_count,
87        });
88    }
89
90    /// Get the entries of the first group with `count_tag`.
91    pub fn group(&self, count_tag: u32) -> Option<&[FieldMap]> {
92        self.members.iter().find_map(|m| match m {
93            Member::Group {
94                count_tag: ct,
95                entries,
96                ..
97            } if *ct == count_tag => Some(entries.as_slice()),
98            _ => None,
99        })
100    }
101
102    /// Get one entry (by 0-based `index`) of the first group with `count_tag` (US9, feature 005,
103    /// FR-024/FR-025). `None` if the group doesn't exist or `index` is out of range.
104    pub fn get_group(&self, count_tag: u32, index: usize) -> Option<&FieldMap> {
105        self.group(count_tag).and_then(|entries| entries.get(index))
106    }
107
108    /// Replace one entry (by 0-based `index`) of the first group with `count_tag` (US9, feature
109    /// 005, FR-024/FR-025). No-op if the group doesn't exist or `index` is out of range.
110    pub fn replace_group(&mut self, count_tag: u32, index: usize, entry: FieldMap) {
111        if let Some(Member::Group {
112            count_tag: ct,
113            entries,
114            ..
115        }) = self
116            .members
117            .iter_mut()
118            .find(|m| matches!(m, Member::Group { count_tag: ct, .. } if *ct == count_tag))
119        {
120            debug_assert_eq!(*ct, count_tag);
121            if let Some(slot) = entries.get_mut(index) {
122                *slot = entry;
123            }
124        }
125    }
126
127    /// Remove one entry (by 0-based `index`) of the first group with `count_tag` (US9, feature
128    /// 005, FR-024/FR-025), shifting later entries down. No-op if the group doesn't exist or
129    /// `index` is out of range.
130    pub fn remove_group(&mut self, count_tag: u32, index: usize) {
131        if let Some(Member::Group {
132            count_tag: ct,
133            entries,
134            declared_count,
135        }) = self
136            .members
137            .iter_mut()
138            .find(|m| matches!(m, Member::Group { count_tag: ct, .. } if *ct == count_tag))
139        {
140            debug_assert_eq!(*ct, count_tag);
141            if index < entries.len() {
142                entries.remove(index);
143                // NEW-22 (feature 009): the entry count just changed -- a stale wire-declared
144                // count would otherwise be re-encoded verbatim, now genuinely wrong rather than
145                // preserving fidelity to anything real.
146                *declared_count = None;
147            }
148        }
149    }
150
151    /// Iterate the top-level fields (skipping repeating groups), in order.
152    pub fn fields(&self) -> impl Iterator<Item = &Field> {
153        self.members.iter().filter_map(|m| match m {
154            Member::Field(f) => Some(f),
155            Member::Group { .. } => None,
156        })
157    }
158
159    /// Iterate every top-level member (feature 011, FR-001) — both plain fields and repeating
160    /// groups, in wire order. Unlike [`Self::fields`] (which silently skips `Member::Group`
161    /// entries), this is the accessor for callers that need to see a message's real structure —
162    /// e.g. a group whose own count tag is itself required at the message level (see
163    /// `truefix-dict`'s `present()`), or any caller walking a decoded message generically without
164    /// assuming every top-level tag is a plain field.
165    pub fn members(&self) -> impl Iterator<Item = MemberRef<'_>> {
166        self.members.iter().map(|m| match m {
167            Member::Field(f) => MemberRef::Field(f),
168            Member::Group {
169                count_tag,
170                entries,
171                declared_count,
172            } => MemberRef::Group {
173                count_tag: *count_tag,
174                entries,
175                declared_count: *declared_count,
176            },
177        })
178    }
179
180    /// Internal: ordered members, for the encoder.
181    pub(crate) fn raw_members(&self) -> &[Member] {
182        &self.members
183    }
184}
185
186/// A borrowed view of one [`FieldMap`] top-level member, returned by [`FieldMap::members`] —
187/// either a plain field or a repeating group's count tag plus its ordered entries.
188#[derive(Debug, Clone, Copy)]
189pub enum MemberRef<'a> {
190    /// A plain field.
191    Field(&'a Field),
192    /// A repeating group: its count tag, ordered entries, and (per [`crate::Group`]'s doc) the
193    /// wire-declared count when it didn't match `entries.len()`.
194    Group {
195        /// The `NoXxx` count tag.
196        count_tag: u32,
197        /// The group's ordered entries.
198        entries: &'a [FieldMap],
199        /// The count declared on the wire, if decoded from one and it didn't match
200        /// `entries.len()` (`None` for a group built directly by application code).
201        declared_count: Option<i64>,
202    },
203}