Skip to main content

hl7_2/
message.rs

1//! The parsed message: what release it speaks, what structure it is, and
2//! how to read, change, and write it back.
3//!
4//! A [`Message`] is an `er7::Message` plus the two things `er7` cannot
5//! know — which HL7 release the sender used, and which dictionary that
6//! selects — and every mode reads it through those. It is also the
7//! multi-modal escape hatch the design calls for: the raw message is never
8//! consumed or discarded, so a caller who has decoded into a struct can
9//! still reach [`Message::raw`] for the one vendor field the struct does
10//! not model, without re-parsing.
11
12use crate::dictionary::Dictionary;
13use crate::generic::Node;
14use crate::structure::{self, Layout};
15use crate::validate::{Diagnostic, Severity};
16use crate::{Error, Options, Version, generic};
17use er7::{Path, Segment, Separators};
18use std::sync::Arc;
19
20/// One parsed HL7 v2 message.
21///
22/// Built by [`crate::parse`] or [`crate::parse_with_options`]; see the
23/// crate documentation for the three modes that read it.
24#[derive(Debug, Clone)]
25pub struct Message {
26    raw: er7::Message,
27    version: Version,
28    dictionary: Arc<Dictionary>,
29}
30
31impl Message {
32    /// Parse `text` under `options`. See [`crate::parse_with_options`],
33    /// which is the same call under the name callers reach for.
34    pub(crate) fn parse(text: &str, options: &Options) -> Result<Message, Error> {
35        let raw = er7::parse(&crate::normalize(text))?;
36        let version = options
37            .version
38            .or_else(|| Version::from_message(&raw))
39            .unwrap_or_default();
40        let dictionary = match &options.dictionary {
41            Some(dictionary) => Arc::clone(dictionary),
42            None => version.dictionary(),
43        };
44        let message = Message {
45            raw,
46            version,
47            dictionary,
48        };
49        if options.strict {
50            let failures: Vec<Diagnostic> = message
51                .validate()
52                .into_iter()
53                .filter(|diagnostic| diagnostic.severity == Severity::Error)
54                .collect();
55            if !failures.is_empty() {
56                return Err(Error::Invalid(failures));
57            }
58        }
59        Ok(message)
60    }
61
62    /// The HL7 release this message is read as: MSH-12 resolved through
63    /// [`Version::nearest`], or whatever [`Options::version`] forced.
64    #[must_use]
65    pub fn version(&self) -> Version {
66        self.version
67    }
68
69    /// The dictionary this message is read through.
70    #[must_use]
71    pub fn dictionary(&self) -> &Dictionary {
72        &self.dictionary
73    }
74
75    /// The message structure ID: MSH-9.3 when the sender supplied one,
76    /// otherwise derived from MSH-9.1 and MSH-9.2 through the dictionary —
77    /// `ORU_R01`, `ADT_A01`, `ACK`.
78    ///
79    /// Read from the message each time rather than cached at parse, so a
80    /// message whose header was changed — by [`Message::set`] or by
81    /// [`crate::Builder`] — reports what it now says it is.
82    #[must_use]
83    pub fn structure_id(&self) -> String {
84        match self.raw.message_structure().filter(|id| !id.is_empty()) {
85            Some(id) => id,
86            None => self.dictionary.structure_id(
87                &self.raw.message_code().unwrap_or_default(),
88                &self.raw.trigger_event().unwrap_or_default(),
89            ),
90        }
91    }
92
93    /// The delimiters this message declared in MSH-1 and MSH-2.
94    #[must_use]
95    pub fn separators(&self) -> &Separators {
96        &self.raw.separators
97    }
98
99    /// The underlying `er7` message — the escape hatch.
100    ///
101    /// Everything this crate knows is derived from here, and nothing is
102    /// lost on the way in, so a caller who needs a field no mode models
103    /// reads it here rather than parsing the text a second time.
104    #[must_use]
105    pub fn raw(&self) -> &er7::Message {
106        &self.raw
107    }
108
109    /// The underlying `er7` message, mutably. Changes are visible to every
110    /// other method immediately, including [`Message::to_er7`].
111    pub fn raw_mut(&mut self) -> &mut er7::Message {
112        &mut self.raw
113    }
114
115    /// Take the underlying `er7` message, dropping the dictionary.
116    #[must_use]
117    pub fn into_raw(self) -> er7::Message {
118        self.raw
119    }
120
121    /// Every segment, in message order.
122    pub fn segments(&self) -> impl Iterator<Item = &Segment> {
123        self.raw.segments.iter()
124    }
125
126    /// The first segment named `name`.
127    #[must_use]
128    pub fn segment(&self, name: &str) -> Option<&Segment> {
129        self.raw.segment(name)
130    }
131
132    /// The `occurrence`-th (1-based) segment named `name`.
133    #[must_use]
134    pub fn segment_at(&self, name: &str, occurrence: usize) -> Option<&Segment> {
135        self.raw.segment_at(name, occurrence)
136    }
137
138    /// Write the message back as ER7.
139    ///
140    /// For a message parsed and not modified this reproduces the input,
141    /// differing only where the input was not canonical (other segment
142    /// terminators, blank lines, leading whitespace) — that guarantee is
143    /// `er7`'s, and this crate does not weaken it.
144    #[must_use]
145    pub fn to_er7(&self) -> String {
146        self.raw.to_er7()
147    }
148
149    /// Write the message back as ER7, choosing the segment terminator; see
150    /// [`er7::RenderOptions`].
151    #[must_use]
152    pub fn to_er7_with(&self, options: er7::RenderOptions) -> String {
153        self.raw.to_er7_with(options)
154    }
155
156    // ---- generic mode ---------------------------------------------------
157
158    /// The whole message as a navigable tree, with segments grouped into
159    /// the message structure when they fit it and left flat when they do
160    /// not. See [`crate::generic`] for the naming rules.
161    #[must_use]
162    pub fn tree(&self) -> Node {
163        self.tree_with_options(true)
164    }
165
166    /// The message as a tree, with message-structure grouping optionally
167    /// suppressed. A flat tree is one node per segment under the root, and
168    /// is what a caller who navigates by segment name wants.
169    #[must_use]
170    pub fn tree_with_options(&self, grouped: bool) -> Node {
171        let separators = &self.raw.separators;
172        let mut occurrences: Vec<usize> = Vec::with_capacity(self.raw.segments.len());
173        let mut counts: std::collections::BTreeMap<&str, usize> =
174            std::collections::BTreeMap::default();
175        for segment in &self.raw.segments {
176            let count = counts.entry(segment.name.as_str()).or_default();
177            *count += 1;
178            occurrences.push(*count);
179        }
180        let nodes: Vec<Node> = self
181            .raw
182            .segments
183            .iter()
184            .zip(&occurrences)
185            .map(|(segment, occurrence)| {
186                generic::segment(segment, *occurrence, &self.dictionary, separators)
187            })
188            .collect();
189        let structure_id = self.structure_id();
190        let children = match grouped.then(|| self.layout()).flatten() {
191            Some(layout) => build(&layout, &nodes, &structure_id),
192            None => nodes,
193        };
194        generic::root(&structure_id, children)
195    }
196
197    /// How this message's segments fit its structure, or `None` when the
198    /// dictionary has no grammar for it or the segments do not fit.
199    #[must_use]
200    pub fn layout(&self) -> Option<Vec<Layout>> {
201        let items = self.dictionary.structure(&self.structure_id())?;
202        let names: Vec<&str> = self
203            .raw
204            .segments
205            .iter()
206            .map(|segment| segment.name.as_str())
207            .collect();
208        structure::group(items, &names)
209    }
210
211    // ---- reading --------------------------------------------------------
212
213    /// The value at `path`, e.g. `PID-5.1`, `OBX[2]-5`, `MSH-9.3`.
214    ///
215    /// Returns the decoded text of the first match, or `None` when the path
216    /// names nothing in this message. Path syntax is `er7`'s; see
217    /// [`er7::Path`].
218    /// # Errors
219    ///
220    /// [`Error::Path`] when `path` is not a valid HL7 path.
221    pub fn get(&self, path: &str) -> Result<Option<String>, Error> {
222        Ok(self.raw.query(path)?)
223    }
224
225    /// Every value matching `path`. A path that omits an occurrence or a
226    /// repetition matches all of them, so `OBX-5` reads every result in the
227    /// message in one call.
228    /// # Errors
229    ///
230    /// [`Error::Path`] when `path` is not a valid HL7 path.
231    pub fn get_all(&self, path: &str) -> Result<Vec<String>, Error> {
232        Ok(self.raw.query_all(path)?)
233    }
234
235    /// Every repetition of the field at `path`, in message order.
236    ///
237    /// This differs from [`Message::get_all`] at exactly one point: a path
238    /// that names a whole field, such as `PID-3`, is one value to `er7` —
239    /// the field's text, repetition separators and all — because that is
240    /// what the field *is*. Here it is the repetitions, because a caller
241    /// asking for a list of them is asking about `241900~99~7` as three
242    /// identifiers rather than one string. Paths that already name a
243    /// repetition or a component behave as [`Message::get_all`].
244    ///
245    /// ```
246    /// let message = hl7_2::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1||241900~99~7")?;
247    /// assert_eq!(message.repetitions("PID-3")?, ["241900", "99", "7"]);
248    /// assert_eq!(message.get("PID-3")?.as_deref(), Some("241900~99~7"));
249    /// # Ok::<(), hl7_2::Error>(())
250    /// ```
251    /// # Errors
252    ///
253    /// [`Error::Path`] when `path` is not a valid HL7 path.
254    pub fn repetitions(&self, path: &str) -> Result<Vec<String>, Error> {
255        let parsed = Path::parse(path)?;
256        let Some(number) = parsed.field else {
257            return self.get_all(path);
258        };
259        if parsed.repetition.is_some() || parsed.component.is_some() {
260            return self.get_all(path);
261        }
262        let mut values = Vec::new();
263        let mut occurrence = 0;
264        for segment in &self.raw.segments {
265            if segment.name != parsed.segment {
266                continue;
267            }
268            occurrence += 1;
269            if parsed
270                .segment_occurrence
271                .is_some_and(|wanted| wanted != occurrence)
272            {
273                continue;
274            }
275            if let Some(field) = segment.field(number) {
276                for repetition in &field.repetitions {
277                    values.push(repetition.to_text(&self.raw.separators));
278                }
279            }
280        }
281        Ok(values)
282    }
283
284    /// The data type the dictionary gives the field at `path`, resolving
285    /// OBX-5 through OBX-2. `None` when the segment or field is unknown.
286    /// # Errors
287    ///
288    /// [`Error::Path`] when `path` is not a valid HL7 path.
289    pub fn type_of(&self, path: &str) -> Result<Option<String>, Error> {
290        let path = Path::parse(path)?;
291        let Some(field) = path.field else {
292            return Ok(None);
293        };
294        let occurrence = path.segment_occurrence.unwrap_or(1);
295        let Some(segment) = self.raw.segment_at(&path.segment, occurrence) else {
296            return Ok(None);
297        };
298        Ok(match self.dictionary.field_type(&path.segment, field) {
299            Some(crate::dictionary::VARIABLE) => self.dictionary.variable_type(segment),
300            other => other,
301        }
302        .map(str::to_string))
303    }
304
305    // ---- writing --------------------------------------------------------
306
307    /// Set the value at `path`, creating whatever the path names and the
308    /// message does not yet have.
309    ///
310    /// `value` is data, not wire format: delimiters inside it are escaped,
311    /// so setting `SMITH^JOHN` writes one component containing a literal
312    /// caret. Use [`Message::set_er7`] to write text that is already
313    /// encoded, and note that setting a level replaces everything beneath
314    /// it — `set("PID-5", ...)` discards the components PID-5 had.
315    ///
316    /// The segment must already exist; [`Message::append_segment`] and
317    /// [`crate::Builder`] create segments.
318    ///
319    /// ```
320    /// let mut message = hl7_2::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1")?;
321    /// message.set("PID-5.1", "SMITH")?;
322    /// message.set("PID-5.2", "JOHN")?;
323    /// assert_eq!(message.get("PID-5")?.as_deref(), Some("SMITH^JOHN"));
324    /// # Ok::<(), hl7_2::Error>(())
325    /// ```
326    /// # Errors
327    ///
328    /// [`Error::Path`] when `path` is not a valid HL7 path, or names a
329    /// position that cannot be written.
330    pub fn set(&mut self, path: &str, value: &str) -> Result<(), Error> {
331        let separators = self.raw.separators;
332        let encoded = er7::escape::escape(value, &separators).into_owned();
333        self.write(path, &encoded, Create::Yes)
334    }
335
336    /// Set the value at `path` to text that is already ER7-encoded, so its
337    /// delimiters keep their structural meaning: `set_er7("PID-5",
338    /// "SMITH^JOHN")` writes two components.
339    ///
340    /// The text is parsed only down to the levels the path leaves open —
341    /// writing to `PID-5.1.2` treats the text as a single subcomponent
342    /// value, because there is no level left for a delimiter to divide.
343    /// # Errors
344    ///
345    /// [`Error::Path`] when `path` is not a valid HL7 path, or names a
346    /// position that cannot be written.
347    pub fn set_er7(&mut self, path: &str, er7_text: &str) -> Result<(), Error> {
348        self.write(path, er7_text, Create::Yes)
349    }
350
351    /// Set the value at `path` to the HL7 explicit null `""`, which tells
352    /// the receiver to clear the value rather than leave it alone. This is
353    /// not the same as [`Message::clear`].
354    /// # Errors
355    ///
356    /// [`Error::Path`] when `path` is not a valid HL7 path, or names a
357    /// position that cannot be written.
358    pub fn set_null(&mut self, path: &str) -> Result<(), Error> {
359        self.write(path, er7::message::NULL, Create::Yes)
360    }
361
362    /// Empty the value at `path`, as if the sender had never populated it.
363    /// Compare [`Message::set_null`], which says "clear this" out loud.
364    ///
365    /// Clearing what is already absent does nothing and succeeds — it does
366    /// not create the empty field it would then be emptying, and it does
367    /// not fail on a missing segment. That is what makes writing an
368    /// `Option::None` in struct mode a no-op rather than a message full of
369    /// empty components.
370    /// # Errors
371    ///
372    /// [`Error::Path`] when `path` is not a valid HL7 path, or names a
373    /// position that cannot be written.
374    pub fn clear(&mut self, path: &str) -> Result<(), Error> {
375        self.write(path, "", Create::No)
376    }
377
378    /// Write already-encoded text at `path`, growing the message to fit
379    /// when `create` says to and stopping quietly when it does not.
380    fn write(&mut self, path: &str, encoded: &str, create: Create) -> Result<(), Error> {
381        let separators = self.raw.separators;
382        let path = Path::parse(path)?;
383        let Some(number) = path.field else {
384            return Err(Error::UnwritablePath(
385                "a path must name a field to be written".to_string(),
386            ));
387        };
388        let occurrence = path.segment_occurrence.unwrap_or(1);
389        let segment = match self.raw.segment_at_mut(&path.segment, occurrence) {
390            Some(segment) => segment,
391            None if create == Create::No => return Ok(()),
392            None => {
393                return Err(Error::NoSuchSegment {
394                    name: path.segment.clone(),
395                    occurrence,
396                });
397            }
398        };
399        if segment.fields.len() < number {
400            if create == Create::No {
401                return Ok(());
402            }
403            segment.fields.resize_with(number, Default::default);
404        }
405        let field = &mut segment.fields[number - 1];
406        let repetition = path.repetition.unwrap_or(1);
407        if field.repetitions.len() < repetition {
408            if create == Create::No {
409                return Ok(());
410            }
411            field.repetitions.resize_with(repetition, Default::default);
412        }
413        let repetition = &mut field.repetitions[repetition - 1];
414        // Each level below the one the path names is filled by splitting
415        // the text on that level's delimiter, so `set_er7("PID-5",
416        // "SMITH^JOHN")` writes two components while `set` — which escaped
417        // the caret first — writes one.
418        match (path.component, path.subcomponent) {
419            (None, _) => {
420                repetition.components = encoded
421                    .split(separators.component)
422                    .map(|text| component_from(text, &separators))
423                    .collect();
424            }
425            (Some(component), None) => {
426                if !grow(&mut repetition.components, component, create) {
427                    return Ok(());
428                }
429                repetition.components[component - 1] = component_from(encoded, &separators);
430            }
431            (Some(component), Some(subcomponent)) => {
432                if !grow(&mut repetition.components, component, create) {
433                    return Ok(());
434                }
435                let component = &mut repetition.components[component - 1];
436                if !grow(&mut component.subcomponents, subcomponent, create) {
437                    return Ok(());
438                }
439                component.subcomponents[subcomponent - 1] = er7::Subcomponent::new(encoded);
440            }
441        }
442        Ok(())
443    }
444
445    /// Append an empty segment named `name` and return it for populating.
446    ///
447    /// ```
448    /// let mut message = hl7_2::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5")?;
449    /// message.append_segment("PID");
450    /// message.set("PID-3.1", "241900")?;
451    /// assert!(message.to_er7().ends_with("\rPID|||241900"));
452    /// # Ok::<(), hl7_2::Error>(())
453    /// ```
454    pub fn append_segment(&mut self, name: &str) -> &mut Segment {
455        self.insert_segment(self.raw.segments.len(), name)
456    }
457
458    /// Insert an empty segment named `name` at `index` in the segment list,
459    /// clamped to the end. Inserting is how a segment lands in the place
460    /// its structure expects rather than after everything else.
461    pub fn insert_segment(&mut self, index: usize, name: &str) -> &mut Segment {
462        let index = index.min(self.raw.segments.len());
463        self.raw.segments.insert(
464            index,
465            Segment {
466                name: name.to_string(),
467                fields: Vec::new(),
468            },
469        );
470        &mut self.raw.segments[index]
471    }
472
473    /// Remove the `occurrence`-th (1-based) segment named `name`, returning
474    /// it. The MSH header cannot be removed.
475    pub fn remove_segment(&mut self, name: &str, occurrence: usize) -> Option<Segment> {
476        let mut seen = 0;
477        let index = self.raw.segments.iter().position(|segment| {
478            if segment.name == name {
479                seen += 1;
480            }
481            seen == occurrence && segment.name == name
482        })?;
483        if index == 0 {
484            return None;
485        }
486        Some(self.raw.segments.remove(index))
487    }
488
489    /// Remove every segment named `name`, returning how many went. The MSH
490    /// header is never removed.
491    pub fn remove_segments(&mut self, name: &str) -> usize {
492        let before = self.raw.segments.len();
493        let mut index = 0;
494        self.raw.segments.retain(|segment| {
495            index += 1;
496            index == 1 || segment.name != name
497        });
498        before - self.raw.segments.len()
499    }
500
501    // ---- validation and struct mode -------------------------------------
502
503    /// Check this message against its dictionary; see [`crate::validate`].
504    ///
505    /// Never fails and never changes the message: it reports. Parsing with
506    /// [`Options::strict`] runs the same check and turns any
507    /// [`Severity::Error`] into a parse failure.
508    #[must_use]
509    pub fn validate(&self) -> Vec<Diagnostic> {
510        crate::validate::validate(self)
511    }
512
513    /// Decode into a type that implements [`crate::FromHl7`] — struct mode.
514    ///
515    /// ```
516    /// # #[cfg(feature = "derive")] fn main() -> Result<(), hl7_2::Error> {
517    /// use hl7_2::FromHl7;
518    ///
519    /// #[derive(FromHl7)]
520    /// struct Patient {
521    ///     #[hl7("PID-3.1")]
522    ///     id: String,
523    ///     #[hl7("PID-5.1.1")]
524    ///     family_name: String,
525    /// }
526    ///
527    /// let message = hl7_2::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1||241900||SMITH^JOHN")?;
528    /// let patient: Patient = message.decode()?;
529    /// assert_eq!(patient.id, "241900");
530    /// assert_eq!(patient.family_name, "SMITH");
531    /// # Ok(())
532    /// # }
533    /// # #[cfg(not(feature = "derive"))] fn main() {}
534    /// ```
535    /// # Errors
536    ///
537    /// Whatever the type's [`FromHl7`](crate::FromHl7) implementation
538    /// reports: a path it could not read, or a value it could not convert.
539    pub fn decode<T: crate::FromHl7>(&self) -> Result<T, Error> {
540        T::from_hl7(self)
541    }
542}
543
544impl std::fmt::Display for Message {
545    /// The message as ER7; see [`Message::to_er7`].
546    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547        f.write_str(&self.to_er7())
548    }
549}
550
551/// Whether a write may bring into being what it is writing to.
552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
553enum Create {
554    /// Grow the message so the path exists.
555    Yes,
556    /// Leave the message alone if the path does not already exist.
557    No,
558}
559
560/// Grow `list` so that `position` (1-based) exists, reporting whether the
561/// caller may go on to write there.
562fn grow<T: Default>(list: &mut Vec<T>, position: usize, create: Create) -> bool {
563    if list.len() < position {
564        if create == Create::No {
565            return false;
566        }
567        list.resize_with(position, Default::default);
568    }
569    true
570}
571
572/// Split already-encoded text into one component's subcomponents.
573fn component_from(text: &str, separators: &Separators) -> er7::Component {
574    er7::Component {
575        subcomponents: text
576            .split(separators.subcomponent)
577            .map(er7::Subcomponent::new)
578            .collect(),
579    }
580}
581
582/// Turn a matched layout into tree nodes, cloning each segment's node into
583/// the group it landed in.
584fn build(layout: &[Layout], nodes: &[Node], root_name: &str) -> Vec<Node> {
585    layout
586        .iter()
587        .map(|item| match item {
588            Layout::Segment(index) => nodes[*index].clone(),
589            Layout::Group { name, items } => {
590                generic::group(root_name, name, build(items, nodes, root_name))
591            }
592        })
593        .collect()
594}
595
596#[cfg(test)]
597mod tests {
598    use super::*;
599
600    const HEADER: &str = "MSH|^~\\&|hphis||EPIC||20131011093851||ADT^A01|14AAACVDD|P|2.5";
601
602    fn message() -> Message {
603        crate::parse(&format!("{HEADER}\rPID|1||241900||TEST^FOUAZ\rNK1|1")).unwrap()
604    }
605
606    #[test]
607    fn reads_version_and_structure_off_the_header() {
608        let message = message();
609        assert_eq!(message.version(), Version::V2_5);
610        assert_eq!(message.structure_id(), "ADT_A01");
611        // A trigger event that shares a structure resolves through the
612        // dictionary's aliases.
613        let other =
614            crate::parse("MSH|^~\\&|A||||1||ADT^A08|1|P|2.5\rEVN|A08\rPID|1\rPV1|1").unwrap();
615        assert_eq!(other.structure_id(), "ADT_A01");
616        // MSH-9.3 wins when the sender supplies one.
617        let explicit = crate::parse("MSH|^~\\&|A||||1||ADT^A08^ADT_A08|1|P|2.5").unwrap();
618        assert_eq!(explicit.structure_id(), "ADT_A08");
619    }
620
621    #[test]
622    fn defaults_the_version_when_msh_12_is_missing_or_odd() {
623        let message = crate::parse("MSH|^~\\&|A||||1||ACK|1|P|\rMSA|AA|1").unwrap();
624        assert_eq!(message.version(), Version::V2_5);
625        let message = crate::parse("MSH|^~\\&|A||||1||ACK|1|P|2.3.1\rMSA|AA|1").unwrap();
626        assert_eq!(message.version(), Version::V2_3_1);
627        // An unmodelled point release reads as the nearest older one.
628        let message = crate::parse("MSH|^~\\&|A||||1||ACK|1|P|2.5.2\rMSA|AA|1").unwrap();
629        assert_eq!(message.version(), Version::V2_5_1);
630    }
631
632    #[test]
633    fn reads_values_by_path() {
634        let message = message();
635        assert_eq!(message.get("PID-5.1").unwrap().as_deref(), Some("TEST"));
636        assert_eq!(message.get("PID-5").unwrap().as_deref(), Some("TEST^FOUAZ"));
637        assert_eq!(message.get("PID-99").unwrap(), None);
638        assert_eq!(message.get("ZZZ-1").unwrap(), None);
639        assert!(matches!(message.get("PID-0"), Err(Error::Path(_))));
640        assert_eq!(message.type_of("PID-5").unwrap().as_deref(), Some("XPN"));
641        assert_eq!(message.type_of("ZZZ-1").unwrap(), None);
642    }
643
644    #[test]
645    fn writes_values_creating_what_is_missing() {
646        let mut message = message();
647        message.set("PID-8", "F").unwrap();
648        assert_eq!(message.get("PID-8").unwrap().as_deref(), Some("F"));
649        // Beyond the current end of the segment.
650        message.set("PID-11.3", "SEATTLE").unwrap();
651        assert_eq!(message.get("PID-11.3").unwrap().as_deref(), Some("SEATTLE"));
652        // Into a repetition that does not exist yet.
653        message.set("PID[1]-3[2].1", "OTHER").unwrap();
654        assert_eq!(
655            message.get_all("PID-3.1").unwrap(),
656            ["241900".to_string(), "OTHER".to_string()]
657        );
658        // A missing segment is an error, not a silent no-op.
659        assert!(matches!(
660            message.set("OBX-5", "x"),
661            Err(Error::NoSuchSegment { .. })
662        ));
663        assert!(matches!(
664            message.set("PID", "x"),
665            Err(Error::UnwritablePath(_))
666        ));
667    }
668
669    #[test]
670    fn set_escapes_and_set_er7_does_not() {
671        let mut message = message();
672        message.set("PID-5.1", "SMITH^JOHN").unwrap();
673        // One component holding a literal caret ...
674        assert!(message.to_er7().contains("\\S\\"), "{}", message.to_er7());
675        assert_eq!(
676            message.get("PID-5.1").unwrap().as_deref(),
677            Some("SMITH^JOHN")
678        );
679        // ... versus two components.
680        let mut message = message2();
681        message.set_er7("PID-5", "SMITH^JOHN").unwrap();
682        assert_eq!(message.get("PID-5.2").unwrap().as_deref(), Some("JOHN"));
683    }
684
685    fn message2() -> Message {
686        crate::parse(&format!("{HEADER}\rPID|1")).unwrap()
687    }
688
689    #[test]
690    fn distinguishes_clearing_from_nulling() {
691        let mut message = message();
692        message.set_null("PID-5").unwrap();
693        assert!(message.to_er7().contains("|\"\""));
694        message.clear("PID-5").unwrap();
695        assert!(!message.to_er7().contains("\"\""));
696        // Clearing what was never there leaves no trace of having tried:
697        // no empty components, no error about the missing segment.
698        let before = message.to_er7();
699        message.clear("PID-5.3").unwrap();
700        message.clear("PID-40.2").unwrap();
701        message.clear("OBX-5").unwrap();
702        assert_eq!(message.to_er7(), before);
703    }
704
705    #[test]
706    fn adds_and_removes_segments() {
707        let mut message = message();
708        message.append_segment("OBX");
709        message.set("OBX-3.1", "GLU").unwrap();
710        assert!(message.to_er7().ends_with("OBX|||GLU"));
711        message.insert_segment(1, "EVN");
712        assert_eq!(message.raw().segments[1].name, "EVN");
713        assert_eq!(message.remove_segments("NK1"), 1);
714        assert!(message.segment("NK1").is_none());
715        // The header stays put whatever is asked.
716        assert_eq!(message.remove_segments("MSH"), 0);
717        assert!(message.remove_segment("MSH", 1).is_none());
718    }
719
720    #[test]
721    fn round_trips_an_unmodified_message() {
722        let text = format!("{HEADER}\rPID|1||241900||TEST^FOUAZ\rNK1|1");
723        assert_eq!(crate::parse(&text).unwrap().to_er7(), text);
724    }
725
726    #[test]
727    fn falls_back_to_a_flat_tree_when_the_structure_does_not_fit() {
728        let message = message(); // ADT_A01 requires EVN and PV1
729        assert!(message.layout().is_none());
730        let tree = message.tree();
731        assert_eq!(tree.name(), "ADT_A01");
732        assert!(tree.child("PID").is_some(), "segments must stay reachable");
733    }
734}