hl7-parser 0.3.0

Parses the structure of HL7v2 messages, but does not validate the correctness of the messages.
Documentation
use std::ops::Range;

use super::Span;
use crate::{
    message::{Field, Segment, Separators},
    parser::field::field,
};
use nom::{
    bytes::complete::{tag, take_while_m_n},
    combinator::opt,
    multi::separated_list0,
    sequence::preceded,
    IResult,
};

#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) struct MSH<'m> {
    pub(crate) separators: Separators,
    source: &'m str,
    fields: Vec<Field<'m>>,
    range: Range<usize>,
}

pub fn msh<'i>(lenient_newlines: bool) -> impl FnMut(Span<'i>) -> IResult<Span<'i>, MSH<'i>> {
    move |i| parse_msh(i, lenient_newlines)
}

fn msh_name<'i>() -> impl FnMut(Span<'i>) -> IResult<Span<'i>, Span<'i>> {
    move |i| tag("MSH")(i)
}

fn separators<'i>(lenient_newlines: bool) -> impl FnMut(Span<'i>) -> IResult<Span<'i>, Separators> {
    move |i| {
        let (i, seps) = take_while_m_n(5, 5, |c: char| c.is_ascii())(i)?;
        let mut chars = seps.input.chars();
        let seps = Separators {
            field: chars.next().expect("field separator"),
            component: chars.next().expect("component separator"),
            repetition: chars.next().expect("repetition separator"),
            escape: chars.next().expect("escape"),
            subcomponent: chars.next().expect("subcomponent separator"),
            lenient_newlines,
        };
        Ok((i, seps))
    }
}

fn parse_msh(i: Span<'_>, lenient_newlines: bool) -> IResult<Span<'_>, MSH<'_>> {
    let input_src = i.input;
    let pos_start = i.offset;

    let (i, _) = msh_name()(i)?;
    let (i, separators) = separators(lenient_newlines)(i)?;
    let (i, mut fields) = preceded(
        opt(nom::character::complete::char(separators.field)),
        separated_list0(
            nom::character::complete::char(separators.field),
            field(separators),
        ),
    )(i)?;

    let pos_end = i.offset;
    let msh_src = &input_src[..pos_end];

    let field_separator = Field::new_single(&input_src[3..4], 3..4);
    let encoding_characters = Field::new_single(&input_src[4..8], 4..8);

    fields.insert(0, encoding_characters);
    fields.insert(0, field_separator);

    Ok((
        i,
        MSH {
            separators,
            source: msh_src,
            fields,
            range: pos_start..pos_end,
        },
    ))
}

impl<'m> From<MSH<'m>> for Segment<'m> {
    fn from(msh: MSH<'m>) -> Self {
        Segment {
            source: msh.source,
            name: "MSH",
            fields: msh.fields,
            range: msh.range,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions_sorted::assert_eq;

    #[test]
    fn can_parse_msh_start() {
        let input = Span::new(r"MSH|^~\&|");
        let actual = parse_msh(input, false).unwrap().1;
        assert_eq!(actual.fields.len(), 3);
        assert_eq!(actual.range, 0..9);
        assert_eq!(actual.separators.field, '|');
        assert_eq!(actual.separators.component, '^');
        assert_eq!(actual.separators.repetition, '~');
        assert_eq!(actual.separators.escape, '\\');
        assert_eq!(actual.separators.subcomponent, '&');
        assert_eq!(actual.fields[0].raw_value(), "|");
        assert_eq!(actual.fields[1].raw_value(), "^~\\&");
    }

    #[test]
    fn can_parse_msh() {
        let input = Span::new(r"MSH|^~\&|AccMgr|1");
        let actual = parse_msh(input, false).unwrap().1;
        assert_eq!(actual.fields.len(), 4);
        assert_eq!(actual.range, 0..17);
        assert_eq!(actual.fields[2].raw_value(), "AccMgr");
        assert_eq!(actual.fields[3].raw_value(), "1");
    }
}