1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, thiserror::Error)]
16pub enum Error {
17 #[error("Invalid segment ID")]
18 InvalidSegmentId,
19
20 #[error("Bad delimiter length")]
21 BadDelimLength,
22
23 #[error("Duplicate delimiters")]
24 DuplicateDelims,
25
26 #[error("Unbalanced escape")]
27 UnbalancedEscape,
28
29 #[error("Invalid escape token")]
30 InvalidEscapeToken,
31
32 #[error("MSH field malformed")]
33 MshFieldMalformed,
34
35 #[error("MSH-10 missing")]
36 Msh10Missing,
37
38 #[error("Invalid processing ID")]
39 InvalidProcessingId,
40
41 #[error("Unrecognized version")]
42 UnrecognizedVersion,
43
44 #[error("Invalid charset")]
45 InvalidCharset,
46
47 #[error("Framing error: {0}")]
48 Framing(String),
49
50 #[error("Write failed")]
51 WriteFailed,
52
53 #[error("Parse error at segment {segment_id} field {field_index}: {source}")]
54 ParseError {
55 segment_id: String,
56 field_index: usize,
57 #[source]
58 source: Box<Error>,
59 },
60
61 #[error("Invalid field format: {details}")]
62 InvalidFieldFormat { details: String },
63
64 #[error("Invalid repetition format: {details}")]
65 InvalidRepFormat { details: String },
66
67 #[error("Invalid component format: {details}")]
68 InvalidCompFormat { details: String },
69
70 #[error("Invalid subcomponent format: {details}")]
71 InvalidSubcompFormat { details: String },
72
73 #[error("Batch parsing error: {details}")]
74 BatchParseError { details: String },
75
76 #[error("Invalid batch header: {details}")]
77 InvalidBatchHeader { details: String },
78
79 #[error("Invalid batch trailer: {details}")]
80 InvalidBatchTrailer { details: String },
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct Delims {
86 pub field: char,
87 pub comp: char,
88 pub rep: char,
89 pub esc: char,
90 pub sub: char,
91}
92
93impl Default for Delims {
94 fn default() -> Self {
95 Self {
96 field: '|',
97 comp: '^',
98 rep: '~',
99 esc: '\\',
100 sub: '&',
101 }
102 }
103}
104
105impl Delims {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn parse_from_msh(msh: &str) -> Result<Self, Error> {
113 if msh.len() < 8 {
114 return Err(Error::BadDelimLength);
115 }
116
117 let field_sep = msh.chars().nth(3).ok_or(Error::BadDelimLength)?;
118 let comp_char = msh.chars().nth(4).ok_or(Error::BadDelimLength)?;
119 let rep_char = msh.chars().nth(5).ok_or(Error::BadDelimLength)?;
120 let esc_char = msh.chars().nth(6).ok_or(Error::BadDelimLength)?;
121 let sub_char = msh.chars().nth(7).ok_or(Error::BadDelimLength)?;
122
123 let delimiters = [field_sep, comp_char, rep_char, esc_char, sub_char];
125 for i in 0..delimiters.len() {
126 for j in (i + 1)..delimiters.len() {
127 if delimiters[i] == delimiters[j] {
128 return Err(Error::DuplicateDelims);
129 }
130 }
131 }
132
133 Ok(Self {
134 field: field_sep,
135 comp: comp_char,
136 rep: rep_char,
137 esc: esc_char,
138 sub: sub_char,
139 })
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct Message {
146 pub delims: Delims,
147 pub segments: Vec<Segment>,
148 #[serde(default)]
150 pub charsets: Vec<String>,
151}
152
153impl Message {
154 pub fn new() -> Self {
156 Self {
157 delims: Delims::default(),
158 segments: Vec::new(),
159 charsets: Vec::new(),
160 }
161 }
162
163 pub fn with_segments(segments: Vec<Segment>) -> Self {
165 Self {
166 delims: Delims::default(),
167 segments,
168 charsets: Vec::new(),
169 }
170 }
171}
172
173impl Default for Message {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
181pub struct Batch {
182 pub header: Option<Segment>, pub messages: Vec<Message>,
184 pub trailer: Option<Segment>, }
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
189pub struct FileBatch {
190 pub header: Option<Segment>, pub batches: Vec<Batch>,
192 pub trailer: Option<Segment>, }
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct Segment {
198 pub id: [u8; 3],
199 pub fields: Vec<Field>,
200}
201
202impl Segment {
203 pub fn new(id: &[u8; 3]) -> Self {
205 Self {
206 id: *id,
207 fields: Vec::new(),
208 }
209 }
210
211 pub fn id_str(&self) -> &str {
213 std::str::from_utf8(&self.id).unwrap_or("???")
214 }
215
216 pub fn add_field(&mut self, field: Field) {
218 self.fields.push(field);
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct Field {
225 pub reps: Vec<Rep>,
226}
227
228impl Field {
229 pub fn new() -> Self {
231 Self { reps: Vec::new() }
232 }
233
234 pub fn from_text(text: impl Into<String>) -> Self {
236 Self {
237 reps: vec![Rep::from_text(text)],
238 }
239 }
240
241 pub fn add_rep(&mut self, rep: Rep) {
243 self.reps.push(rep);
244 }
245
246 pub fn first_text(&self) -> Option<&str> {
248 self.reps
249 .first()?
250 .comps
251 .first()?
252 .subs
253 .first()
254 .and_then(|atom| match atom {
255 Atom::Text(t) => Some(t.as_str()),
256 Atom::Null => None,
257 })
258 }
259}
260
261impl Default for Field {
262 fn default() -> Self {
263 Self::new()
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct Rep {
270 pub comps: Vec<Comp>,
271}
272
273impl Rep {
274 pub fn new() -> Self {
276 Self { comps: Vec::new() }
277 }
278
279 pub fn from_text(text: impl Into<String>) -> Self {
281 Self {
282 comps: vec![Comp::from_text(text)],
283 }
284 }
285
286 pub fn add_comp(&mut self, comp: Comp) {
288 self.comps.push(comp);
289 }
290}
291
292impl Default for Rep {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct Comp {
301 pub subs: Vec<Atom>,
302}
303
304impl Comp {
305 pub fn new() -> Self {
307 Self { subs: Vec::new() }
308 }
309
310 pub fn from_text(text: impl Into<String>) -> Self {
312 Self {
313 subs: vec![Atom::Text(text.into())],
314 }
315 }
316
317 pub fn add_sub(&mut self, atom: Atom) {
319 self.subs.push(atom);
320 }
321}
322
323impl Default for Comp {
324 fn default() -> Self {
325 Self::new()
326 }
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub enum Atom {
332 Text(String),
333 Null,
334}
335
336impl Atom {
337 pub fn text(s: impl Into<String>) -> Self {
339 Atom::Text(s.into())
340 }
341
342 pub fn null() -> Self {
344 Atom::Null
345 }
346
347 pub fn is_null(&self) -> bool {
349 matches!(self, Atom::Null)
350 }
351
352 pub fn as_text(&self) -> Option<&str> {
354 match self {
355 Atom::Text(s) => Some(s.as_str()),
356 Atom::Null => None,
357 }
358 }
359}
360
361#[derive(Debug, Clone, PartialEq)]
363pub enum Presence {
364 Missing,
366 Empty,
368 Null,
370 Value(String),
372}
373
374impl Presence {
375 pub fn is_missing(&self) -> bool {
377 matches!(self, Presence::Missing)
378 }
379
380 pub fn is_present(&self) -> bool {
382 !self.is_missing()
383 }
384
385 pub fn has_value(&self) -> bool {
387 matches!(self, Presence::Value(_))
388 }
389
390 pub fn value(&self) -> Option<&str> {
392 match self {
393 Presence::Value(v) => Some(v.as_str()),
394 _ => None,
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests;