Skip to main content

hl7_2/
generic.rs

1//! Generic mode: parse anything into a navigable tree.
2//!
3//! This is the mode for the vendor whose messages you have never seen. It
4//! asks nothing of the message beyond a readable MSH header: whatever
5//! segments arrive get a node, whatever the dictionary recognises gets a
6//! named one, and whatever it does not gets a positional name and is still
7//! there to read. Nothing is dropped and nothing is an error.
8//!
9//! Node names follow the same rules as the sibling conversion crates, so a
10//! path through this tree reads the same as a key in
11//! `hl7-2-from-er7-into-json`'s output or an element in
12//! `hl7-2-from-er7-into-xml`'s:
13//!
14//! | level | known type | unknown type |
15//! |---|---|---|
16//! | segment | `PID` | `ZPD` |
17//! | field | `PID.5` | `ZPD.2` |
18//! | component | `XPN.1` (the field's type) | `ZPD.2.1` |
19//! | subcomponent | `FN.1` (the component's type) | `ZPD.2.1.1` |
20//!
21//! Alongside the name, every node carries the `er7` path that locates it
22//! ([`Node::path`]) — `PID[1]-5[1].1.2` — which is what turns "I found
23//! something here" into "…and here is how to read or write it", including
24//! for [`crate::Message::set`] and for validation diagnostics.
25
26use crate::dictionary::{Dictionary, VARIABLE};
27use er7::{Component, Repetition, Segment, Separators};
28
29/// Which level of the HL7 tree a node sits at.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Kind {
32    /// The whole message, or a message-structure group such as
33    /// `ORU_R01.ORDER_OBSERVATION`.
34    Group,
35    /// One segment occurrence.
36    Segment,
37    /// One repetition of one field.
38    Field,
39    /// One component of a field.
40    Component,
41    /// One subcomponent of a component.
42    Subcomponent,
43}
44
45/// One node of the generic tree.
46///
47/// A node is either a container (a group, a segment, or a field/component
48/// whose type the dictionary can break apart) or a leaf holding text.
49/// [`Node::text`] answers for both: on a container it is the decoded text
50/// of everything beneath, delimiters included, so `PID.5` reads as
51/// `SMITH^JOHN` whether or not the dictionary knew what an `XPN` is.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Node {
54    name: String,
55    path: String,
56    kind: Kind,
57    text: String,
58    null: bool,
59    children: Vec<Node>,
60}
61
62impl Node {
63    /// This node's dictionary-derived name, e.g. `PID.5` or `XPN.1`.
64    #[must_use]
65    pub fn name(&self) -> &str {
66        &self.name
67    }
68
69    /// The `er7` path that locates this node in the message, e.g.
70    /// `PID[1]-5[1].1.2`. Pass it to [`crate::Message::get`],
71    /// [`crate::Message::set`], or `er7`'s own query API.
72    ///
73    /// The root node has an empty path: it is the message itself.
74    #[must_use]
75    pub fn path(&self) -> &str {
76        &self.path
77    }
78
79    /// Which level of the tree this node sits at.
80    #[must_use]
81    pub fn kind(&self) -> Kind {
82        self.kind
83    }
84
85    /// The decoded text of this node and everything beneath it, with
86    /// structural delimiters intact.
87    #[must_use]
88    pub fn text(&self) -> &str {
89        &self.text
90    }
91
92    /// True when the sender wrote the HL7 explicit null `""` here, meaning
93    /// "clear this value" rather than "I have nothing to say". The
94    /// difference matters on the way to a database, so it survives parsing.
95    #[must_use]
96    pub fn is_null(&self) -> bool {
97        self.null
98    }
99
100    /// True when this node has no children: a value, not a container.
101    #[must_use]
102    pub fn is_leaf(&self) -> bool {
103        self.children.is_empty()
104    }
105
106    /// This node's children, in message order.
107    #[must_use]
108    pub fn children(&self) -> &[Node] {
109        &self.children
110    }
111
112    /// The first child named `name`.
113    ///
114    /// ```
115    /// let message = hl7_2::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1||9||SMITH^JOHN")?;
116    /// let tree = message.tree();
117    /// let pid = tree.find("PID").unwrap();
118    /// assert_eq!(pid.child("PID.5").unwrap().child("XPN.2").unwrap().text(), "JOHN");
119    /// # Ok::<(), hl7_2::Error>(())
120    /// ```
121    #[must_use]
122    pub fn child(&self, name: &str) -> Option<&Node> {
123        self.children.iter().find(|child| child.name == name)
124    }
125
126    /// Every child named `name`, in order. This is how repetitions are
127    /// read: a field sent as `A~B` is two `PID.3` children, not one.
128    pub fn children_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> {
129        self.children.iter().filter(move |child| child.name == name)
130    }
131
132    /// The first node named `name` anywhere beneath this one, searched
133    /// depth-first. Handy for reaching a segment without knowing which
134    /// groups a structure nests it in.
135    #[must_use]
136    pub fn find(&self, name: &str) -> Option<&Node> {
137        self.descendants().find(|node| node.name == name)
138    }
139
140    /// Every node named `name` anywhere beneath this one, in message order.
141    pub fn find_all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> {
142        self.descendants().filter(move |node| node.name == name)
143    }
144
145    /// Every node beneath this one, depth-first, excluding this node.
146    #[must_use]
147    pub fn descendants(&self) -> Descendants<'_> {
148        Descendants {
149            stack: self.children.iter().rev().collect(),
150        }
151    }
152}
153
154/// Depth-first iterator over a node's descendants; see [`Node::descendants`].
155#[derive(Debug)]
156pub struct Descendants<'a> {
157    stack: Vec<&'a Node>,
158}
159
160impl<'a> Iterator for Descendants<'a> {
161    type Item = &'a Node;
162
163    fn next(&mut self) -> Option<&'a Node> {
164        let node = self.stack.pop()?;
165        self.stack.extend(node.children.iter().rev());
166        Some(node)
167    }
168}
169
170/// Build the root node for a message whose segments have already been
171/// arranged by [`crate::structure`], or left flat.
172pub(crate) fn root(name: &str, children: Vec<Node>) -> Node {
173    let text = children
174        .iter()
175        .map(|child| child.text.as_str())
176        .collect::<Vec<&str>>()
177        .join("\r");
178    Node {
179        name: name.to_string(),
180        path: String::new(),
181        kind: Kind::Group,
182        text,
183        null: false,
184        children,
185    }
186}
187
188/// Build a group node, named the way the family names groups: the message
189/// structure ID, a dot, then the group name — `ORU_R01.ORDER_OBSERVATION`.
190pub(crate) fn group(root_name: &str, name: &str, children: Vec<Node>) -> Node {
191    let mut node = root(&format!("{root_name}.{name}"), children);
192    node.path = String::new();
193    node
194}
195
196/// Build the node for one segment occurrence.
197///
198/// `occurrence` is 1-based among segments of the same name, and becomes the
199/// `[n]` in every path beneath — so a node found in the second `OBX` knows
200/// it came from `OBX[2]`.
201pub(crate) fn segment(
202    seg: &Segment,
203    occurrence: usize,
204    dictionary: &Dictionary,
205    separators: &Separators,
206) -> Node {
207    let base = format!("{}[{occurrence}]", seg.name);
208    // OBX-5's data type is whatever OBX-2 says it is.
209    let variable = dictionary.variable_type(seg).map(str::to_string);
210    let mut children = Vec::new();
211    for (index, field) in seg.fields.iter().enumerate() {
212        if field.is_empty() {
213            continue;
214        }
215        let number = index + 1;
216        let name = format!("{}.{number}", seg.name);
217        let data_type = match dictionary.field_type(&seg.name, number) {
218            Some(VARIABLE) => variable.as_deref(),
219            other => other,
220        };
221        for (repetition, occurrence) in field.repetitions.iter().enumerate() {
222            if occurrence.is_empty() {
223                continue;
224            }
225            children.push(field_node(
226                &name,
227                &format!("{base}-{number}[{}]", repetition + 1),
228                data_type,
229                occurrence,
230                dictionary,
231                separators,
232            ));
233        }
234    }
235    Node {
236        name: seg.name.clone(),
237        path: base,
238        kind: Kind::Segment,
239        text: seg.to_text(separators),
240        null: false,
241        children,
242    }
243}
244
245/// One field repetition. Expanded into components when the dictionary knows
246/// the field's type, kept whole when it does not and the value is simple,
247/// and given positional names when it is neither.
248fn field_node(
249    name: &str,
250    path: &str,
251    data_type: Option<&str>,
252    repetition: &Repetition,
253    dictionary: &Dictionary,
254    separators: &Separators,
255) -> Node {
256    let text = repetition.to_text(separators);
257    let mut node = Node {
258        name: name.to_string(),
259        path: path.to_string(),
260        kind: Kind::Field,
261        text,
262        null: repetition.is_null(),
263        children: Vec::new(),
264    };
265    if repetition.is_null() {
266        return node;
267    }
268    if let Some(components) = data_type.and_then(|dt| dictionary.composite_components(dt)) {
269        let data_type = data_type.unwrap_or_default();
270        for (index, component) in repetition.components.iter().enumerate() {
271            if component.is_empty() {
272                continue;
273            }
274            node.children.push(component_node(
275                &format!("{data_type}.{}", index + 1),
276                &format!("{path}.{}", index + 1),
277                components.get(index).map(String::as_str),
278                component,
279                dictionary,
280                separators,
281            ));
282        }
283        return node;
284    }
285    // Unknown or primitive type. A single value stays a leaf; anything with
286    // internal structure still gets nodes, named positionally.
287    if let [only] = repetition.components.as_slice()
288        && only.subcomponents.len() <= 1
289    {
290        return node;
291    }
292    for (index, component) in repetition.components.iter().enumerate() {
293        if component.is_empty() {
294            continue;
295        }
296        node.children.push(component_node(
297            &format!("{name}.{}", index + 1),
298            &format!("{path}.{}", index + 1),
299            None,
300            component,
301            dictionary,
302            separators,
303        ));
304    }
305    node
306}
307
308/// One component. A component whose own type is composite — `XPN.1` is an
309/// `FN`, `CX.4` is an `HD` — expands into subcomponents named after that
310/// type; everything else is a leaf or positional.
311fn component_node(
312    name: &str,
313    path: &str,
314    data_type: Option<&str>,
315    component: &Component,
316    dictionary: &Dictionary,
317    separators: &Separators,
318) -> Node {
319    let mut node = Node {
320        name: name.to_string(),
321        path: path.to_string(),
322        kind: Kind::Component,
323        text: component.to_text(separators),
324        null: component.is_null(),
325        children: Vec::new(),
326    };
327    if component.is_null() || component.subcomponents.len() <= 1 {
328        return node;
329    }
330    let composite = data_type.filter(|dt| dictionary.is_composite(dt));
331    for (index, subcomponent) in component.subcomponents.iter().enumerate() {
332        if subcomponent.is_empty() {
333            continue;
334        }
335        let number = index + 1;
336        node.children.push(Node {
337            name: match composite {
338                Some(data_type) => format!("{data_type}.{number}"),
339                None => format!("{name}.{number}"),
340            },
341            path: format!("{path}.{number}"),
342            kind: Kind::Subcomponent,
343            text: subcomponent.value(separators).into_owned(),
344            null: subcomponent.is_null(),
345            children: Vec::new(),
346        });
347    }
348    node
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    fn tree(text: &str) -> Node {
356        crate::parse(text).unwrap().tree()
357    }
358
359    const HEADER: &str = "MSH|^~\\&|hphis||EPIC||20131011093851||ORU^R01|14AAACVDD|P|2.5";
360
361    #[test]
362    fn names_known_types_after_the_type_and_the_rest_positionally() {
363        let tree = tree(&format!("{HEADER}\rPID|1||241900||TEST^FOUAZ\rZPD|a^b"));
364        let pid = tree.find("PID").unwrap();
365        let name = pid.child("PID.5").unwrap();
366        assert_eq!(name.text(), "TEST^FOUAZ");
367        // XPN.1 is an FN, so its first subcomponent is named FN.1.
368        assert_eq!(name.child("XPN.1").unwrap().text(), "TEST");
369        assert_eq!(name.child("XPN.2").unwrap().text(), "FOUAZ");
370        // A Z-segment has no dictionary entry, so names stay positional and
371        // nothing is lost.
372        let zpd = tree.find("ZPD").unwrap();
373        assert_eq!(
374            zpd.child("ZPD.1").unwrap().child("ZPD.1.1").unwrap().text(),
375            "a"
376        );
377        assert_eq!(
378            zpd.child("ZPD.1").unwrap().child("ZPD.1.2").unwrap().text(),
379            "b"
380        );
381    }
382
383    #[test]
384    fn every_node_carries_the_path_that_reads_it_back() {
385        let message = crate::parse(&format!("{HEADER}\rPID|1||241900||TEST^FOUAZ")).unwrap();
386        let tree = message.tree();
387        let given = tree.find("XPN.2").unwrap();
388        assert_eq!(given.path(), "PID[1]-5[1].2");
389        assert_eq!(message.get(given.path()).unwrap().as_deref(), Some("FOUAZ"));
390    }
391
392    #[test]
393    fn repetitions_are_separate_siblings() {
394        let tree = tree(&format!("{HEADER}\rPID|1||A~B~C"));
395        let pid = tree.find("PID").unwrap();
396        let ids: Vec<&str> = pid.children_named("PID.3").map(Node::text).collect();
397        assert_eq!(ids, ["A", "B", "C"]);
398        assert_eq!(pid.child("PID.3").unwrap().path(), "PID[1]-3[1]");
399        assert_eq!(
400            pid.children_named("PID.3").nth(2).unwrap().path(),
401            "PID[1]-3[3]"
402        );
403    }
404
405    #[test]
406    fn the_explicit_null_survives() {
407        let tree = tree(&format!("{HEADER}\rPID|1||\"\""));
408        let field = tree.find("PID").unwrap().child("PID.3").unwrap();
409        assert!(field.is_null(), "explicit null must not read as absent");
410        assert!(tree.find("PID").unwrap().child("PID.4").is_none());
411    }
412
413    #[test]
414    fn obx_5_takes_its_type_from_obx_2() {
415        let coded = tree(&format!("{HEADER}\rOBX|1|CE|X||a^b^c"));
416        let value = coded.find("OBX").unwrap().child("OBX.5").unwrap();
417        assert_eq!(value.child("CE.1").unwrap().text(), "a");
418        // A primitive OBX-2 leaves OBX-5 a plain value.
419        let numeric = tree(&format!("{HEADER}\rOBX|1|NM|X||7.4"));
420        assert_eq!(
421            numeric.find("OBX").unwrap().child("OBX.5").unwrap().text(),
422            "7.4"
423        );
424    }
425
426    #[test]
427    fn groups_nest_under_the_structure_id() {
428        let tree = tree(&format!("{HEADER}\rPID|1\rOBR|1\rOBX|1|NM|X||7"));
429        assert_eq!(tree.name(), "ORU_R01");
430        let result = tree.child("ORU_R01.PATIENT_RESULT").unwrap();
431        let order = result.child("ORU_R01.ORDER_OBSERVATION").unwrap();
432        assert!(
433            order
434                .child("ORU_R01.OBSERVATION")
435                .unwrap()
436                .child("OBX")
437                .is_some()
438        );
439        // ... and find() reaches through them without knowing the nesting.
440        assert!(tree.find("OBX").is_some());
441    }
442
443    #[test]
444    fn descendants_walks_everything_once() {
445        let tree = tree(&format!("{HEADER}\rPID|1||A~B"));
446        let count = tree.descendants().count();
447        let named = tree.find_all("PID.3").count();
448        assert_eq!(named, 2);
449        assert!(count > named);
450    }
451}