Skip to main content

hl7_net/
segment.rs

1use crate::encoding::HL7Encoding;
2use crate::error::Hl7Error;
3use crate::field::Field;
4
5/// An HL7 segment: a name (e.g. `MSH`, `PID`) followed by a list of [`Field`]s.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Segment {
8    raw: String,
9    /// The segment name (first three characters).
10    pub name: String,
11    /// The fields of the segment. For `MSH`, index 0 is the field-delimiter field
12    /// and index 1 is the encoding-characters field.
13    pub fields: Vec<Field>,
14    /// Order in which the segment appeared in the original message.
15    pub(crate) sequence_no: usize,
16}
17
18impl Segment {
19    /// Creates an empty, named segment.
20    pub fn new(name: impl Into<String>) -> Self {
21        Self {
22            raw: String::new(),
23            name: name.into(),
24            fields: Vec::new(),
25            sequence_no: 0,
26        }
27    }
28
29    /// Parses a segment from a raw line, applying the MSH special-casing for the
30    /// delimiter fields.
31    pub fn parse(raw: &str, enc: &HL7Encoding) -> Result<Self, Hl7Error> {
32        if raw.chars().count() < 3 {
33            return Err(Hl7Error::with_code(
34                format!("Invalid segment (too short): {raw}"),
35                Hl7Error::BAD_MESSAGE,
36            ));
37        }
38
39        let name: String = raw.chars().take(3).collect();
40        let is_msh = name == "MSH";
41
42        let parts: Vec<&str> = raw.split(enc.field_delimiter).collect();
43        let mut fields = Vec::with_capacity(parts.len());
44
45        for (i, part) in parts.iter().enumerate().skip(1) {
46            let field = if is_msh && i == 1 {
47                Field::new_delimiters(part)
48            } else {
49                Field::parse(part, enc)
50            };
51            fields.push(field);
52        }
53
54        if is_msh {
55            // MSH-1 is the field delimiter itself.
56            fields.insert(0, Field::new_delimiters(&enc.field_delimiter.to_string()));
57        }
58
59        Ok(Self { raw: raw.to_string(), name, fields, sequence_no: 0 })
60    }
61
62    /// A deep copy of the segment.
63    pub fn deep_copy(&self) -> Segment {
64        self.clone()
65    }
66
67    /// The raw value, or `None` when "present but null".
68    pub fn raw(&self, enc: &HL7Encoding) -> Option<&str> {
69        if self.raw == enc.present_but_null {
70            None
71        } else {
72            Some(&self.raw)
73        }
74    }
75
76    /// The decoded value of the whole segment line.
77    pub fn value(&self, enc: &HL7Encoding) -> Option<String> {
78        if self.raw == enc.present_but_null {
79            None
80        } else {
81            Some(enc.decode(&self.raw))
82        }
83    }
84
85    /// The 0-based sequence number of the segment within its message.
86    pub fn sequence_no(&self) -> usize {
87        self.sequence_no
88    }
89
90    /// Appends an empty field.
91    pub fn add_empty_field(&mut self, enc: &HL7Encoding) {
92        self.add_new_field(Field::parse("", enc));
93    }
94
95    /// Appends a field parsed from `content`.
96    pub fn add_new_field_value(&mut self, content: &str, enc: &HL7Encoding) {
97        self.add_new_field(Field::parse(content, enc));
98    }
99
100    /// Appends a field.
101    pub fn add_new_field(&mut self, field: Field) {
102        self.fields.push(field);
103    }
104
105    /// Inserts a field at a 1-based position, padding with blank fields when the
106    /// position is beyond the current length.
107    pub fn add_new_field_at(&mut self, field: Field, position: usize, enc: &HL7Encoding) {
108        let index = position.saturating_sub(1);
109
110        if index < self.fields.len() {
111            self.fields[index] = field;
112        } else {
113            while self.fields.len() < index {
114                self.fields.push(Field::parse("", enc));
115            }
116            self.fields.push(field);
117        }
118    }
119
120    /// Returns the field at the given 1-based position.
121    pub fn field(&self, position: usize) -> Result<&Field, Hl7Error> {
122        position
123            .checked_sub(1)
124            .and_then(|i| self.fields.get(i))
125            .ok_or_else(|| Hl7Error::new("Field not available"))
126    }
127
128    /// Serializes the segment into `out`.
129    pub fn serialize(&self, out: &mut String, enc: &HL7Encoding) {
130        out.push_str(&self.name);
131
132        if !self.fields.is_empty() {
133            out.push(enc.field_delimiter);
134        }
135
136        // For MSH, field 0 is the field delimiter (already written above).
137        let start = if self.name == "MSH" { 1 } else { 0 };
138
139        for i in start..self.fields.len() {
140            if i > start {
141                out.push(enc.field_delimiter);
142            }
143            self.fields[i].serialize(out, enc);
144        }
145
146        out.push_str(&enc.segment_delimiter);
147    }
148}