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() }
}
#[must_use]
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();
}
#[must_use]
pub fn is_null(&self) -> bool {
self.raw == NULL
}
#[must_use]
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 {
#[must_use]
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)
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self.subcomponents.as_slice(), [only] if only.is_null())
}
}
impl Repetition {
#[must_use]
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)
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self.components.as_slice(), [only] if only.is_null())
}
}
impl Field {
#[must_use]
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)?)
}
#[must_use]
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)
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self.repetitions.as_slice(), [only] if only.is_null())
}
}
impl Segment {
#[must_use]
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)?)
}
#[must_use]
pub fn component(&self, field: usize, component: usize) -> Option<&Component> {
self.field(field)?.component(component)
}
#[must_use]
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)
}
#[must_use]
pub fn segment(&self, name: &str) -> Option<&Segment> {
self.segments.iter().find(|s| s.name == name)
}
#[must_use]
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)?)
}
#[must_use]
pub fn header(&self) -> Option<&Segment> {
self.segments.first()
}
pub fn query(&self, path: &str) -> Result<Option<String>, Error> {
Ok(self
.query_path_mode(&Path::parse(path)?, true, true)
.into_iter()
.next())
}
pub fn query_all(&self, path: &str) -> Result<Vec<String>, Error> {
Ok(self.query_path(&Path::parse(path)?))
}
#[must_use]
pub fn query_path(&self, path: &Path) -> Vec<String> {
self.query_path_mode(path, true, false)
}
#[must_use]
pub fn query_path_raw(&self, path: &Path) -> Vec<String> {
self.query_path_mode(path, false, false)
}
fn query_path_mode(&self, path: &Path, decode: bool, stop_after_first: bool) -> Vec<String> {
let mut out = Vec::new();
match path.segment_occurrence {
Some(occurrence) => {
if let Some(segment) = self.segment_at(&path.segment, occurrence) {
self.push_from_segment(&mut out, segment, path, decode, stop_after_first);
}
}
None => {
for segment in self.segments_named(&path.segment) {
self.push_from_segment(&mut out, segment, path, decode, stop_after_first);
if stop_after_first && !out.is_empty() {
break;
}
}
}
}
out
}
fn push_from_segment(
&self,
out: &mut Vec<String>,
segment: &Segment,
path: &Path,
decode: bool,
stop_after_first: bool,
) {
let separators = &self.separators;
let Some(number) = path.field else {
out.push(write(
segment,
separators,
decode,
Segment::to_er7,
Segment::to_text,
));
return;
};
let Some(field) = segment.field(number) else {
return;
};
if segment.is_header() && number <= 2 {
out.push(field.to_er7(separators));
return;
}
if path.repetition.is_none() && path.component.is_none() {
out.push(write(
field,
separators,
decode,
Field::to_er7,
Field::to_text,
));
return;
}
match path.repetition {
Some(n) => {
if let Some(repetition) = field.repetition(n) {
self.push_below_repetition(out, repetition, path, decode);
}
}
None => {
for repetition in &field.repetitions {
self.push_below_repetition(out, repetition, path, decode);
if stop_after_first && !out.is_empty() {
break;
}
}
}
}
}
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(if decode {
subcomponent.value(separators).into_owned()
} else {
subcomponent.raw.clone()
});
}
}
#[must_use]
pub fn message_code(&self) -> Option<String> {
self.first_value("MSH-9.1")
}
#[must_use]
pub fn trigger_event(&self) -> Option<String> {
self.first_value("MSH-9.2")
}
#[must_use]
pub fn message_structure(&self) -> Option<String> {
self.first_value("MSH-9.3")
}
#[must_use]
pub fn control_id(&self) -> Option<String> {
self.first_value("MSH-10")
}
#[must_use]
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 a_missing_position_yields_no_value() {
let message = message();
assert_eq!(message.query("PID-99").unwrap(), None);
assert_eq!(message.query("PID-5.9").unwrap(), None);
assert_eq!(message.query("PID-5.1.9").unwrap(), None);
assert_eq!(message.query("ZZZ-1").unwrap(), None);
assert_eq!(message.query("OBX[9]-1").unwrap(), None);
assert!(message.query_all("PID-99").unwrap().is_empty());
assert_eq!(message.segments_named("OBX").count(), 2);
assert_eq!(message.query_all("OBX-6").unwrap(), vec!["mg/dL"]);
}
#[test]
fn msh_conveniences_are_none_when_absent() {
let absent = parse("MSH|^~\\&|LAB").unwrap();
assert_eq!(absent.message_code(), None);
assert_eq!(absent.trigger_event(), None);
assert_eq!(absent.message_structure(), None);
assert_eq!(absent.control_id(), None);
assert_eq!(absent.version(), None);
let empty = parse("MSH|^~\\&|LAB|||||||||").unwrap();
assert_eq!(empty.message_code(), None);
assert_eq!(empty.control_id(), None);
let partial = parse("MSH|^~\\&|LAB||||||ADT^A08|MSG1|P|2.3").unwrap();
assert_eq!(partial.message_code().as_deref(), Some("ADT"));
assert_eq!(partial.trigger_event().as_deref(), Some("A08"));
assert_eq!(partial.message_structure(), None);
assert_eq!(partial.version().as_deref(), Some("2.3"));
let versioned = parse("MSH|^~\\&|LAB|||||||||2.5.1^AUS^2.5.1").unwrap();
assert_eq!(versioned.version().as_deref(), Some("2.5.1"));
}
#[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");
}
}