hl7_2/builder.rs
1//! Building a message from nothing.
2//!
3//! Reading is the common case, but a system that reads HL7 usually has to
4//! answer in it too — an `ACK` at minimum. A [`Builder`] starts from a
5//! well-formed MSH header for a chosen release and adds segments and
6//! values from there.
7//!
8//! Errors are collected rather than returned at each step, so a chain of
9//! calls stays a chain and [`Builder::build`] reports what went wrong. The
10//! builder takes no timestamp from the clock and invents no control ID:
11//! both are the caller's, because a message that made up its own would be
12//! untraceable and untestable.
13
14use crate::{Error, Message, Options, Version};
15use std::sync::Arc;
16
17/// Assemble a message segment by segment; see the module documentation.
18///
19/// ```
20/// use hl7_2::{Builder, Version};
21///
22/// let message = Builder::new(Version::V2_5)
23/// .message_type("ADT", "A01")
24/// .control_id("MSG00001")
25/// .timestamp("20240101093851")
26/// .sending("HIS", "HOSPITAL")
27/// .receiving("EPIC", "CLINIC")
28/// .segment("EVN")
29/// .set("EVN-1", "A01")
30/// .segment("PID")
31/// .set("PID-3.1", "241900")
32/// .set("PID-5.1.1", "SMITH")
33/// .set("PID-5.2", "JOHN")
34/// .build()?;
35///
36/// assert_eq!(message.structure_id(), "ADT_A01");
37/// assert!(message.to_er7().contains("PID|||241900||SMITH^JOHN"));
38/// # Ok::<(), hl7_2::Error>(())
39/// ```
40#[derive(Debug)]
41pub struct Builder {
42 message: Message,
43 failures: Vec<Error>,
44}
45
46impl Builder {
47 /// Start a message for `version`, with the standard delimiters
48 /// (`|^~\&`), a processing ID of `P` (production), and MSH-12 set.
49 ///
50 /// Everything else — message type, control ID, timestamp, sender,
51 /// receiver — is empty until set, and MSH-9 and MSH-10 being empty is
52 /// exactly what [`Message::validate`] reports, so a half-built message
53 /// says so rather than looking finished.
54 #[must_use]
55 /// # Panics
56 ///
57 /// Never in practice: the bundled dictionaries are embedded at compile
58 /// time and parsed on first use, so a failure here would mean this
59 /// crate shipped a malformed one.
60 pub fn new(version: Version) -> Builder {
61 let header = format!("MSH|^~\\&|||||||||P|{version}");
62 let message = crate::parse_with_options(&header, &Options::new().with_version(version))
63 .expect("a builder's own header is always well formed");
64 Builder {
65 message,
66 failures: Vec::new(),
67 }
68 }
69
70 /// Start a message for `version`, read through `dictionary` — the
71 /// schema-mode counterpart of [`Builder::new`].
72 #[must_use]
73 /// # Panics
74 ///
75 /// Never in practice: the bundled dictionaries are embedded at compile
76 /// time and parsed on first use, so a failure here would mean this
77 /// crate shipped a malformed one.
78 pub fn with_dictionary(version: Version, dictionary: Arc<crate::Dictionary>) -> Builder {
79 let header = format!("MSH|^~\\&|||||||||P|{version}");
80 let options = Options::new()
81 .with_version(version)
82 .with_dictionary(dictionary);
83 let message = crate::parse_with_options(&header, &options)
84 .expect("a builder's own header is always well formed");
85 Builder {
86 message,
87 failures: Vec::new(),
88 }
89 }
90
91 /// Start from an existing message, to add to it or answer it.
92 #[must_use]
93 pub fn from_message(message: Message) -> Builder {
94 Builder {
95 message,
96 failures: Vec::new(),
97 }
98 }
99
100 /// Set MSH-9: the message code and trigger event, e.g. `("ADT",
101 /// "A01")`. The structure ID (MSH-9.3) is filled in from the
102 /// dictionary, so `ADT^A04` correctly declares `ADT_A01`.
103 #[must_use]
104 pub fn message_type(mut self, code: &str, trigger: &str) -> Builder {
105 let structure = self.message.dictionary().structure_id(code, trigger);
106 self = self.set("MSH-9.1", code);
107 self = self.set("MSH-9.2", trigger);
108 self.set("MSH-9.3", &structure)
109 }
110
111 /// Set MSH-10, the message control ID: the sender's own identifier for
112 /// this message, which the receiver echoes in its acknowledgement.
113 #[must_use]
114 pub fn control_id(self, id: &str) -> Builder {
115 self.set("MSH-10", id)
116 }
117
118 /// Set MSH-7, the date and time the message was sent, as HL7 writes it
119 /// (`YYYYMMDDHHMMSS`, optionally with a fraction and an offset).
120 #[must_use]
121 pub fn timestamp(self, timestamp: &str) -> Builder {
122 self.set("MSH-7.1", timestamp)
123 }
124
125 /// Set MSH-3 and MSH-4, the sending application and facility.
126 #[must_use]
127 pub fn sending(self, application: &str, facility: &str) -> Builder {
128 self.set("MSH-3.1", application).set("MSH-4.1", facility)
129 }
130
131 /// Set MSH-5 and MSH-6, the receiving application and facility.
132 #[must_use]
133 pub fn receiving(self, application: &str, facility: &str) -> Builder {
134 self.set("MSH-5.1", application).set("MSH-6.1", facility)
135 }
136
137 /// Set MSH-11, the processing ID: `P` production, `T` training, `D`
138 /// debugging. A builder starts at `P`.
139 #[must_use]
140 pub fn processing_id(self, id: &str) -> Builder {
141 self.set("MSH-11.1", id)
142 }
143
144 /// Append an empty segment. Subsequent [`Builder::set`] calls that name
145 /// this segment address this occurrence, because paths without an
146 /// explicit `[n]` mean the first — so add a segment, fill it, then add
147 /// the next.
148 #[must_use]
149 pub fn segment(mut self, name: &str) -> Builder {
150 self.message.append_segment(name);
151 self
152 }
153
154 /// Set a value, escaping delimiters in it; see [`Message::set`].
155 #[must_use]
156 pub fn set(mut self, path: &str, value: &str) -> Builder {
157 if let Err(error) = self.message.set(path, value) {
158 self.failures.push(error);
159 }
160 self
161 }
162
163 /// Set a value from text that is already ER7-encoded; see
164 /// [`Message::set_er7`].
165 #[must_use]
166 pub fn set_er7(mut self, path: &str, er7_text: &str) -> Builder {
167 if let Err(error) = self.message.set_er7(path, er7_text) {
168 self.failures.push(error);
169 }
170 self
171 }
172
173 /// Write a [`crate::ToHl7`] value's fields into the message being
174 /// built — struct mode's other direction.
175 #[must_use]
176 pub fn encode(mut self, value: &impl crate::ToHl7) -> Builder {
177 if let Err(error) = value.to_hl7(&mut self.message) {
178 self.failures.push(error);
179 }
180 self
181 }
182
183 /// Finish, returning the message, or the first error a step hit.
184 /// # Errors
185 ///
186 /// The first [`Error`] recorded while building — a path that could not
187 /// be written, reported here rather than at the call that caused it, so
188 /// a chain of setters reads as one expression.
189 pub fn build(self) -> Result<Message, Error> {
190 match self.failures.into_iter().next() {
191 Some(error) => Err(error),
192 None => Ok(self.message),
193 }
194 }
195
196 /// Finish, and reject a message that does not pass validation. Use it
197 /// for an outbound message, where sending something malformed costs
198 /// more than noticing here.
199 /// # Errors
200 ///
201 /// As [`Builder::build`], and additionally when the built message does
202 /// not validate against its dictionary.
203 pub fn build_valid(self) -> Result<Message, Error> {
204 let message = self.build()?;
205 let failures: Vec<crate::Diagnostic> = message
206 .validate()
207 .into_iter()
208 .filter(|diagnostic| diagnostic.severity == crate::Severity::Error)
209 .collect();
210 if failures.is_empty() {
211 Ok(message)
212 } else {
213 Err(Error::Invalid(failures))
214 }
215 }
216}
217
218/// Build an `ACK` acknowledging `message`, in the release `message` speaks.
219///
220/// MSA-2 echoes the control ID being acknowledged, which is what lets the
221/// sender match the answer to the question; the acknowledgement's own
222/// control ID and timestamp are the caller's to supply.
223///
224/// ```
225/// let message = hl7_2::parse("MSH|^~\\&|LAB|L|EPIC|E|20240101||ORU^R01|99|P|2.5\rPID|1")?;
226/// let ack = hl7_2::builder::acknowledge(&message, "AA", "ACK00001", "20240101093900").build()?;
227/// assert_eq!(ack.get("MSA-2")?.as_deref(), Some("99"));
228/// // The answer goes back where it came from.
229/// assert_eq!(ack.get("MSH-5.1")?.as_deref(), Some("LAB"));
230/// # Ok::<(), hl7_2::Error>(())
231/// ```
232#[must_use]
233pub fn acknowledge(message: &Message, code: &str, control_id: &str, timestamp: &str) -> Builder {
234 let value = |path: &str| message.get(path).ok().flatten().unwrap_or_default();
235 Builder::new(message.version())
236 .message_type("ACK", &value("MSH-9.2"))
237 .control_id(control_id)
238 .timestamp(timestamp)
239 // Sender and receiver swap: the acknowledgement goes back.
240 .sending(&value("MSH-5.1"), &value("MSH-6.1"))
241 .receiving(&value("MSH-3.1"), &value("MSH-4.1"))
242 .processing_id(&value("MSH-11.1"))
243 .segment("MSA")
244 .set("MSA-1", code)
245 .set("MSA-2", &value("MSH-10"))
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn builds_a_message_from_nothing() {
254 let message = Builder::new(Version::V2_5)
255 .message_type("ADT", "A01")
256 .control_id("1")
257 .timestamp("20240101093851")
258 .segment("EVN")
259 .set("EVN-1", "A01")
260 .segment("PID")
261 .set("PID-3.1", "241900")
262 .segment("PV1")
263 .set("PV1-2", "I")
264 .build()
265 .unwrap();
266 assert_eq!(message.version(), Version::V2_5);
267 assert_eq!(message.structure_id(), "ADT_A01");
268 assert_eq!(message.get("PID-3.1").unwrap().as_deref(), Some("241900"));
269 // Built messages are valid messages.
270 assert_eq!(message.validate(), []);
271 // And they parse back to themselves.
272 let text = message.to_er7();
273 assert_eq!(crate::parse(&text).unwrap().to_er7(), text);
274 }
275
276 #[test]
277 fn resolves_the_structure_id_through_the_dictionary() {
278 let message = Builder::new(Version::V2_5)
279 .message_type("ADT", "A08")
280 .build()
281 .unwrap();
282 assert_eq!(message.get("MSH-9.3").unwrap().as_deref(), Some("ADT_A01"));
283 }
284
285 #[test]
286 fn reports_the_first_failure_at_build_time() {
287 let error = Builder::new(Version::V2_5)
288 .set("PID-3.1", "241900") // no PID segment yet
289 .build()
290 .unwrap_err();
291 assert!(matches!(error, Error::NoSuchSegment { .. }), "{error}");
292 }
293
294 #[test]
295 fn build_valid_rejects_an_incomplete_message() {
296 // No message type and no control ID: not something to send.
297 let error = Builder::new(Version::V2_5).build_valid().unwrap_err();
298 match error {
299 Error::Invalid(diagnostics) => assert_eq!(diagnostics.len(), 2, "{diagnostics:?}"),
300 other => panic!("expected a validation failure, got {other}"),
301 }
302 }
303
304 #[test]
305 fn acknowledges_a_message() {
306 let message = crate::parse(
307 "MSH|^~\\&|LAB|LAB1|EPIC|CLINIC|20240101||ORU^R01|99|P|2.5\rPID|1\rOBR|1\rOBX|1|NM|X||7",
308 )
309 .unwrap();
310 let ack = acknowledge(&message, "AA", "ACK1", "20240101093900")
311 .build_valid()
312 .unwrap();
313 assert_eq!(ack.structure_id(), "ACK");
314 assert_eq!(ack.get("MSA-1").unwrap().as_deref(), Some("AA"));
315 assert_eq!(ack.get("MSA-2").unwrap().as_deref(), Some("99"));
316 assert_eq!(ack.get("MSH-5.1").unwrap().as_deref(), Some("LAB"));
317 assert_eq!(ack.get("MSH-6.1").unwrap().as_deref(), Some("LAB1"));
318 assert_eq!(ack.get("MSH-3.1").unwrap().as_deref(), Some("EPIC"));
319 }
320
321 #[test]
322 fn acknowledges_in_the_senders_release() {
323 let message =
324 crate::parse("MSH|^~\\&|LAB|L|EPIC|E|20240101||ORU^R01|99|P|2.3\rPID|1").unwrap();
325 let ack = acknowledge(&message, "AE", "1", "20240101")
326 .build()
327 .unwrap();
328 assert_eq!(ack.version(), Version::V2_3);
329 assert_eq!(ack.get("MSH-12").unwrap().as_deref(), Some("2.3"));
330 }
331}