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}