rusthl7/
message.rs

1use super::segments::Segment;
2use super::separators::Separators;
3use super::*;
4use std::convert::TryFrom;
5use std::fmt::Display;
6use std::ops::Index;
7
8/// A Message is an entire HL7 message parsed into it's constituent segments, fields, repeats and subcomponents,
9/// and it consists of (1 or more) Segments.
10/// Message parses the source string into &str slices (minimising copying)
11#[derive(Debug, PartialEq)]
12pub struct Message<'a> {
13    pub source: &'a str,
14    pub segments: Vec<Segment<'a>>,
15    separators: Separators,
16}
17
18impl<'a> Message<'a> {
19    pub fn new(source: &'a str) -> Message<'a> {
20        let separators = str::parse::<Separators>(source).unwrap();
21        let segments: Vec<Segment<'a>> = source
22            .split(separators.segment)
23            .map(|line| Segment::parse(line, &separators).unwrap())
24            .collect();
25
26        Message {
27            source,
28            segments,
29            separators,
30        }
31    }
32
33    /// Extracts generic elements for external use by matching first field to name
34    pub fn segments_by_name(&self, name: &str) -> Result<Vec<&Segment<'a>>, Hl7ParseError> {
35        let found: Vec<&Segment<'a>> = self
36            .segments
37            .iter()
38            .filter(|s| s.fields[0].source == name)
39            .collect();
40        Ok(found)
41    }
42
43    /// Present input vectors of &generics to vectors of &str
44    pub fn segments_to_str_vecs(
45        segments: Vec<&Segment<'a>>,
46    ) -> Result<Vec<Vec<&'a str>>, Hl7ParseError> {
47        let vecs = segments
48            .iter()
49            .map(|s| s.fields.iter().map(|f| f.value()).collect())
50            .collect();
51        Ok(vecs)
52    }
53
54    /// Returns the source string slice used to create this Message initially.  This method does not allocate.
55    /// ## Example:
56    /// ```
57    /// # use rusthl7::Hl7ParseError;
58    /// # use rusthl7::message::Message;
59    /// # use std::convert::TryFrom;
60    /// # fn main() -> Result<(), Hl7ParseError> {
61    /// let source = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4";
62    /// let m = Message::try_from(source)?;
63    /// assert_eq!(source, m.as_str());
64    /// # Ok(())
65    /// # }
66    /// ```
67    #[inline]
68    pub fn as_str(&self) -> &'a str {
69        self.source
70    }
71
72    /// Gets the delimiter information for this Message
73    pub fn get_separators(&self) -> Separators {
74        self.separators
75    }
76
77    /// Access Segment, Field, or sub-field string references by string index
78    pub fn query<'b, S>(&self, idx: S) -> &'a str
79    where
80        S: Into<&'b str>,
81    {
82        let idx = idx.into();
83
84        // Parse index elements
85        let indices = Self::parse_query_string(idx);
86        let seg_name = indices[0];
87        // Find our first segment without offending the borow checker
88        let seg_index = self
89            .segments
90            .iter()
91            .position(|r| &r.as_str()[..seg_name.len()] == seg_name)
92            .expect("Segment not found");
93        let seg = &self.segments[seg_index];
94        if indices.len() < 2 {
95            seg.source
96        } else {
97            let query = indices[1..].join(".");
98            seg.query(&*query)
99        }
100    }
101
102    /// Parse query/index string to fill-in missing values.
103    /// Required when conumer requests "PID.F3.C1" to pass integers down
104    /// to the usize indexers at the appropriate positions
105    pub fn parse_query_string(query: &str) -> Vec<&str> {
106        fn query_idx_pos(indices: &[&str], idx: &str) -> Option<usize> {
107            indices[1..]
108                .iter()
109                .position(|r| r[0..1].to_uppercase() == idx)
110        }
111        let indices: Vec<&str> = query.split('.').collect();
112        // Leave segment name untouched - complex match
113        let mut res = vec![indices[0]];
114        // Get segment positions, if any
115        let sub_pos = query_idx_pos(&indices, "S");
116        let com_pos = query_idx_pos(&indices, "C");
117        let rep_pos = query_idx_pos(&indices, "R");
118        let fld_pos = query_idx_pos(&indices, "F");
119        // Push segment values to result, returning early if possible
120        match fld_pos {
121            Some(f) => res.push(indices[f + 1]),
122            None => {
123                // If empty but we have subsections, default to F1
124                if rep_pos.is_some() || com_pos.is_some() || sub_pos.is_some() {
125                    res.push("F1")
126                } else {
127                    return res;
128                }
129            }
130        };
131        match rep_pos {
132            Some(r) => res.push(indices[r + 1]),
133            None => {
134                // If empty but we have subsections, default to R1
135                if com_pos.is_some() || sub_pos.is_some() {
136                    res.push("R1")
137                } else {
138                    return res;
139                }
140            }
141        };
142        match com_pos {
143            Some(c) => res.push(indices[c + 1]),
144            None => {
145                // If empty but we have a subcomponent, default to C1
146                if sub_pos.is_some() {
147                    res.push("C1")
148                } else {
149                    return res;
150                }
151            }
152        };
153        if let Some(s) = sub_pos {
154            res.push(indices[s + 1])
155        }
156        res
157    }
158}
159
160impl<'a> TryFrom<&'a str> for Message<'a> {
161    type Error = Hl7ParseError;
162
163    /// Takes the source HL7 string and parses it into this message.  Segments
164    /// and other data are slices (`&str`) into the source HL7
165    fn try_from(source: &'a str) -> Result<Self, Self::Error> {
166        let delimiters = str::parse::<Separators>(source)?;
167
168        let segments: Result<Vec<Segment<'a>>, Hl7ParseError> = source
169            .split(delimiters.segment)
170            .map(|line| Segment::parse(line, &delimiters))
171            .collect();
172
173        let msg = Message {
174            source,
175            segments: segments?,
176            separators: delimiters,
177        };
178
179        Ok(msg)
180    }
181}
182
183impl<'a> Display for Message<'a> {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        write!(f, "{}", self.source)
186    }
187}
188
189impl<'a> Clone for Message<'a> {
190    /// Creates a new cloned Message object referencing the same source slice as the original.
191    /// ## Example:
192    /// ```
193    /// # use rusthl7::Hl7ParseError;
194    /// # use rusthl7::message::Message;
195    /// # use std::convert::TryFrom;
196    /// # fn main() -> Result<(), Hl7ParseError> {
197    /// let m = Message::try_from("MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4")?;
198    /// let cloned = m.clone(); // this object is looking at the same string slice as m
199    /// # Ok(())
200    /// # }
201    /// ```
202    fn clone(&self) -> Self {
203        Message::try_from(self.source).unwrap()
204    }
205}
206
207impl<'a> Index<usize> for Message<'a> {
208    type Output = &'a str;
209
210    /// Access Segment string reference by numeric index
211    fn index(&self, idx: usize) -> &Self::Output {
212        if idx > self.segments.len() {
213            return &"";
214        }
215        &self.segments[idx].source
216    }
217}
218#[cfg(feature = "string_index")]
219impl<'a> Index<String> for Message<'a> {
220    type Output = &'a str;
221
222    /// Access Segment, Field, or sub-field string references by string index
223    #[cfg(feature = "string_index")]
224    fn index(&self, idx: String) -> &Self::Output {
225        // Parse index elements
226        let indices = Self::parse_query_string(&idx);
227        let seg_name = indices[0];
228        // Find our first segment without offending the borow checker
229        let seg_index = self
230            .segments
231            .iter()
232            .position(|r| &r.as_str()[..seg_name.len()] == seg_name)
233            .expect("Segment not found");
234        let seg = &self.segments[seg_index];
235        if indices.len() < 2 {
236            &seg.source
237        } else {
238            &seg[indices[1..].join(".")]
239        }
240    }
241}
242
243#[cfg(feature = "string_index")]
244impl<'a> Index<&str> for Message<'a> {
245    type Output = &'a str;
246
247    #[cfg(feature = "string_index")]
248    fn index(&self, idx: &str) -> &Self::Output {
249        &self[String::from(idx)]
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn ensure_segments_are_returned() -> Result<(), Hl7ParseError> {
259        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
260        let msg = Message::try_from(hl7)?;
261
262        assert_eq!(msg.segments.len(), 2);
263        Ok(())
264    }
265
266    #[test]
267    fn ensure_segments_are_found() -> Result<(), Hl7ParseError> {
268        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
269        let msg = Message::try_from(hl7)?;
270
271        assert_eq!(msg.segments_by_name("OBR").unwrap().len(), 1);
272        Ok(())
273    }
274
275    #[test]
276    fn ensure_segments_convert_to_vectors() -> Result<(), Hl7ParseError> {
277        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
278        let msg = Message::try_from(hl7)?;
279        let segs = msg.segments_by_name("OBR")?;
280        let sval = segs.first().unwrap().fields.first().unwrap().value();
281        let vecs = Message::segments_to_str_vecs(segs).unwrap();
282        let vval = vecs.first().unwrap().first().unwrap();
283
284        assert_eq!(vval, &sval);
285        Ok(())
286    }
287    #[test]
288    fn ensure_clones_are_owned() -> Result<(), Hl7ParseError> {
289        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
290        let msg = Message::try_from(hl7)?;
291        // Verify that we can clone and take ownership
292        let dolly = msg.clone();
293        let dolly = dolly.to_owned();
294        assert_eq!(msg.query("MSH.F7"), dolly.query("MSH.F7"));
295        Ok(())
296    }
297
298    #[test]
299    fn ensure_to_string() -> Result<(), Hl7ParseError> {
300        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
301        let msg = Message::try_from(hl7)?;
302        assert_eq!(msg.to_string(), String::from(hl7));
303        Ok(())
304    }
305
306    #[test]
307    fn ensure_message_creation() -> Result<(), Hl7ParseError> {
308        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment";
309        let msg0 = Message::try_from(hl7)?;
310        let msg1 = Message::new(hl7);
311
312        assert_eq!(msg0, msg1);
313        Ok(())
314    }
315
316    #[test]
317    fn ensure_query() -> Result<(), Hl7ParseError> {
318        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment";
319        let msg = Message::try_from(hl7)?;
320        assert_eq!(msg.query("OBR.F1.R1.C2"), "sub&segment");
321        assert_eq!(msg.query(&*"OBR.F1.R1.C1".to_string()), "segment"); // Test the Into param with a String
322        assert_eq!(msg.query(&*String::from("OBR.F1.R1.C1")), "segment");
323        assert_eq!(msg.query("MSH.F1"), "^~\\&");
324        Ok(())
325    }
326
327    #[cfg(feature = "string_index")]
328    mod string_index_tests {
329        use super::*;
330        #[test]
331        fn ensure_index() -> Result<(), Hl7ParseError> {
332            let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment";
333            let msg = Message::try_from(hl7)?;
334            assert_eq!(msg["OBR.F1.R1.C2"], "sub&segment");
335            assert_eq!(msg[&*"OBR.F1.R1.C1".to_string()], "segment"); // Test the Into param with a String
336            assert_eq!(msg[String::from("OBR.F1.R1.C1")], "segment");
337            assert_eq!(msg[String::from("OBR.F1.C1")], "segment"); // Test missing element in selector
338            assert_eq!(msg[String::from("OBR.F1.R1.C2.S1")], "sub");
339            println!("{}", Message::parse_query_string("MSH.F2").join("."));
340            assert_eq!(msg["MSH.F2"], "^~\\&");
341            Ok(())
342        }
343    }
344}