1use hl7v2_escape::escape_text;
34use hl7v2_model::*;
35
36pub use hl7v2_json::{to_json, to_json_string, to_json_string_pretty};
38
39pub fn write(msg: &Message) -> Vec<u8> {
61 let mut buf = Vec::new();
62
63 for segment in &msg.segments {
65 buf.extend_from_slice(&segment.id);
67
68 if &segment.id == b"MSH" {
70 buf.push(msg.delims.field as u8);
72
73 buf.push(msg.delims.comp as u8);
75 buf.push(msg.delims.rep as u8);
76 buf.push(msg.delims.esc as u8);
77 buf.push(msg.delims.sub as u8);
78
79 for field in &segment.fields[1..] {
81 buf.push(msg.delims.field as u8);
83 write_field(&mut buf, field, &msg.delims);
84 }
85 } else {
86 for field in &segment.fields {
88 buf.push(msg.delims.field as u8);
89 write_field(&mut buf, field, &msg.delims);
90 }
91 }
92
93 buf.push(b'\r');
95 }
96
97 buf
98}
99
100pub fn write_mllp(msg: &Message) -> Vec<u8> {
123 let hl7_bytes = write(msg);
124 hl7v2_mllp::wrap_mllp(&hl7_bytes)
125}
126
127pub fn write_batch(batch: &Batch) -> Vec<u8> {
137 let mut result = Vec::new();
138
139 if let Some(header) = &batch.header {
141 result.extend_from_slice(&header.id);
142 let delims = if let Some(first_msg) = batch.messages.first() {
144 &first_msg.delims
145 } else {
146 &Delims::default()
147 };
148 result.push(delims.field as u8);
149 write_segment_fields(header, &mut result, delims);
150 result.push(b'\r');
151 }
152
153 for message in &batch.messages {
155 result.extend(write(message));
156 }
157
158 if let Some(trailer) = &batch.trailer {
160 result.extend_from_slice(&trailer.id);
161 let delims = if let Some(first_msg) = batch.messages.first() {
162 &first_msg.delims
163 } else {
164 &Delims::default()
165 };
166 result.push(delims.field as u8);
167 write_segment_fields(trailer, &mut result, delims);
168 result.push(b'\r');
169 }
170
171 result
172}
173
174pub fn write_file_batch(file_batch: &FileBatch) -> Vec<u8> {
184 let mut result = Vec::new();
185
186 if let Some(header) = &file_batch.header {
188 result.extend_from_slice(&header.id);
189 let delims = get_delimiters_from_file_batch(file_batch);
190 result.push(delims.field as u8);
191 write_segment_fields(header, &mut result, &delims);
192 result.push(b'\r');
193 }
194
195 for batch in &file_batch.batches {
197 result.extend(write_batch(batch));
198 }
199
200 if let Some(trailer) = &file_batch.trailer {
202 result.extend_from_slice(&trailer.id);
203 let delims = get_delimiters_from_file_batch(file_batch);
204 result.push(delims.field as u8);
205 write_segment_fields(trailer, &mut result, &delims);
206 result.push(b'\r');
207 }
208
209 result
210}
211
212fn write_field(output: &mut Vec<u8>, field: &Field, delims: &Delims) {
218 for (i, rep) in field.reps.iter().enumerate() {
219 if i > 0 {
220 output.push(delims.rep as u8);
221 }
222 write_rep(output, rep, delims);
223 }
224}
225
226fn write_rep(output: &mut Vec<u8>, rep: &Rep, delims: &Delims) {
228 for (i, comp) in rep.comps.iter().enumerate() {
229 if i > 0 {
230 output.push(delims.comp as u8);
231 }
232 write_comp(output, comp, delims);
233 }
234}
235
236fn write_comp(output: &mut Vec<u8>, comp: &Comp, delims: &Delims) {
238 for (i, atom) in comp.subs.iter().enumerate() {
239 if i > 0 {
240 output.push(delims.sub as u8);
241 }
242 write_atom(output, atom, delims);
243 }
244}
245
246fn write_atom(output: &mut Vec<u8>, atom: &Atom, delims: &Delims) {
248 match atom {
249 Atom::Text(text) => {
250 let escaped = escape_text(text, delims);
252 output.extend_from_slice(escaped.as_bytes());
253 }
254 Atom::Null => {
255 output.extend_from_slice(b"\"\"");
256 }
257 }
258}
259
260fn write_segment_fields(segment: &Segment, output: &mut Vec<u8>, delims: &Delims) {
262 for (i, field) in segment.fields.iter().enumerate() {
263 if i > 0 {
264 output.push(delims.field as u8);
265 }
266 write_field(output, field, delims);
267 }
268}
269
270fn get_delimiters_from_file_batch(file_batch: &FileBatch) -> Delims {
272 if let Some(first_batch) = file_batch.batches.first()
274 && let Some(first_message) = first_batch.messages.first()
275 {
276 return first_message.delims.clone();
277 }
278 Delims::default()
280}
281
282#[cfg(test)]
283mod tests;
284
285#[cfg(test)]
286mod integration_tests {
287 use super::*;
288 use hl7v2_parser::parse;
289
290 #[test]
291 fn test_write_simple_message() {
292 let message = Message {
293 delims: Delims::default(),
294 segments: vec![Segment {
295 id: *b"MSH",
296 fields: vec![
297 Field::from_text("^~\\&"),
298 Field::from_text("SendingApp"),
299 Field::from_text("SendingFac"),
300 ],
301 }],
302 charsets: vec![],
303 };
304
305 let bytes = write(&message);
306 let result = String::from_utf8(bytes).unwrap();
307
308 assert!(result.starts_with("MSH|"));
309 assert!(result.ends_with("\r"));
310 }
311
312 #[test]
313 fn test_write_with_repetitions() {
314 let message = Message {
315 delims: Delims::default(),
316 segments: vec![Segment {
317 id: *b"PID",
318 fields: vec![
319 Field {
320 reps: vec![Rep::from_text("1")],
321 },
322 Field {
323 reps: vec![Rep::from_text("12345")],
324 },
325 Field {
326 reps: vec![
327 Rep {
328 comps: vec![Comp::from_text("Doe"), Comp::from_text("John")],
329 },
330 Rep {
331 comps: vec![Comp::from_text("Smith"), Comp::from_text("Jane")],
332 },
333 ],
334 },
335 ],
336 }],
337 charsets: vec![],
338 };
339
340 let bytes = write(&message);
341 let result = String::from_utf8(bytes).unwrap();
342
343 assert!(result.contains("Doe^John~Smith^Jane"));
345 }
346
347 #[test]
348 fn test_write_with_escaping() {
349 let message = Message {
350 delims: Delims::default(),
351 segments: vec![Segment {
352 id: *b"PID",
353 fields: vec![
354 Field::from_text("1"),
355 Field::from_text("test|value"), ],
357 }],
358 charsets: vec![],
359 };
360
361 let bytes = write(&message);
362 let result = String::from_utf8(bytes).unwrap();
363
364 assert!(result.contains("test\\F\\value"));
366 }
367
368 #[test]
369 fn test_write_mllp() {
370 let message = Message {
371 delims: Delims::default(),
372 segments: vec![Segment {
373 id: *b"MSH",
374 fields: vec![Field::from_text("^~\\&")],
375 }],
376 charsets: vec![],
377 };
378
379 let framed = write_mllp(&message);
380
381 assert_eq!(framed[0], hl7v2_mllp::MLLP_START);
382 assert_eq!(framed[framed.len() - 2], hl7v2_mllp::MLLP_END_1);
383 assert_eq!(framed[framed.len() - 1], hl7v2_mllp::MLLP_END_2);
384 }
385
386 #[test]
387 fn test_to_json() {
388 let message = Message {
389 delims: Delims::default(),
390 segments: vec![Segment {
391 id: *b"MSH",
392 fields: vec![Field::from_text("^~\\&"), Field::from_text("SendingApp")],
393 }],
394 charsets: vec![],
395 };
396
397 let json = to_json(&message);
398
399 assert!(json.is_object());
400 assert!(json.get("meta").is_some());
401 assert!(json.get("segments").is_some());
402
403 let meta = json.get("meta").unwrap();
404 assert!(meta.get("delims").is_some());
405 }
406
407 #[test]
408 fn test_roundtrip() {
409 let original = Message {
411 delims: Delims::default(),
412 segments: vec![
413 Segment {
414 id: *b"MSH",
415 fields: vec![
416 Field::from_text("^~\\&"),
417 Field::from_text("SendingApp"),
418 Field::from_text("SendingFac"),
419 ],
420 },
421 Segment {
422 id: *b"PID",
423 fields: vec![
424 Field::from_text("1"),
425 Field::from_text("12345"),
426 Field {
427 reps: vec![Rep {
428 comps: vec![Comp::from_text("Doe"), Comp::from_text("John")],
429 }],
430 },
431 ],
432 },
433 ],
434 charsets: vec![],
435 };
436
437 let bytes = write(&original);
439
440 let parsed = parse(&bytes).unwrap();
442
443 assert_eq!(original.segments.len(), parsed.segments.len());
445 assert_eq!(original.segments[0].id, parsed.segments[0].id);
446 assert_eq!(original.segments[1].id, parsed.segments[1].id);
447 }
448}