Skip to main content

mig_assembly/
transaction_view.rs

1//! One transaction of a message, as if the message held no other.
2//!
3//! A UTILMD message repeats SG4, one per transaction (`IDE+24`), and the AHB
4//! states its conditions per transaction: "Wenn SG4 STS+7++ZG9 vorhanden" asks
5//! about the transaction the annotated field sits in. Asked of the whole
6//! message, another transaction answers. A list PID (55065, 55195, …) adds an
7//! SG4 of another kind, the list itself (`IDE+Z01`), which the transactions'
8//! conditions do refer to ("Wenn im SG4 IDE+Z01 … STS+E01 nicht vorhanden").
9//!
10//! [`TransactionView`] keeps the repetition of the transaction group asked
11//! about, first, and every repetition of another kind; it leaves out the other
12//! repetitions of the same kind.
13
14use crate::assembler::{AssembledGroupInstance, AssembledSegment, AssembledTree};
15
16/// [`AssembledTree::transaction_view`]'s result.
17#[derive(Debug, Clone)]
18pub struct TransactionView {
19    /// The message with the other transactions of the same kind left out. The
20    /// transaction itself is repetition 0 of its group.
21    pub tree: AssembledTree,
22    /// Segment numbers (`AssembledSegment::segment_number`) of everything left
23    /// out, sorted.
24    pub left_out: Vec<u32>,
25}
26
27/// The kind of a repetition: its entry segment's tag and qualifier
28/// (`IDE`, `24`).
29fn kind(instance: &AssembledGroupInstance) -> Option<(&str, &str)> {
30    let entry = instance.segments.first()?;
31    let qualifier = entry
32        .elements
33        .first()
34        .and_then(|e| e.first())
35        .map_or("", String::as_str);
36    Some((entry.tag.as_str(), qualifier))
37}
38
39fn segment_numbers(instance: &AssembledGroupInstance, out: &mut Vec<u32>) {
40    let own = instance.segments.iter().chain(&instance.skipped_segments);
41    out.extend(own.filter_map(|s: &AssembledSegment| s.segment_number));
42    for group in &instance.child_groups {
43        for repetition in &group.repetitions {
44            segment_numbers(repetition, out);
45        }
46    }
47}
48
49impl AssembledTree {
50    /// The message as transaction `index` of the top-level group `group_id`
51    /// sees it (see the [module](crate::transaction_view)). `None` when the
52    /// group has no such repetition.
53    pub fn transaction_view(&self, group_id: &str, index: usize) -> Option<TransactionView> {
54        let position = self.groups.iter().position(|g| g.group_id == group_id)?;
55        let group = &self.groups[position];
56        let own = group.repetitions.get(index)?;
57        let own_kind = kind(own);
58        let mut repetitions = vec![own.clone()];
59        let mut left_out = Vec::new();
60        for (i, repetition) in group.repetitions.iter().enumerate() {
61            if i == index {
62                continue;
63            }
64            if kind(repetition) == own_kind {
65                segment_numbers(repetition, &mut left_out);
66            } else {
67                repetitions.push(repetition.clone());
68            }
69        }
70        left_out.sort_unstable();
71        let mut groups = Vec::with_capacity(self.groups.len());
72        for (i, g) in self.groups.iter().enumerate() {
73            if i == position {
74                let mut view = g.clone_shallow();
75                view.repetitions = std::mem::take(&mut repetitions);
76                groups.push(view);
77            } else {
78                groups.push(g.clone());
79            }
80        }
81        Some(TransactionView {
82            tree: AssembledTree {
83                segments: self.segments.clone(),
84                groups,
85                post_group_start: self.post_group_start,
86                inter_group_segments: self.inter_group_segments.clone(),
87            },
88            left_out,
89        })
90    }
91}
92
93impl crate::assembler::AssembledGroup {
94    /// The group without its repetitions.
95    fn clone_shallow(&self) -> Self {
96        Self {
97            group_id: self.group_id.clone(),
98            repetitions: Vec::new(),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::assembler::AssembledGroup;
107
108    fn seg(text: &str, number: u32) -> AssembledSegment {
109        let mut parts = text.split('+');
110        AssembledSegment {
111            tag: parts.next().unwrap().to_string(),
112            elements: parts.map(|e| vec![e.to_string()]).collect(),
113            mig_number: None,
114            segment_number: Some(number),
115        }
116    }
117
118    fn inst(
119        segments: Vec<AssembledSegment>,
120        child_groups: Vec<AssembledGroup>,
121    ) -> AssembledGroupInstance {
122        AssembledGroupInstance {
123            segments,
124            child_groups,
125            entry_mig_number: None,
126            variant_mig_numbers: vec![],
127            skipped_segments: vec![],
128            skipped_positions: vec![],
129        }
130    }
131
132    fn sg8(seq: &str, number: u32) -> AssembledGroup {
133        AssembledGroup {
134            group_id: "SG8".into(),
135            repetitions: vec![inst(vec![seg(seq, number)], vec![])],
136        }
137    }
138
139    /// BGM, SG2, then SG4: a list (`IDE+Z01`) and three transactions.
140    fn message() -> AssembledTree {
141        AssembledTree {
142            segments: vec![seg("BGM+E01", 1)],
143            groups: vec![
144                AssembledGroup {
145                    group_id: "SG2".into(),
146                    repetitions: vec![inst(vec![seg("NAD+MS", 2)], vec![])],
147                },
148                AssembledGroup {
149                    group_id: "SG4".into(),
150                    repetitions: vec![
151                        inst(vec![seg("IDE+Z01", 3)], vec![]),
152                        inst(vec![seg("IDE+24", 4)], vec![sg8("SEQ+Z01", 5)]),
153                        inst(vec![seg("IDE+24", 6)], vec![sg8("SEQ+Z02", 7)]),
154                        inst(vec![seg("IDE+24", 8)], vec![sg8("SEQ+Z03", 9)]),
155                    ],
156                },
157            ],
158            post_group_start: 1,
159            inter_group_segments: Default::default(),
160        }
161    }
162
163    fn entries(tree: &AssembledTree) -> Vec<String> {
164        tree.groups[1]
165            .repetitions
166            .iter()
167            .map(|r| format!("{}+{}", r.segments[0].tag, r.segments[0].elements[0][0]))
168            .collect()
169    }
170
171    #[test]
172    fn a_transaction_sees_itself_first_and_the_list_but_no_other_transaction() {
173        let view = message().transaction_view("SG4", 2).unwrap();
174        assert_eq!(entries(&view.tree), ["IDE+24", "IDE+Z01"]);
175        assert_eq!(
176            view.tree.groups[1].repetitions[0].segments[0].segment_number,
177            Some(6)
178        );
179        assert_eq!(view.left_out, [4, 5, 8, 9]);
180        // What is not the transaction group is kept as it is.
181        assert_eq!(view.tree.groups[0], message().groups[0]);
182        assert_eq!(view.tree.segments, message().segments);
183    }
184
185    #[test]
186    fn the_list_sees_every_transaction() {
187        let view = message().transaction_view("SG4", 0).unwrap();
188        assert_eq!(
189            entries(&view.tree),
190            ["IDE+Z01", "IDE+24", "IDE+24", "IDE+24"]
191        );
192        assert!(view.left_out.is_empty());
193    }
194
195    #[test]
196    fn no_such_group_or_repetition_is_no_view() {
197        assert!(message().transaction_view("SG5", 0).is_none());
198        assert!(message().transaction_view("SG4", 4).is_none());
199    }
200}