use crate::escape::unescape;
use crate::{Error, Path, Separators};
use std::borrow::Cow;
pub const NULL: &str = "\"\"";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub separators: Separators,
pub segments: Vec<Segment>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub name: String,
pub fields: Vec<Field>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Field {
pub repetitions: Vec<Repetition>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Repetition {
pub components: Vec<Component>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Component {
pub subcomponents: Vec<Subcomponent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Subcomponent {
pub raw: String,
}
impl Subcomponent {
pub fn new(raw: impl Into<String>) -> Subcomponent {
Subcomponent { raw: raw.into() }
}
pub fn value(&self, separators: &Separators) -> Cow<'_, str> {
if self.is_null() {
return Cow::Borrowed("");
}
unescape(&self.raw, separators)
}
pub fn set(&mut self, value: &str, separators: &Separators) {
self.raw = crate::escape::escape(value, separators).into_owned();
}
pub fn is_null(&self) -> bool {
self.raw == NULL
}
pub fn is_empty(&self) -> bool {
self.raw.is_empty()
}
}
impl From<&str> for Subcomponent {
fn from(raw: &str) -> Subcomponent {
Subcomponent::new(raw)
}
}
impl Component {
pub fn subcomponent(&self, n: usize) -> Option<&Subcomponent> {
self.subcomponents.get(n.checked_sub(1)?)
}
pub fn subcomponent_mut(&mut self, n: usize) -> Option<&mut Subcomponent> {
self.subcomponents.get_mut(n.checked_sub(1)?)
}
pub fn is_empty(&self) -> bool {
self.subcomponents.iter().all(Subcomponent::is_empty)
}
pub fn is_null(&self) -> bool {
matches!(self.subcomponents.as_slice(), [only] if only.is_null())
}
}
impl Repetition {
pub fn component(&self, n: usize) -> Option<&Component> {
self.components.get(n.checked_sub(1)?)
}
pub fn component_mut(&mut self, n: usize) -> Option<&mut Component> {
self.components.get_mut(n.checked_sub(1)?)
}
pub fn is_empty(&self) -> bool {
self.components.iter().all(Component::is_empty)
}
pub fn is_null(&self) -> bool {
matches!(self.components.as_slice(), [only] if only.is_null())
}
}
impl Field {
pub fn repetition(&self, n: usize) -> Option<&Repetition> {
self.repetitions.get(n.checked_sub(1)?)
}
pub fn repetition_mut(&mut self, n: usize) -> Option<&mut Repetition> {
self.repetitions.get_mut(n.checked_sub(1)?)
}
pub fn component(&self, n: usize) -> Option<&Component> {
self.repetitions.first()?.component(n)
}
pub fn is_empty(&self) -> bool {
self.repetitions.iter().all(Repetition::is_empty)
}
pub fn is_null(&self) -> bool {
matches!(self.repetitions.as_slice(), [only] if only.is_null())
}
}
impl Segment {
pub fn field(&self, n: usize) -> Option<&Field> {
self.fields.get(n.checked_sub(1)?)
}
pub fn field_mut(&mut self, n: usize) -> Option<&mut Field> {
self.fields.get_mut(n.checked_sub(1)?)
}
pub fn component(&self, field: usize, component: usize) -> Option<&Component> {
self.field(field)?.component(component)
}
pub fn is_header(&self) -> bool {
is_header_name(&self.name)
}
}
pub(crate) fn is_header_name(name: &str) -> bool {
matches!(name, "MSH" | "FHS" | "BHS")
}
fn write<T>(
node: &T,
separators: &Separators,
decode: bool,
to_er7: fn(&T, &Separators) -> String,
to_text: fn(&T, &Separators) -> String,
) -> String {
if decode {
to_text(node, separators)
} else {
to_er7(node, separators)
}
}
impl Message {
pub fn segments_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Segment> {
self.segments.iter().filter(move |s| s.name == name)
}
pub fn segment(&self, name: &str) -> Option<&Segment> {
self.segments.iter().find(|s| s.name == name)
}
pub fn segment_at(&self, name: &str, occurrence: usize) -> Option<&Segment> {
self.segments
.iter()
.filter(|s| s.name == name)
.nth(occurrence.checked_sub(1)?)
}
pub fn segment_at_mut(&mut self, name: &str, occurrence: usize) -> Option<&mut Segment> {
self.segments
.iter_mut()
.filter(|s| s.name == name)
.nth(occurrence.checked_sub(1)?)
}
pub fn header(&self) -> Option<&Segment> {
self.segments.first()
}
pub fn query(&self, path: &str) -> Result<Option<String>, Error> {
Ok(self.query_path(&Path::parse(path)?).into_iter().next())
}
pub fn query_all(&self, path: &str) -> Result<Vec<String>, Error> {
Ok(self.query_path(&Path::parse(path)?))
}
pub fn query_path(&self, path: &Path) -> Vec<String> {
self.query_path_mode(path, true)
}
pub fn query_path_raw(&self, path: &Path) -> Vec<String> {
self.query_path_mode(path, false)
}
fn query_path_mode(&self, path: &Path, decode: bool) -> Vec<String> {
let separators = &self.separators;
let mut out = Vec::new();
let segments: Vec<&Segment> = match path.segment_occurrence {
Some(occurrence) => self
.segment_at(&path.segment, occurrence)
.into_iter()
.collect(),
None => self.segments_named(&path.segment).collect(),
};
for segment in segments {
let Some(number) = path.field else {
out.push(write(
segment,
separators,
decode,
Segment::to_er7,
Segment::to_text,
));
continue;
};
let Some(field) = segment.field(number) else {
continue;
};
if segment.is_header() && number <= 2 {
out.push(field.to_er7(separators));
continue;
}
if path.repetition.is_none() && path.component.is_none() {
out.push(write(
field,
separators,
decode,
Field::to_er7,
Field::to_text,
));
continue;
}
let repetitions: Vec<&Repetition> = match path.repetition {
Some(n) => field.repetition(n).into_iter().collect(),
None => field.repetitions.iter().collect(),
};
for repetition in repetitions {
self.push_below_repetition(&mut out, repetition, path, decode);
}
}
out
}
fn push_below_repetition(
&self,
out: &mut Vec<String>,
repetition: &Repetition,
path: &Path,
decode: bool,
) {
let separators = &self.separators;
let Some(number) = path.component else {
out.push(write(
repetition,
separators,
decode,
Repetition::to_er7,
Repetition::to_text,
));
return;
};
let Some(component) = repetition.component(number) else {
return;
};
let Some(number) = path.subcomponent else {
out.push(write(
component,
separators,
decode,
Component::to_er7,
Component::to_text,
));
return;
};
if let Some(subcomponent) = component.subcomponent(number) {
out.push(match decode {
true => subcomponent.value(separators).into_owned(),
false => subcomponent.raw.clone(),
});
}
}
pub fn message_code(&self) -> Option<String> {
self.first_value("MSH-9.1")
}
pub fn trigger_event(&self) -> Option<String> {
self.first_value("MSH-9.2")
}
pub fn message_structure(&self) -> Option<String> {
self.first_value("MSH-9.3")
}
pub fn control_id(&self) -> Option<String> {
self.first_value("MSH-10")
}
pub fn version(&self) -> Option<String> {
self.first_value("MSH-12.1")
}
fn first_value(&self, path: &str) -> Option<String> {
let path = Path::parse(path).expect("path literal is well-formed");
self.query_path(&path)
.into_iter()
.next()
.filter(|value| !value.is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse;
const ADT: &str = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ADT^A08^ADT_A01|MSG9|P|2.5\r\
PID|1||12345^^^ACME&1.2.3&ISO^MR||SMITH^JOHN^Q||19800101|M|||||\
555-1111~555-2222\r\
OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL\r\
OBX|2|ST|X^Note^L||\"\"";
fn message() -> Message {
parse(ADT).unwrap()
}
#[test]
fn reads_the_msh_conveniences() {
let message = message();
assert_eq!(message.message_code().as_deref(), Some("ADT"));
assert_eq!(message.trigger_event().as_deref(), Some("A08"));
assert_eq!(message.message_structure().as_deref(), Some("ADT_A01"));
assert_eq!(message.control_id().as_deref(), Some("MSG9"));
assert_eq!(message.version().as_deref(), Some("2.5"));
}
#[test]
fn queries_each_depth() {
let message = message();
assert_eq!(
message.query("PID-5").unwrap().as_deref(),
Some("SMITH^JOHN^Q")
);
assert_eq!(message.query("PID-5.1").unwrap().as_deref(), Some("SMITH"));
assert_eq!(
message.query("PID-3.4.2").unwrap().as_deref(),
Some("1.2.3")
);
assert_eq!(message.query("PID-99").unwrap(), None);
assert_eq!(message.query("ZZZ-1").unwrap(), None);
}
#[test]
fn queries_repetitions_and_occurrences() {
let message = message();
assert_eq!(
message.query_all("PID-13").unwrap(),
vec!["555-1111~555-2222"]
);
assert_eq!(message.query_all("PID-13[2].1").unwrap(), vec!["555-2222"]);
assert_eq!(message.query_all("OBX-5").unwrap(), vec!["187", ""]);
assert_eq!(
message.query_all("OBX[1]-3.2").unwrap(),
vec!["Cholesterol"]
);
}
#[test]
fn queries_header_delimiters_literally() {
let message = message();
assert_eq!(message.query("MSH-1").unwrap().as_deref(), Some("|"));
assert_eq!(message.query("MSH-2").unwrap().as_deref(), Some("^~\\&"));
}
#[test]
fn distinguishes_absent_empty_and_null() {
let message = message();
let pid = message.segment("PID").unwrap();
assert!(pid.field(2).unwrap().is_empty());
assert!(!pid.field(2).unwrap().is_null());
assert!(pid.field(99).is_none());
let obx = message.segment_at("OBX", 2).unwrap();
assert!(obx.field(5).unwrap().is_null());
assert!(!obx.field(5).unwrap().is_empty());
}
#[test]
fn sets_a_value_with_encoding() {
let separators = Separators::default();
let mut subcomponent = Subcomponent::default();
subcomponent.set("Smith & Jones", &separators);
assert_eq!(subcomponent.raw, r"Smith \T\ Jones");
assert_eq!(subcomponent.value(&separators), "Smith & Jones");
}
}