use crate::{Error, Message};
use std::fmt::Write as _;
pub trait FromHl7: Sized {
fn from_hl7(message: &Message) -> Result<Self, Error>;
}
pub trait ToHl7 {
fn to_hl7(&self, message: &mut Message) -> Result<(), Error>;
}
pub trait FromHl7Value: Sized {
fn from_hl7_value(message: &Message, path: &str) -> Result<Self, Error>;
}
pub trait ToHl7Value {
fn to_hl7_value(&self, message: &mut Message, path: &str) -> Result<(), Error>;
}
pub trait FromHl7Text: Sized {
fn from_hl7_text(text: &str, path: &str) -> Result<Self, Error>;
}
pub trait ToHl7Text {
fn to_hl7_text(&self) -> Option<String>;
}
impl FromHl7Text for String {
fn from_hl7_text(text: &str, _path: &str) -> Result<String, Error> {
Ok(text.to_string())
}
}
impl ToHl7Text for String {
fn to_hl7_text(&self) -> Option<String> {
Some(self.clone())
}
}
impl ToHl7Text for str {
fn to_hl7_text(&self) -> Option<String> {
Some(self.to_string())
}
}
impl FromHl7Text for bool {
fn from_hl7_text(text: &str, path: &str) -> Result<bool, Error> {
match text.trim() {
"Y" | "y" | "1" | "true" | "TRUE" => Ok(true),
"N" | "n" | "0" | "false" | "FALSE" => Ok(false),
other => Err(Error::BadValue {
path: path.to_string(),
expected: "Y or N".to_string(),
found: other.to_string(),
}),
}
}
}
impl ToHl7Text for bool {
fn to_hl7_text(&self) -> Option<String> {
Some(if *self { "Y" } else { "N" }.to_string())
}
}
macro_rules! numbers {
($($type:ty),*) => {$(
impl FromHl7Text for $type {
fn from_hl7_text(text: &str, path: &str) -> Result<$type, Error> {
text.trim().parse().map_err(|_| Error::BadValue {
path: path.to_string(),
expected: stringify!($type).to_string(),
found: text.to_string(),
})
}
}
impl ToHl7Text for $type {
fn to_hl7_text(&self) -> Option<String> {
Some(self.to_string())
}
}
)*};
}
numbers!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
macro_rules! scalars {
($($type:ty),*) => {$(
impl FromHl7Value for $type {
fn from_hl7_value(message: &Message, path: &str) -> Result<$type, Error> {
match message.get(path)?.filter(|text| !text.is_empty()) {
Some(text) => <$type as FromHl7Text>::from_hl7_text(&text, path),
None => Err(Error::MissingField { path: path.to_string() }),
}
}
}
impl ToHl7Value for $type {
fn to_hl7_value(&self, message: &mut Message, path: &str) -> Result<(), Error> {
match <$type as ToHl7Text>::to_hl7_text(self) {
Some(text) => message.set(path, &text),
None => message.clear(path),
}
}
}
impl FromHl7Value for Option<$type> {
fn from_hl7_value(message: &Message, path: &str) -> Result<Option<$type>, Error> {
match message.get(path)?.filter(|text| !text.is_empty()) {
Some(text) => <$type as FromHl7Text>::from_hl7_text(&text, path).map(Some),
None => Ok(None),
}
}
}
impl ToHl7Value for Option<$type> {
fn to_hl7_value(&self, message: &mut Message, path: &str) -> Result<(), Error> {
match self {
Some(value) => value.to_hl7_value(message, path),
None => message.clear(path),
}
}
}
impl FromHl7Value for Vec<$type> {
fn from_hl7_value(message: &Message, path: &str) -> Result<Vec<$type>, Error> {
message
.repetitions(path)?
.iter()
.filter(|text| !text.is_empty())
.map(|text| <$type as FromHl7Text>::from_hl7_text(text, path))
.collect()
}
}
impl ToHl7Value for Vec<$type> {
fn to_hl7_value(&self, message: &mut Message, path: &str) -> Result<(), Error> {
for (index, value) in self.iter().enumerate() {
let path = crate::typed::with_repetition(path, index + 1)?;
value.to_hl7_value(message, &path)?;
}
Ok(())
}
}
)*};
}
scalars!(
String, bool, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
pub(crate) fn with_repetition(path: &str, repetition: usize) -> Result<String, Error> {
let parsed = er7::Path::parse(path)?;
let field = parsed
.field
.ok_or_else(|| Error::UnwritablePath(format!("{path}: a repeating value needs a field")))?;
let mut out = parsed.segment.clone();
if let Some(occurrence) = parsed.segment_occurrence {
let _ = write!(out, "[{occurrence}]");
}
let _ = write!(out, "-{field}[{repetition}]");
if let Some(component) = parsed.component {
let _ = write!(out, ".{component}");
if let Some(subcomponent) = parsed.subcomponent {
let _ = write!(out, ".{subcomponent}");
}
}
Ok(out)
}
#[derive(Debug, Clone)]
pub struct Raw {
message: Message,
}
impl Raw {
#[must_use]
pub fn new(message: Message) -> Raw {
Raw { message }
}
#[must_use]
pub fn message(&self) -> &Message {
&self.message
}
pub fn get(&self, path: &str) -> Result<Option<String>, Error> {
self.message.get(path)
}
pub fn get_all(&self, path: &str) -> Result<Vec<String>, Error> {
self.message.get_all(path)
}
#[must_use]
pub fn tree(&self) -> crate::generic::Node {
self.message.tree()
}
#[must_use]
pub fn to_er7(&self) -> String {
self.message.to_er7()
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEXT: &str = "MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1||241900~99||SMITH^JOHN|||M";
#[derive(Debug)]
struct Patient {
id: String,
all_ids: Vec<String>,
sequence: u32,
middle: Option<String>,
raw: Raw,
}
impl FromHl7 for Patient {
fn from_hl7(message: &Message) -> Result<Patient, Error> {
Ok(Patient {
id: FromHl7Value::from_hl7_value(message, "PID-3.1")?,
all_ids: FromHl7Value::from_hl7_value(message, "PID-3.1")?,
sequence: FromHl7Value::from_hl7_value(message, "PID-1")?,
middle: FromHl7Value::from_hl7_value(message, "PID-5.3")?,
raw: Raw::new(message.clone()),
})
}
}
#[test]
fn reads_scalars_repetitions_and_absences() {
let patient: Patient = crate::parse(TEXT).unwrap().decode().unwrap();
assert_eq!(patient.id, "241900");
assert_eq!(patient.all_ids, ["241900", "99"]);
assert_eq!(patient.sequence, 1);
assert_eq!(patient.middle, None);
assert_eq!(patient.raw.get("PID-8").unwrap().as_deref(), Some("M"));
}
#[test]
fn a_required_field_that_is_absent_is_an_error() {
let message = crate::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1").unwrap();
let error = Patient::from_hl7(&message).unwrap_err();
assert!(
matches!(&error, Error::MissingField { path } if path == "PID-3.1"),
"{error}"
);
}
#[test]
fn a_value_of_the_wrong_shape_names_the_path() {
let message = crate::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|x||9").unwrap();
let error = Patient::from_hl7(&message).unwrap_err();
assert!(
matches!(&error, Error::BadValue { path, .. } if path == "PID-1"),
"{error}"
);
assert!(error.to_string().contains("u32"), "{error}");
}
#[test]
fn writes_scalars_options_and_repetitions_back() {
let mut message = crate::parse("MSH|^~\\&|A||||1||ADT^A01|1|P|2.5\rPID|1").unwrap();
"SMITH"
.to_string()
.to_hl7_value(&mut message, "PID-5.1")
.unwrap();
7u32.to_hl7_value(&mut message, "PID-1").unwrap();
true.to_hl7_value(&mut message, "PID-30").unwrap();
None::<String>.to_hl7_value(&mut message, "PID-8").unwrap();
vec!["A".to_string(), "B".to_string()]
.to_hl7_value(&mut message, "PID-3.1")
.unwrap();
assert_eq!(message.get("PID-5.1").unwrap().as_deref(), Some("SMITH"));
assert_eq!(message.get("PID-1").unwrap().as_deref(), Some("7"));
assert_eq!(message.get("PID-30").unwrap().as_deref(), Some("Y"));
assert_eq!(message.get_all("PID-3.1").unwrap(), ["A", "B"]);
}
#[test]
fn rewrites_a_path_to_address_one_repetition() {
assert_eq!(with_repetition("PID-3.1", 2).unwrap(), "PID-3[2].1");
assert_eq!(with_repetition("OBX[2]-5", 1).unwrap(), "OBX[2]-5[1]");
assert!(with_repetition("PID", 1).is_err());
}
#[test]
fn booleans_read_the_spellings_senders_use() {
for (text, expected) in [("Y", true), ("n", false), ("1", true), ("0", false)] {
assert_eq!(bool::from_hl7_text(text, "PID-30").unwrap(), expected);
}
assert!(bool::from_hl7_text("maybe", "PID-30").is_err());
}
}