hl7-parser 0.3.0

Parses the structure of HL7v2 messages, but does not validate the correctness of the messages.
Documentation
mod separators;
pub use separators::*;
mod subcomponent;
pub use subcomponent::*;
mod component;
pub use component::*;
mod repeat;
pub use repeat::*;
mod field;
pub use field::*;
mod segment;
pub use segment::*;

use crate::locate::LocatedCursor;

use crate::{
    parser::ParseError,
    query::{LocationQuery, LocationQueryResult},
};

/// A parsed HL7 message. This is the top-level structure that you get when you parse a message.
/// It contains the segments of the message, as well as the separators used in the message.
///
/// # Examples
///
/// ```
/// let message = hl7_parser::Message::parse("MSH|^~\\&|").unwrap();
/// let msh = message.segment("MSH").unwrap();
/// assert_eq!(msh.name, "MSH");
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Message<'m> {
    pub(crate) source: &'m str,
    pub segments: Vec<Segment<'m>>,
    pub separators: Separators,
}

impl<'m> Message<'m> {
    /// Parse a message from a string.
    /// This will return an error if the message is not a valid HL7 message.
    ///
    /// # Examples
    ///
    /// ```
    /// let message = hl7_parser::Message::parse("MSH|^~\\&|EPIC|EPICADT|SMS|SMSADT|199912271408|CHARRIS|ADT^A04|1817457|D|2.5|\rEVN|A04|199912271408|||CHARRIS\rPID||0493575^^^2^ID 1|454721||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M||B|254 MYSTREET AVE^^MYTOWN^OH^44123^USA||(216)123-4567|||M|NON|400003403~1129086|\rNK1||ROE^MARIE^^^^|SPO||(216)123-4567||EC|||||||||||||||||||||||||||\rPV1||O|168 ~219~C~PMA^^^^^^^^^||||277^ALLEN MYLASTNAME^BONNIE^^^^|||||||||| ||2688684|||||||||||||||||||||||||199912271408||||||002376853").unwrap();
    /// let msh = message.segment("MSH").unwrap();
    /// assert_eq!(msh.field(4).unwrap().raw_value(), "EPICADT");
    /// let pid = message.segment("PID").unwrap();
    /// let patient_name = pid.field(5).unwrap();
    /// assert_eq!(patient_name.raw_value(), "DOE^JOHN^^^^");
    /// let first_name = patient_name.component(2).unwrap();
    /// let last_name = patient_name.component(1).unwrap();
    /// assert_eq!(first_name.raw_value(), "JOHN");
    /// assert_eq!(last_name.raw_value(), "DOE");
    /// ```
    pub fn parse(input: &'m str) -> Result<Self, ParseError> {
        crate::parser::message::message(false)(input.into())
            .map(|(_, m)| m)
            .map_err(|e| e.into())
    }

    /// Parse a message from a string, allowing lenient newlines.
    /// This will return an error if the message is not a valid HL7 message.
    /// If `lenient_newlines` is true, this will allow `\n` and `\r\n` to be treated
    /// the same as `\r` as the separator for segments.
    /// This is useful for parsing messages that come as standard text files
    /// where each segment is separated by platform-specific newlines.
    ///
    /// # Examples
    ///
    /// ```
    /// let message = hl7_parser::Message::parse_with_lenient_newlines("MSH|^~\\&|EPIC|EPICADT|SMS|SMSADT|199912271408|CHARRIS|ADT^A04|1817457|D|2.5|\nEVN|A04|199912271408|||CHARRIS\nPID||0493575^^^2^ID 1|454721||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M||B|254 MYSTREET AVE^^MYTOWN^OH^44123^USA||(216)123-4567|||M|NON|400003403~1129086|\nNK1||ROE^MARIE^^^^|SPO||(216)123-4567||EC|||||||||||||||||||||||||||\nPV1||O|168 ~219~C~PMA^^^^^^^^^||||277^ALLEN MYLASTNAME^BONNIE^^^^|||||||||| ||2688684|||||||||||||||||||||||||199912271408||||||002376853", true).unwrap();
    /// let msh = message.segment("MSH").unwrap();
    /// assert_eq!(msh.field(4).unwrap().raw_value(), "EPICADT");
    /// let pid = message.segment("PID").unwrap();
    /// let patient_name = pid.field(5).unwrap();
    /// assert_eq!(patient_name.raw_value(), "DOE^JOHN^^^^");
    /// let first_name = patient_name.component(2).unwrap();
    /// let last_name = patient_name.component(1).unwrap();
    /// assert_eq!(first_name.raw_value(), "JOHN");
    /// assert_eq!(last_name.raw_value(), "DOE");
    /// ```
    pub fn parse_with_lenient_newlines(
        input: &'m str,
        lenient_newlines: bool,
    ) -> Result<Self, ParseError> {
        crate::parser::message::message(lenient_newlines)(input.into())
            .map(|(_, m)| m)
            .map_err(|e| e.into())
    }

    /// Find a segment with the given name. If there are more than one segments
    /// with this name, return the first one.
    ///
    /// # Examples
    ///
    /// ```
    /// let message = hl7_parser::Message::parse("MSH|^~\\&|EPIC|EPICADT|SMS|SMSADT|199912271408|CHARRIS|ADT^A04|1817457|D|2.5|\rEVN|A04|199912271408|||CHARRIS\rPID||0493575^^^2^ID 1|454721||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M||B|254 MYSTREET AVE^^MYTOWN^OH^44123^USA||(216)123-4567|||M|NON|400003403~1129086|\rNK1||ROE^MARIE^^^^|SPO||(216)123-4567||EC|||||||||||||||||||||||||||\rPV1||O|168 ~219~C~PMA^^^^^^^^^||||277^ALLEN MYLASTNAME^BONNIE^^^^|||||||||| ||2688684|||||||||||||||||||||||||199912271408||||||002376853").unwrap();
    /// let msh = message.segment("MSH").unwrap();
    /// assert_eq!(msh.name, "MSH");
    /// assert_eq!(msh.field(4).unwrap().raw_value(), "EPICADT");
    /// let pid = message.segment("PID").unwrap();
    /// assert_eq!(pid.field(2).unwrap().raw_value(), "0493575^^^2^ID 1");
    /// ```
    pub fn segment(&self, name: &str) -> Option<&Segment<'m>> {
        self.segments.iter().find(|s| s.name == name)
    }

    /// Find the nth segment with the given name. If there are fewer than n segments
    /// with this name, return `None`.
    /// Segments are 1-indexed.
    ///
    /// # Examples
    ///
    /// ```
    /// let message =
    /// hl7_parser::Message::parse("MSH|^~\\&|\rABC|foo\rXYZ|bar\rABC|baz").unwrap();
    /// let abc1 = message.segment_n("ABC", 1).unwrap();
    /// assert_eq!(abc1.field(1).unwrap().raw_value(), "foo");
    /// let abc2 = message.segment_n("ABC", 2).unwrap();
    /// assert_eq!(abc2.field(1).unwrap().raw_value(), "baz");
    /// let abc3 = message.segment_n("ABC", 3);
    /// assert_eq!(abc3, None);
    /// ```
    pub fn segment_n(&self, name: &str, n: usize) -> Option<&Segment<'m>> {
        debug_assert!(n > 0, "Segments are 1-indexed");
        self.segments.iter().filter(|s| s.name == name).nth(n - 1)
    }

    /// Count the number of segments with the given name.
    pub fn segment_count(&self, name: &str) -> usize {
        self.segments.iter().filter(|s| s.name == name).count()
    }

    /// An iterator over the segments of the message
    pub fn segments(&self) -> impl Iterator<Item = &Segment<'m>> {
        self.segments.iter()
    }

    /// Get the raw value of the message. This is the value as it appears in the message,
    /// without any decoding of escape sequences, and including all segments and
    /// their separators.
    /// This is the same as the input string that was used to parse the message.
    pub fn raw_value(&self) -> &'m str {
        self.source
    }

    /// Locate the cursor within the message. Equivalent to calling
    /// `hl7_parser::locate::locate_cursor` with the message and the cursor position.
    pub fn locate_cursor(&self, cursor: usize) -> Option<LocatedCursor> {
        crate::locate::locate_cursor(self, cursor)
    }

    /// Query the message for a specific location. This is a more flexible way to
    /// access the fields, components, and subcomponents of the message.
    ///
    /// # Examples
    /// ```
    /// let message =
    /// hl7_parser::Message::parse("MSH|^~\\&|foo|bar|baz|quux|20010504094523||ADT^A01|1234|P|2.3|||").unwrap();
    /// let field = message.query("MSH.3").unwrap().raw_value();
    /// assert_eq!(field, "foo");
    /// let component = message.query("MSH.7.1").unwrap().raw_value();
    /// assert_eq!(component, "20010504094523");
    /// ```
    pub fn query<Q>(&'m self, query: Q) -> Option<LocationQueryResult<'m>>
    where
        Q: TryInto<LocationQuery>,
    {
        let query = query.try_into().ok()?;
        let segment_index = query.segment_index.unwrap_or(1);

        if let Some(field) = query.field {
            let repeat = query.repeat.unwrap_or(1);
            if let Some(component) = query.component {
                if let Some(subcomponent) = query.subcomponent {
                    self.segment_n(&query.segment, segment_index)
                        .and_then(|s| s.field(field))
                        .and_then(|f| f.repeat(repeat))
                        .and_then(|r| r.component(component))
                        .and_then(|c| c.subcomponent(subcomponent))
                        .map(LocationQueryResult::Subcomponent)
                } else {
                    self.segment_n(&query.segment, segment_index)
                        .and_then(|s| s.field(field))
                        .and_then(|f| f.repeat(repeat))
                        .and_then(|r| r.component(component))
                        .map(LocationQueryResult::Component)
                }
            } else if query.repeat.is_some() {
                self.segment_n(&query.segment, segment_index)
                    .and_then(|s| s.field(field))
                    .and_then(|f| f.repeat(repeat))
                    .map(LocationQueryResult::Repeat)
            } else {
                self.segment_n(&query.segment, segment_index)
                    .and_then(|s| s.field(field))
                    .map(LocationQueryResult::Field)
            }
        } else {
            self.segment_n(&query.segment, segment_index)
                .map(LocationQueryResult::Segment)
        }
    }
}