rusthl7/
segments.rs

1use super::{fields::Field, separators::Separators, Hl7ParseError};
2use std::fmt::Display;
3use std::ops::Index;
4
5/// A generic bag o' fields, representing an arbitrary segment.
6#[derive(Debug, PartialEq, Clone)]
7pub struct Segment<'a> {
8    pub source: &'a str,
9    pub delim: char,
10    pub fields: Vec<Field<'a>>,
11}
12
13impl<'a> Segment<'a> {
14    /// Convert the given line of text into a Segment.
15    pub fn parse<S: Into<&'a str>>(
16        input: S,
17        delims: &Separators,
18    ) -> Result<Segment<'a>, Hl7ParseError> {
19        let input = input.into();
20
21        let fields: Result<Vec<Field<'a>>, Hl7ParseError> = input
22            .split(delims.field)
23            .map(|line| Field::parse(line, delims))
24            .collect();
25
26        let fields = fields?;
27        let seg = Segment {
28            source: input,
29            delim: delims.segment,
30            fields,
31        };
32        Ok(seg)
33    }
34
35    /// Export source to str
36    #[inline]
37    pub fn as_str(&self) -> &'a str {
38        self.source
39    }
40
41    /// Access Field as string reference
42    pub fn query<'b, S>(&self, fidx: S) -> &'a str
43    where
44        S: Into<&'b str>,
45    {
46        let fidx = fidx.into();
47        let sections = fidx.split('.').collect::<Vec<&str>>();
48
49        match sections.len() {
50            1 => {
51                let stringnum = sections[0]
52                    .chars()
53                    .filter(|c| c.is_digit(10))
54                    .collect::<String>();
55                let idx: usize = stringnum.parse().unwrap();
56                self[idx]
57            }
58            _ => {
59                let stringnum = sections[0]
60                    .chars()
61                    .filter(|c| c.is_digit(10))
62                    .collect::<String>();
63                let idx: usize = stringnum.parse().unwrap();
64                if idx > self.fields.len() - 1 {
65                    return "";
66                }
67                let field = &self.fields[idx];
68                let query = sections[1..].join(".");
69
70                field.query(&*query)
71            }
72        }
73    }
74}
75
76impl<'a> Display for Segment<'a> {
77    /// Required for to_string() and other formatter consumers
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        write!(f, "{}", self.source)
80    }
81}
82
83impl<'a> Index<usize> for Segment<'a> {
84    type Output = &'a str;
85    /// Access Field as string reference
86    fn index(&self, fidx: usize) -> &Self::Output {
87        if fidx > self.fields.len() - 1 {
88            return &"";
89        };
90        &self.fields[fidx].source
91    }
92}
93
94impl<'a> Index<(usize, usize)> for Segment<'a> {
95    type Output = &'a str;
96    /// Access Field component as string reference
97    fn index(&self, fidx: (usize, usize)) -> &Self::Output {
98        if fidx.0 > self.fields.len() - 1 || fidx.1 > self.fields[fidx.0].components.len() - 1 {
99            return &"";
100        }
101        &self.fields[fidx.0][fidx.1]
102    }
103}
104
105impl<'a> Index<(usize, usize, usize)> for Segment<'a> {
106    type Output = &'a str;
107    /// Access Field subcomponent as string reference
108    fn index(&self, fidx: (usize, usize, usize)) -> &Self::Output {
109        if fidx.0 > self.fields.len() - 1
110            || fidx.1 > self.fields[fidx.0].components.len() - 1
111            || fidx.2 > self.fields[fidx.0].subcomponents[fidx.1].len() - 1
112        {
113            return &"";
114        }
115        &self.fields[fidx.0][(fidx.1, fidx.2)]
116    }
117}
118
119#[cfg(feature = "string_index")]
120impl<'a> Index<&str> for Segment<'a> {
121    type Output = &'a str;
122    /// Access Field as string reference
123    #[cfg(feature = "string_index")]
124    fn index(&self, fidx: &str) -> &Self::Output {
125        let sections = fidx.split('.').collect::<Vec<&str>>();
126        let stringnum = sections[0]
127            .chars()
128            .filter(|c| c.is_digit(10))
129            .collect::<String>();
130        let mut idx: usize = stringnum.parse().unwrap();
131        // MSH segment has an off-by-one problem in that the first
132        // field separator is considered to be a field in the spec
133        // https://hl7-definition.caristix.com/v2/HL7v2.8/Segments/MSH
134        if self.fields[0].source == "MSH" {
135            if idx == 1 {
136                // return &&self.source[3..3]; //TODO figure out how to return a string ref safely
137                return &"|";
138            } else {
139                idx = idx - 1
140            }
141        }
142        match sections.len() {
143            1 => &self[idx],
144            _ => {
145                if idx < self.fields.len() {
146                    &self.fields[idx][sections[1..].join(".")]
147                } else {
148                    &""
149                }
150            }
151        }
152    }
153}
154
155#[cfg(feature = "string_index")]
156impl<'a> Index<String> for Segment<'a> {
157    type Output = &'a str;
158
159    /// Access Segment, Field, or sub-field string references by string index
160    #[cfg(feature = "string_index")]
161    fn index(&self, idx: String) -> &Self::Output {
162        &self[idx.as_str()]
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use crate::{message::Message, segments::*, separators::Separators, Hl7ParseError};
169    use std::convert::TryFrom;
170
171    #[test]
172    fn ensure_numeric_index() {
173        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment";
174        let msg = Message::try_from(hl7).unwrap();
175        let x = &msg.segments[1];
176        let (f, c, s) = (x[1], x[(1, 0)], x[(1, 0, 1)]);
177        assert_eq!(f, "segment^sub&segment");
178        assert_eq!(c, f);
179        assert_eq!(s, "sub&segment");
180    }
181
182    #[test]
183    fn ensure_string_query() {
184        let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment";
185        let msg = Message::try_from(hl7).unwrap();
186        let x = &msg.segments[1];
187        let (f, c, s, oob) = (
188            x.query("F1"),                       //&str
189            x.query("F1.R1"),                    // &str
190            x.query(&*String::from("F1.R1.C1")), //String
191            String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"),
192        );
193        assert_eq!(f, "segment^sub&segment");
194        assert_eq!(c, f);
195        assert_eq!(s, "segment");
196        assert_eq!(oob, "");
197    }
198
199    #[cfg(feature = "string_index")]
200    mod string_index_tests {
201        use super::*;
202        #[test]
203        fn ensure_string_index() {
204            let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment";
205            let msg = Message::try_from(hl7).unwrap();
206            let x = &msg.segments[1];
207            let (f, c, s, oob) = (
208                x["F1"],                  // &str
209                x["F1.R1"],               // &str
210                x["F1.R1.C1".to_owned()], // String
211                x["F1.R2.C2"],
212            );
213            assert_eq!(f, "segment^sub&segment");
214            assert_eq!(c, "segment^sub&segment");
215            assert_eq!(s, "segment");
216            assert_eq!(oob, "");
217        }
218    }
219}