hl7-net 0.1.0

Lightweight HL7 V2 parser/writer, ported from the Efferent HL7-V2 .NET library
Documentation
use crate::component::Component;
use crate::encoding::HL7Encoding;
use crate::error::Hl7Error;

/// A field of an HL7 segment. A field is either componentized (a list of
/// [`Component`]s separated by the component delimiter) or has repetitions
/// (a list of sub-fields separated by the repetition delimiter).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
    raw: String,
    /// The parsed components (empty for a field that has repetitions).
    pub components: Vec<Component>,
    /// The repetitions of this field (empty unless `has_repetitions`).
    pub repetitions: Vec<Field>,
    /// `true` when the field is split into more than one component.
    pub is_componentized: bool,
    /// `true` when the field contains repetitions.
    pub has_repetitions: bool,
    /// `true` for the MSH delimiter-defining fields, whose value is never decoded.
    pub is_delimiters_field: bool,
}

impl Field {
    /// Parses a field from its raw value, handling repetitions and components.
    pub fn parse(raw: &str, enc: &HL7Encoding) -> Self {
        if raw.contains(enc.repeat_delimiter) {
            let repetitions: Vec<Field> = raw
                .split(enc.repeat_delimiter)
                .map(|r| Field::parse(r, enc))
                .collect();

            Self {
                raw: raw.to_string(),
                components: Vec::new(),
                repetitions,
                is_componentized: false,
                has_repetitions: true,
                is_delimiters_field: false,
            }
        } else {
            let components: Vec<Component> = raw
                .split(enc.component_delimiter)
                .map(|c| Component::parse(c, enc))
                .collect();
            let is_componentized = components.len() > 1;

            Self {
                raw: raw.to_string(),
                components,
                repetitions: Vec::new(),
                is_componentized,
                has_repetitions: false,
                is_delimiters_field: false,
            }
        }
    }

    /// Builds a delimiter-defining field (e.g. MSH-1/MSH-2): its value is stored
    /// verbatim as a single, unsplit component.
    pub(crate) fn new_delimiters(raw: &str) -> Self {
        Self {
            raw: raw.to_string(),
            components: vec![Component::single(raw)],
            repetitions: Vec::new(),
            is_componentized: false,
            has_repetitions: false,
            is_delimiters_field: true,
        }
    }

    /// The raw value, or `None` when "present but null".
    pub fn raw(&self, enc: &HL7Encoding) -> Option<&str> {
        if self.raw == enc.present_but_null {
            None
        } else {
            Some(&self.raw)
        }
    }

    /// The decoded value, or `None` when "present but null".
    pub fn value(&self, enc: &HL7Encoding) -> Option<String> {
        if self.raw == enc.present_but_null {
            None
        } else {
            Some(enc.decode(&self.raw))
        }
    }

    /// Re-parses the field from a new raw value (preserving the delimiters flag).
    pub fn set_value(&mut self, value: &str, enc: &HL7Encoding) {
        if self.is_delimiters_field {
            *self = Field::new_delimiters(value);
        } else {
            *self = Field::parse(value, enc);
        }
    }

    /// Returns the component at the given 1-based position.
    pub fn component(&self, position: usize) -> Result<&Component, Hl7Error> {
        position
            .checked_sub(1)
            .and_then(|i| self.components.get(i))
            .ok_or_else(|| Hl7Error::new("Component not available"))
    }

    /// Returns a specific 1-based repetition, if this field has repetitions.
    pub fn repetition(&self, repetition_number: usize) -> Option<&Field> {
        if self.has_repetitions {
            repetition_number.checked_sub(1).and_then(|i| self.repetitions.get(i))
        } else {
            None
        }
    }

    /// Appends a component to the end of the field.
    pub fn add_component(&mut self, component: Component) {
        self.components.push(component);
    }

    /// Inserts a component at a 1-based position, padding with blank components
    /// when the position is beyond the current length.
    pub fn add_component_at(&mut self, component: Component, position: usize, enc: &HL7Encoding) {
        let index = position.saturating_sub(1);

        if index < self.components.len() {
            self.components[index] = component;
        } else {
            while self.components.len() < index {
                self.components.push(Component::parse("", enc));
            }
            self.components.push(component);
        }
    }

    /// Adds a field as a repetition. Requires `has_repetitions` to be set.
    pub fn add_repeating_field(&mut self, field: Field) -> Result<(), Hl7Error> {
        if !self.has_repetitions {
            return Err(Hl7Error::new(
                "Repeating field must have repetitions (has_repetitions = true)",
            ));
        }

        self.repetitions.push(field);
        Ok(())
    }

    /// Removes any trailing components whose decoded value is the empty string.
    pub fn remove_empty_trailing_components(&mut self, enc: &HL7Encoding) {
        while let Some(last) = self.components.last() {
            if last.value(enc).as_deref() == Some("") {
                self.components.pop();
            } else {
                break;
            }
        }
    }

    /// Serializes the field (including any repetitions) into `out`.
    pub fn serialize(&self, out: &mut String, enc: &HL7Encoding) {
        if self.is_delimiters_field {
            if let Some(raw) = self.raw(enc) {
                out.push_str(raw);
            }
            return;
        }

        if self.has_repetitions {
            for (j, rep) in self.repetitions.iter().enumerate() {
                if j > 0 {
                    out.push(enc.repeat_delimiter);
                }
                rep.serialize_single(out, enc);
            }
        } else {
            self.serialize_single(out, enc);
        }
    }

    /// Serializes a single (non-repeating) field's components and subcomponents.
    fn serialize_single(&self, out: &mut String, enc: &HL7Encoding) {
        if self.components.is_empty() {
            out.push_str(&enc.encode_opt(self.value(enc).as_deref()));
            return;
        }

        for (idx, com) in self.components.iter().enumerate() {
            if idx > 0 {
                out.push(enc.component_delimiter);
            }

            if com.sub_components.is_empty() {
                out.push_str(&enc.encode_opt(com.value(enc).as_deref()));
            } else {
                for (i, sub) in com.sub_components.iter().enumerate() {
                    if i > 0 {
                        out.push(enc.subcomponent_delimiter);
                    }
                    out.push_str(&enc.encode_opt(sub.value(enc).as_deref()));
                }
            }
        }
    }
}