hl7_parser/query/
mod.rs

1//! # Querying HL7 messages
2//!
3//! This module provides utilities for querying HL7 messages. This is useful for extracting
4//! specific values from an HL7 message, such as the patient's name or the ordering provider's
5//! name without having to manually traverse the message.
6//!
7//! ## Location queries
8//!
9//! A location query is a string that describes the location of a value within an HL7 message.
10//! The query is made up of the segment name, field index, repeat index, component index, and
11//! subcomponent index. Each part of the query is separated by a period (`.`), and each index is
12//! enclosed in square brackets (`[]`). For example, the query `PID.5[1].1` would refer to the
13//! first subcomponent of the first component of the first repeat of the fifth field of the PID
14//! segment.
15//!
16//! ## Examples
17//!
18//! ```
19//! use hl7_parser::query::LocationQuery;
20//! let query = LocationQuery::parse("MSH[1].2[3].4.5").unwrap();
21//! assert_eq!(query.segment, "MSH");
22//! assert_eq!(query.segment_index, Some(1));
23//! assert_eq!(query.field, Some(2));
24//! assert_eq!(query.repeat, Some(3));
25//! assert_eq!(query.component, Some(4));
26//! assert_eq!(query.subcomponent, Some(5));
27//! ```
28//!
29//! ```
30//! use hl7_parser::query::LocationQuery;
31//! let query = LocationQuery::parse("MSH.2.4").unwrap();
32//! assert_eq!(query.segment, "MSH");
33//! assert_eq!(query.segment_index, None);
34//! assert_eq!(query.field, Some(2));
35//! assert_eq!(query.repeat, None);
36//! assert_eq!(query.component, Some(4));
37//! assert_eq!(query.subcomponent, None);
38//! ```
39//!
40//! ## Building location queries
41//!
42//! A location query can also be built using a builder pattern. This is useful when you want to
43//! ensure that the query is valid at before using it.
44//! ```
45//! use hl7_parser::query::LocationQueryBuilder;
46//! let query = LocationQueryBuilder::new()
47//!    .segment("MSH")
48//!    .segment_index(1)
49//!    .field(2)
50//!    .repeat(3)
51//!    .component(4)
52//!    .subcomponent(5)
53//!    .build()
54//!    .unwrap();
55//! assert_eq!(query.to_string(), "MSH[1].2[3].4.5");
56//! ```
57
58mod parser;
59
60use std::{fmt::Display, str::FromStr};
61
62pub use parser::QueryParseError;
63use thiserror::Error;
64
65use crate::{
66    message::{Component, Field, Repeat, Segment, Separators, Subcomponent},
67    parser::Span,
68};
69
70/// A location query that describes the location of a value within an HL7 message.
71/// The query is made up of the segment name, field index, repeat index, component index, and
72/// subcomponent index. Each part of the query is separated by a period (`.`), and each index is
73/// enclosed in square brackets (`[]`). For example, the query `PID.5[1].1` would refer to the
74/// first subcomponent of the first component of the first repeat of the fifth field of the PID
75/// segment.
76///
77/// All indexes are 1-based.
78#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80pub struct LocationQuery {
81    pub segment: String,
82    pub segment_index: Option<usize>,
83    pub field: Option<usize>,
84    pub repeat: Option<usize>,
85    pub component: Option<usize>,
86    pub subcomponent: Option<usize>,
87}
88
89/// Parse a location query from a string
90///
91/// # Examples
92/// ```
93/// use hl7_parser::query::parse_location_query;
94/// let query = parse_location_query("MSH[1].2[3].4.5").unwrap();
95/// assert_eq!(query.segment, "MSH");
96/// assert_eq!(query.segment_index, Some(1));
97/// assert_eq!(query.field, Some(2));
98/// assert_eq!(query.repeat, Some(3));
99/// assert_eq!(query.component, Some(4));
100/// assert_eq!(query.subcomponent, Some(5));
101/// ```
102///
103/// ```
104/// use hl7_parser::query::parse_location_query;
105/// let query = parse_location_query("MSH.2.4").unwrap();
106/// assert_eq!(query.segment, "MSH");
107/// assert_eq!(query.segment_index, None);
108/// assert_eq!(query.field, Some(2));
109/// assert_eq!(query.repeat, None);
110/// assert_eq!(query.component, Some(4));
111/// assert_eq!(query.subcomponent, None);
112/// ```
113pub fn parse_location_query(query: &str) -> Result<LocationQuery, QueryParseError> {
114    parser::parse_query(Span::new(query))
115        .map(|(_, m)| m)
116        .map_err(|e| e.into())
117}
118
119impl FromStr for LocationQuery {
120    type Err = QueryParseError;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        parse_location_query(s)
124    }
125}
126
127impl TryFrom<&str> for LocationQuery {
128    type Error = QueryParseError;
129
130    fn try_from(value: &str) -> Result<Self, Self::Error> {
131        parse_location_query(value)
132    }
133}
134
135impl TryFrom<String> for LocationQuery {
136    type Error = QueryParseError;
137
138    fn try_from(value: String) -> Result<Self, Self::Error> {
139        parse_location_query(&value)
140    }
141}
142
143impl TryFrom<&String> for LocationQuery {
144    type Error = QueryParseError;
145
146    fn try_from(value: &String) -> Result<Self, Self::Error> {
147        parse_location_query(value)
148    }
149}
150
151impl Display for LocationQuery {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        write!(f, "{}", self.segment)?;
154        if let Some(i) = self.segment_index {
155            write!(f, "[{}]", i)?;
156        }
157        if let Some(i) = self.field {
158            write!(f, ".{}", i)?;
159        } else {
160            return Ok(());
161        }
162        if let Some(i) = self.repeat {
163            write!(f, "[{}]", i)?;
164        }
165        if let Some(i) = self.component {
166            write!(f, ".{}", i)?;
167        } else {
168            return Ok(());
169        }
170        if let Some(i) = self.subcomponent {
171            write!(f, ".{}", i)?;
172        }
173        Ok(())
174    }
175}
176
177impl LocationQuery {
178    /// Parse a location query from a string. Equivalent to `parse_location_query`.
179    pub fn parse(query: &str) -> Result<Self, QueryParseError> {
180        parse_location_query(query)
181    }
182}
183
184/// A builder for creating a location query with error checking.
185#[derive(Debug, Clone)]
186pub struct LocationQueryBuilder {
187    segment: Option<String>,
188    segment_index: Option<usize>,
189    field: Option<usize>,
190    repeat: Option<usize>,
191    component: Option<usize>,
192    subcomponent: Option<usize>,
193}
194
195/// Errors that can occur when building a location query
196#[derive(Debug, Clone, Error)]
197pub enum LocationQueryBuildError {
198    /// The segment is missing
199    #[error("Missing segment: segment is required")]
200    MissingSegment,
201    /// The segment is not 3 characters long
202    #[error("Invalid segment length: segments must be 3 characters long")]
203    InvalidSegmentLength,
204    /// The segment contains non-ASCII uppercase characters
205    #[error("Invalid segment name: segments must be ASCII uppercase")]
206    InvalidSegmentName,
207    /// The segment index is 0
208    #[error("Invalid segment index: segment index must be greater than 0")]
209    InvalidSegmentIndex,
210    /// The field index is 0
211    #[error("Invalid field index: field index must be greater than 0")]
212    InvalidFieldIndex,
213    /// The repeat index is 0
214    #[error("Invalid repeat index: repeat index must be greater than 0")]
215    InvalidRepeatIndex,
216    /// The component index is 0
217    #[error("Invalid component index: component index must be greater than 0")]
218    InvalidComponentIndex,
219    /// The subcomponent index is 0
220    #[error("Invalid subcomponent index: subcomponent index must be greater than 0")]
221    InvalidSubcomponentIndex,
222}
223
224impl Default for LocationQueryBuilder {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230impl LocationQueryBuilder {
231    pub fn new() -> Self {
232        Self {
233            segment: None,
234            segment_index: None,
235            field: None,
236            repeat: None,
237            component: None,
238            subcomponent: None,
239        }
240    }
241
242    /// Create a new location query builder. The segment must be 3 characters long and ASCII
243    /// uppercase.
244    pub fn segment<S: ToString>(mut self, segment: S) -> Self {
245        self.segment = Some(segment.to_string());
246        self
247    }
248
249    /// Set the segment index. This is optional. If not set, the segment index will not be included
250    /// in the query. If set, the segment index must be greater than 0.
251    pub fn segment_index(mut self, index: usize) -> Self {
252        self.segment_index = Some(index);
253        self
254    }
255
256    /// Set the field index. This is optional. If not set, the field index will not be included in
257    /// the query. If set, the field index must be greater than 0.
258    pub fn field(mut self, index: usize) -> Self {
259        self.field = Some(index);
260        self
261    }
262
263    /// Set the repeat index. This is optional. If not set, the repeat index will not be included
264    /// in the query. If set, the repeat index must be greater than 0.
265    pub fn repeat(mut self, index: usize) -> Self {
266        self.repeat = Some(index);
267        self
268    }
269
270    /// Set the component index. This is optional. If not set, the component index will not be
271    /// included in the query. If set, the component index must be greater than 0.
272    pub fn component(mut self, index: usize) -> Self {
273        self.component = Some(index);
274        self
275    }
276
277    /// Set the subcomponent index. This is optional. If not set, the subcomponent index will not
278    /// be included in the query. If set, the subcomponent index must be greater than 0.
279    pub fn subcomponent(mut self, index: usize) -> Self {
280        self.subcomponent = Some(index);
281        self
282    }
283
284    /// Build the location query
285    pub fn build(self) -> Result<LocationQuery, LocationQueryBuildError> {
286        let segment = if let Some(segment) = self.segment {
287            if segment.len() != 3 {
288                return Err(LocationQueryBuildError::InvalidSegmentLength);
289            }
290            if !segment.chars().all(|c| c.is_ascii_uppercase()) {
291                return Err(LocationQueryBuildError::InvalidSegmentName);
292            }
293            segment
294        } else {
295            return Err(LocationQueryBuildError::MissingSegment);
296        };
297
298        let segment_index = if let Some(segment_index) = self.segment_index {
299            if segment_index == 0 {
300                return Err(LocationQueryBuildError::InvalidSegmentIndex);
301            }
302            Some(segment_index)
303        } else {
304            None
305        };
306
307        let field = if let Some(field) = self.field {
308            if field == 0 {
309                return Err(LocationQueryBuildError::InvalidFieldIndex);
310            }
311            Some(field)
312        } else {
313            None
314        };
315
316        let repeat = if let Some(repeat) = self.repeat {
317            if repeat == 0 {
318                return Err(LocationQueryBuildError::InvalidRepeatIndex);
319            }
320            Some(repeat)
321        } else {
322            None
323        };
324
325        let component = if let Some(component) = self.component {
326            if component == 0 {
327                return Err(LocationQueryBuildError::InvalidComponentIndex);
328            }
329            Some(component)
330        } else {
331            None
332        };
333
334        let subcomponent = if let Some(subcomponent) = self.subcomponent {
335            if subcomponent == 0 {
336                return Err(LocationQueryBuildError::InvalidSubcomponentIndex);
337            }
338            Some(subcomponent)
339        } else {
340            None
341        };
342
343        Ok(LocationQuery {
344            segment,
345            segment_index,
346            field,
347            repeat,
348            component,
349            subcomponent,
350        })
351    }
352}
353
354/// A result of a location query. This can be a segment, field, repeat, component, or subcomponent.
355/// The result contains a reference to the corresponding part of the message.
356/// The result can be used to get the raw value of the part of the message, or to display the value
357/// using the separators to decode escape sequences.
358#[derive(Debug, Clone, PartialEq, Eq)]
359#[cfg_attr(feature = "serde", derive(serde::Serialize))]
360pub enum LocationQueryResult<'m> {
361    Segment(&'m Segment<'m>),
362    Field(&'m Field<'m>),
363    Repeat(&'m Repeat<'m>),
364    Component(&'m Component<'m>),
365    Subcomponent(&'m Subcomponent<'m>),
366}
367
368impl<'m> LocationQueryResult<'m> {
369    /// Get the raw value of the result. This is the value as it appears in the message,
370    /// without any decoding of escape sequences.
371    /// Segments will be separated by the segment separator character.
372    /// Fields will be separated by the field separator character.
373    /// Repeats will be separated by the repeat separator character.
374    /// Components will be separated by the component separator character.
375    /// Subcomponents will be separated by the subcomponent separator character.
376    pub fn raw_value(&self) -> &'m str {
377        match self {
378            LocationQueryResult::Segment(seg) => seg.raw_value(),
379            LocationQueryResult::Field(field) => field.raw_value(),
380            LocationQueryResult::Repeat(repeat) => repeat.raw_value(),
381            LocationQueryResult::Component(component) => component.raw_value(),
382            LocationQueryResult::Subcomponent(subcomponent) => subcomponent.raw_value(),
383        }
384    }
385
386    /// Get the range of the result within the message. This is the character range of the result
387    /// within the message, including the separators.
388    pub fn range(&self) -> std::ops::Range<usize> {
389        match self {
390            LocationQueryResult::Segment(seg) => seg.range.clone(),
391            LocationQueryResult::Field(field) => field.range.clone(),
392            LocationQueryResult::Repeat(repeat) => repeat.range.clone(),
393            LocationQueryResult::Component(component) => component.range.clone(),
394            LocationQueryResult::Subcomponent(subcomponent) => subcomponent.range.clone(),
395        }
396    }
397
398    /// Display the result, using the separators to decode escape sequences
399    /// by default. Note: if you want to display the raw value without decoding escape
400    /// sequences, use the `#` flag, e.g. `format!("{:#}", result.display(separators))`.
401    pub fn display(&self, separators: &'m Separators) -> LocationQueryResultDisplay<'m> {
402        LocationQueryResultDisplay {
403            value: self.raw_value(),
404            separators,
405        }
406    }
407}
408
409/// Display the result of a location query, using the separators to decode escape sequences
410pub struct LocationQueryResultDisplay<'m> {
411    value: &'m str,
412    separators: &'m Separators,
413}
414
415impl<'m> Display for LocationQueryResultDisplay<'m> {
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        if f.alternate() {
418            write!(f, "{}", self.value)
419        } else {
420            write!(f, "{}", self.separators.decode(self.value))
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use pretty_assertions_sorted::assert_eq;
429
430    #[test]
431    fn can_display_location_query() {
432        let query = LocationQuery {
433            segment: "MSH".to_string(),
434            segment_index: Some(1),
435            field: Some(2),
436            repeat: Some(3),
437            component: Some(4),
438            subcomponent: Some(5),
439        };
440        assert_eq!(query.to_string(), "MSH[1].2[3].4.5");
441
442        let query = LocationQuery {
443            segment: "MSH".to_string(),
444            segment_index: None,
445            field: Some(2),
446            repeat: None,
447            component: Some(4),
448            subcomponent: None,
449        };
450        assert_eq!(query.to_string(), "MSH.2.4");
451
452        let query = LocationQuery {
453            segment: "MSH".to_string(),
454            segment_index: None,
455            field: None,
456            repeat: None,
457            component: Some(4),
458            subcomponent: Some(5),
459        };
460        assert_eq!(query.to_string(), "MSH");
461    }
462}