Skip to main content

hl7kit/
builder.rs

1//! Building messages.
2//!
3//! The builder is structural: you set fields, components and subcomponents
4//! as plain text and the builder inserts the delimiters and escapes any
5//! delimiter characters that appear inside values. It never lets you write a
6//! delimiter by accident, and the output always parses back with
7//! [`Message::parse`](crate::Message) to the same values.
8//!
9//! ```
10//! use hl7kit::builder::{Builder, Value};
11//! use hl7kit::Message;
12//!
13//! let mut b = Builder::new();
14//! b.segment("MSH")
15//!     .set(3, "RIS")
16//!     .set(4, "HOSP")
17//!     .set(9, Value::components(["ORM", "O01", "ORM_O01"]))
18//!     .set(10, "MSG0001")
19//!     .set(11, "P")
20//!     .set(12, "2.5.1");
21//! b.segment("PID")
22//!     .set(1, "1")
23//!     .set(3, Value::components(["4MR1", "", "", "HOSP", "MR"]))
24//!     .set(5, Value::components(["Doe", "Jane & John"]));
25//! b.segment("ZDS")
26//!     .set(1, Value::components(["1.2.3", "", "Application", "DICOM"]));
27//!
28//! let text = b.build();
29//! assert!(text.starts_with("MSH|^~\\&|RIS|HOSP|||||ORM^O01^ORM_O01|MSG0001|P|2.5.1\r"));
30//! let msg = Message::parse(&text).unwrap();
31//! assert_eq!(msg.get("PID-3.4"), Some("HOSP"));
32//! assert_eq!(msg.get_decoded("PID-5.2").unwrap(), "Jane & John");   // `&` was escaped
33//! assert_eq!(msg.get("ZDS-1.1"), Some("1.2.3"));
34//! ```
35
36use crate::Encoding;
37use std::borrow::Cow;
38
39/// Escape the delimiter characters of `enc` in `text` so it can be placed in
40/// a field as a literal. Line breaks become `\.br\`.
41///
42/// This is the inverse of [`unescape`](crate::unescape) for the delimiter
43/// sequences.
44pub fn escape<'a>(text: &'a str, enc: Encoding) -> Cow<'a, str> {
45    let special = [
46        enc.field,
47        enc.component,
48        enc.repetition,
49        enc.escape,
50        enc.subcomponent,
51        b'\r',
52        b'\n',
53    ];
54    if !text.bytes().any(|b| special.contains(&b)) {
55        return Cow::Borrowed(text);
56    }
57    let esc = enc.escape as char;
58    let mut out = String::with_capacity(text.len() + 8);
59    let mut chars = text.chars().peekable();
60    while let Some(c) = chars.next() {
61        let code = if c.is_ascii() { Some(c as u8) } else { None };
62        match code {
63            Some(b) if b == enc.escape => push_seq(&mut out, esc, "E"),
64            Some(b) if b == enc.field => push_seq(&mut out, esc, "F"),
65            Some(b) if b == enc.component => push_seq(&mut out, esc, "S"),
66            Some(b) if b == enc.repetition => push_seq(&mut out, esc, "R"),
67            Some(b) if b == enc.subcomponent => push_seq(&mut out, esc, "T"),
68            Some(b'\r') => {
69                if chars.peek() == Some(&'\n') {
70                    chars.next();
71                }
72                push_seq(&mut out, esc, ".br");
73            }
74            Some(b'\n') => push_seq(&mut out, esc, ".br"),
75            _ => out.push(c),
76        }
77    }
78    Cow::Owned(out)
79}
80
81fn push_seq(out: &mut String, esc: char, seq: &str) {
82    out.push(esc);
83    out.push_str(seq);
84    out.push(esc);
85}
86
87/// The value of one field: repetitions of components of subcomponents.
88///
89/// Build one with [`Value::text`], [`Value::components`] or
90/// [`Value::repetitions`], or from a `&str`/`String` (a single text).
91#[derive(Debug, Clone, Default, PartialEq, Eq)]
92pub struct Value {
93    /// `repetitions[r][c][s]` is subcomponent `s+1` of component `c+1` of
94    /// repetition `r+1`.
95    pub repetitions: Vec<Vec<Vec<String>>>,
96}
97
98impl Value {
99    /// A field holding one plain text.
100    pub fn text(text: impl Into<String>) -> Value {
101        Value {
102            repetitions: vec![vec![vec![text.into()]]],
103        }
104    }
105
106    /// One repetition with the given components, each a plain text.
107    pub fn components<I, S>(components: I) -> Value
108    where
109        I: IntoIterator<Item = S>,
110        S: Into<String>,
111    {
112        Value {
113            repetitions: vec![components.into_iter().map(|c| vec![c.into()]).collect()],
114        }
115    }
116
117    /// Several repetitions, each given as its components.
118    pub fn repetitions<I, R, S>(reps: I) -> Value
119    where
120        I: IntoIterator<Item = R>,
121        R: IntoIterator<Item = S>,
122        S: Into<String>,
123    {
124        Value {
125            repetitions: reps
126                .into_iter()
127                .map(|r| r.into_iter().map(|c| vec![c.into()]).collect())
128                .collect(),
129        }
130    }
131
132    /// Set subcomponent `s` of component `c` of repetition `r` (all
133    /// one-based), growing the value as needed.
134    pub fn set(&mut self, r: usize, c: usize, s: usize, text: impl Into<String>) -> &mut Value {
135        let (r, c, s) = (r.max(1) - 1, c.max(1) - 1, s.max(1) - 1);
136        if self.repetitions.len() <= r {
137            self.repetitions.resize(r + 1, Vec::new());
138        }
139        let rep = &mut self.repetitions[r];
140        if rep.len() <= c {
141            rep.resize(c + 1, Vec::new());
142        }
143        let comp = &mut rep[c];
144        if comp.len() <= s {
145            comp.resize(s + 1, String::new());
146        }
147        comp[s] = text.into();
148        self
149    }
150
151    fn render(&self, enc: Encoding) -> String {
152        let sub = (enc.subcomponent as char).to_string();
153        let comp = (enc.component as char).to_string();
154        let rep = (enc.repetition as char).to_string();
155        self.repetitions
156            .iter()
157            .map(|r| {
158                r.iter()
159                    .map(|c| {
160                        c.iter()
161                            .map(|s| escape(s, enc))
162                            .collect::<Vec<_>>()
163                            .join(&sub)
164                    })
165                    .collect::<Vec<_>>()
166                    .join(&comp)
167            })
168            .collect::<Vec<_>>()
169            .join(&rep)
170    }
171}
172
173impl From<&str> for Value {
174    fn from(s: &str) -> Value {
175        Value::text(s)
176    }
177}
178
179impl From<String> for Value {
180    fn from(s: String) -> Value {
181        Value::text(s)
182    }
183}
184
185impl From<&String> for Value {
186    fn from(s: &String) -> Value {
187        Value::text(s.clone())
188    }
189}
190
191/// One segment under construction.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct SegmentBuilder {
194    name: String,
195    /// `fields[n - 1]` is field `n`; `None` renders empty.
196    fields: Vec<Option<Value>>,
197}
198
199impl SegmentBuilder {
200    fn new(name: &str) -> SegmentBuilder {
201        SegmentBuilder {
202            name: name.to_ascii_uppercase(),
203            fields: Vec::new(),
204        }
205    }
206
207    /// Segment name.
208    pub fn name(&self) -> &str {
209        &self.name
210    }
211
212    /// Set field `n` (one-based). For `MSH`, fields 1 and 2 are the
213    /// delimiters and cannot be set; they are written from the encoding.
214    pub fn set(&mut self, n: usize, value: impl Into<Value>) -> &mut SegmentBuilder {
215        if n == 0 || (self.name == "MSH" && n <= 2) {
216            return self;
217        }
218        if self.fields.len() < n {
219            self.fields.resize(n, None);
220        }
221        self.fields[n - 1] = Some(value.into());
222        self
223    }
224
225    /// Set component `c` of field `n` (first repetition), keeping the other
226    /// components.
227    pub fn set_component(
228        &mut self,
229        n: usize,
230        c: usize,
231        text: impl Into<String>,
232    ) -> &mut SegmentBuilder {
233        self.set_subcomponent(n, c, 1, text)
234    }
235
236    /// Set subcomponent `s` of component `c` of field `n` (first repetition).
237    pub fn set_subcomponent(
238        &mut self,
239        n: usize,
240        c: usize,
241        s: usize,
242        text: impl Into<String>,
243    ) -> &mut SegmentBuilder {
244        if n == 0 || (self.name == "MSH" && n <= 2) {
245            return self;
246        }
247        if self.fields.len() < n {
248            self.fields.resize(n, None);
249        }
250        self.fields[n - 1]
251            .get_or_insert_with(Value::default)
252            .set(1, c, s, text);
253        self
254    }
255
256    /// Append a repetition to field `n`.
257    pub fn add_repetition<I, S>(&mut self, n: usize, components: I) -> &mut SegmentBuilder
258    where
259        I: IntoIterator<Item = S>,
260        S: Into<String>,
261    {
262        if n == 0 || (self.name == "MSH" && n <= 2) {
263            return self;
264        }
265        if self.fields.len() < n {
266            self.fields.resize(n, None);
267        }
268        let value = self.fields[n - 1].get_or_insert_with(Value::default);
269        if value.repetitions.len() == 1 && value.repetitions[0].is_empty() {
270            value.repetitions.clear();
271        }
272        value
273            .repetitions
274            .push(components.into_iter().map(|c| vec![c.into()]).collect());
275        self
276    }
277
278    fn render(&self, enc: Encoding) -> String {
279        let fs = enc.field as char;
280        let mut out = self.name.clone();
281        let mut fields: Vec<String> = self
282            .fields
283            .iter()
284            .map(|f| f.as_ref().map(|v| v.render(enc)).unwrap_or_default())
285            .collect();
286        if self.name == "MSH" {
287            // Fields 1 and 2 are the delimiters themselves.
288            let mut msh = vec![enc.msh2()];
289            msh.extend(fields.drain(..).skip(2));
290            fields = msh;
291        }
292        for f in fields {
293            out.push(fs);
294            out.push_str(&f);
295        }
296        out
297    }
298}
299
300/// Builds a message segment by segment.
301#[derive(Debug, Clone, PartialEq, Eq)]
302pub struct Builder {
303    encoding: Encoding,
304    segments: Vec<SegmentBuilder>,
305}
306
307impl Default for Builder {
308    fn default() -> Self {
309        Builder::new()
310    }
311}
312
313impl Builder {
314    /// A builder using the standard delimiters `|^~\&`.
315    pub fn new() -> Builder {
316        Builder::with_encoding(Encoding::STANDARD)
317    }
318
319    /// A builder using custom delimiters.
320    pub fn with_encoding(encoding: Encoding) -> Builder {
321        Builder {
322            encoding,
323            segments: Vec::new(),
324        }
325    }
326
327    /// The encoding in use.
328    pub fn encoding(&self) -> Encoding {
329        self.encoding
330    }
331
332    /// Append a segment and return it for filling in. The first segment
333    /// should be `MSH`; [`Builder::build`] inserts one if it is missing.
334    pub fn segment(&mut self, name: &str) -> &mut SegmentBuilder {
335        self.segments.push(SegmentBuilder::new(name));
336        let last = self.segments.len() - 1;
337        &mut self.segments[last]
338    }
339
340    /// The segments so far.
341    pub fn segments(&self) -> &[SegmentBuilder] {
342        &self.segments
343    }
344
345    /// Mutable access to segment `index` (zero-based), e.g. to fill MSH later.
346    pub fn segment_at(&mut self, index: usize) -> Option<&mut SegmentBuilder> {
347        self.segments.get_mut(index)
348    }
349
350    /// Render the message with `\r` segment terminators, as the standard
351    /// requires. Every segment, including the last, ends with `\r`.
352    pub fn build(&self) -> String {
353        let mut out = String::new();
354        if self.segments.first().map(|s| s.name.as_str()) != Some("MSH") {
355            out.push_str(&SegmentBuilder::new("MSH").render(self.encoding));
356            out.push('\r');
357        }
358        for seg in &self.segments {
359            out.push_str(&seg.render(self.encoding));
360            out.push('\r');
361        }
362        out
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use crate::{unescape, Message};
370
371    #[test]
372    fn escape_roundtrips_through_unescape() {
373        let enc = Encoding::STANDARD;
374        let text = "a|b^c~d\\e&f\r\ng\nh";
375        let escaped = escape(text, enc);
376        assert_eq!(escaped, "a\\F\\b\\S\\c\\R\\d\\E\\e\\T\\f\\.br\\g\\.br\\h");
377        assert_eq!(unescape(&escaped, enc), "a|b^c~d\\e&f\ng\nh");
378        assert!(matches!(escape("plain", enc), Cow::Borrowed(_)));
379    }
380
381    #[test]
382    fn msh_is_written_from_the_encoding() {
383        let mut b = Builder::new();
384        b.segment("MSH")
385            .set(1, "ignored")
386            .set(2, "ignored")
387            .set(3, "APP");
388        assert_eq!(b.build(), "MSH|^~\\&|APP\r");
389        let mut c = Builder::with_encoding(Encoding::from_msh2(b'#', b"!*?%").unwrap());
390        c.segment("MSH").set(9, Value::components(["ADT", "A01"]));
391        c.segment("PID").set(3, "A#B");
392        assert_eq!(c.build(), "MSH#!*?%#######ADT!A01\rPID###A?F?B\r");
393        let msg = Message::parse(c.build()).unwrap();
394        assert_eq!(msg.get_decoded("PID-3").unwrap(), "A#B");
395    }
396
397    #[test]
398    fn missing_msh_is_inserted() {
399        let mut b = Builder::new();
400        b.segment("PID").set(1, "1");
401        assert_eq!(b.build(), "MSH|^~\\&\rPID|1\r");
402    }
403
404    #[test]
405    fn structure_roundtrips_through_parser() {
406        let mut b = Builder::new();
407        b.segment("MSH")
408            .set(9, Value::components(["ORU", "R01"]))
409            .set(10, "1");
410        let pid = b.segment("PID");
411        pid.set(
412            3,
413            Value::repetitions([["A", "", "", "H1"], ["B", "", "", "H2"]]),
414        );
415        pid.set_subcomponent(3, 4, 2, "sub");
416        pid.set_component(5, 2, "First");
417        pid.add_repetition(3, ["C"]);
418        b.segment("OBX").set(5, "value with | and ^ inside");
419        let text = b.build();
420        assert_eq!(
421            text,
422            "MSH|^~\\&|||||||ORU^R01|1\rPID|||A^^^H1&sub~B^^^H2~C||^First\rOBX|||||value with \\F\\ and \\S\\ inside\r"
423        );
424        let msg = Message::parse(&text).unwrap();
425        assert_eq!(msg.get("PID-3[2].4"), Some("H2"));
426        assert_eq!(msg.get("PID-3[3].1"), Some("C"));
427        assert_eq!(msg.get("PID-3.4.2"), Some("sub"));
428        assert_eq!(msg.get("PID-5.2"), Some("First"));
429        assert_eq!(
430            msg.get_decoded("OBX-5").unwrap(),
431            "value with | and ^ inside"
432        );
433        assert!(msg.warnings().is_empty());
434    }
435
436    #[test]
437    fn value_set_grows() {
438        let mut v = Value::default();
439        v.set(2, 3, 1, "x");
440        assert_eq!(v.render(Encoding::STANDARD), "~^^x");
441        assert_eq!(Value::from("t").render(Encoding::STANDARD), "t");
442    }
443}