Skip to main content

truefix_core/
group.rs

1//! Repeating-group builder.
2//!
3//! A group is a count tag (NoXxx) plus ordered entries; each entry is a [`FieldMap`] whose
4//! first field is the group delimiter. Dictionary-driven *parsing* of groups from a flat
5//! wire message is added in `truefix-dict` (Stage S4); this type lets callers *build* and
6//! *encode* groups (including nested ones) today.
7
8use crate::field_map::FieldMap;
9
10/// Supplies repeating-group structure to the dictionary-driven decoder without `truefix-core`
11/// depending on `truefix-dict` (the dictionary implements this trait). See [`crate::decode_with_groups`].
12pub trait GroupSpec {
13    /// If `count_tag` is a repeating-group count tag, return its `(delimiter, member_tags)`.
14    fn group_of(&self, count_tag: u32) -> Option<(u32, &[u32])>;
15}
16
17/// A repeating group: a count tag plus its ordered entries.
18#[derive(Debug, Clone, Default, PartialEq, Eq)]
19pub struct Group {
20    count_tag: u32,
21    entries: Vec<FieldMap>,
22    /// NEW-22 (feature 009): the count this group's `NoXxx` field declared on the wire, when
23    /// decoded from one — `None` for a group built fresh (e.g. by application code constructing
24    /// an outbound message), which always encodes `entries.len()`, same as before this field
25    /// existed. Preserves round-trip fidelity for a decoded group whose declared count didn't
26    /// match its actual entry count (previously silently "corrected" to `entries.len()` on
27    /// re-encode, discarding the wire's own original — possibly malformed — declaration).
28    declared_count: Option<i64>,
29}
30
31impl Group {
32    /// Create an empty group for the given NoXxx count tag.
33    pub fn new(count_tag: u32) -> Self {
34        Self {
35            count_tag,
36            entries: Vec::new(),
37            declared_count: None,
38        }
39    }
40
41    /// Append an entry; returns `&mut self` for chaining.
42    pub fn add_entry(&mut self, entry: FieldMap) -> &mut Self {
43        self.entries.push(entry);
44        self
45    }
46
47    /// The NoXxx count tag.
48    pub fn count_tag(&self) -> u32 {
49        self.count_tag
50    }
51
52    /// Number of entries.
53    pub fn len(&self) -> usize {
54        self.entries.len()
55    }
56
57    /// Whether the group has no entries.
58    pub fn is_empty(&self) -> bool {
59        self.entries.is_empty()
60    }
61
62    /// NEW-22 (feature 009): record the count actually declared on the wire (set by the decoder;
63    /// not exposed for outbound-only construction, which has no such thing to preserve).
64    pub(crate) fn set_declared_count(&mut self, n: i64) {
65        self.declared_count = Some(n);
66    }
67
68    /// Consume the group into `(count_tag, entries, declared_count)`.
69    pub(crate) fn into_parts(self) -> (u32, Vec<FieldMap>, Option<i64>) {
70        (self.count_tag, self.entries, self.declared_count)
71    }
72}