use crate::dictionary::Item;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Layout {
Segment(usize),
Group {
name: String,
items: Vec<Layout>,
},
}
impl Layout {
pub fn segment_indices(&self, out: &mut Vec<usize>) {
match self {
Layout::Segment(index) => out.push(*index),
Layout::Group { items, .. } => {
for item in items {
item.segment_indices(out);
}
}
}
}
}
#[must_use]
pub fn group(items: &[Item], segments: &[&str]) -> Option<Vec<Layout>> {
let mut position = 0;
let mut out = Vec::new();
if match_items(items, segments, &mut position, &mut out) && position == segments.len() {
Some(out)
} else {
None
}
}
fn match_items(
items: &[Item],
segments: &[&str],
position: &mut usize,
out: &mut Vec<Layout>,
) -> bool {
for item in items {
let mut occurrences = 0;
loop {
let before = *position;
if *position < segments.len() && item.can_start(segments[*position]) {
match item {
Item::Segment { .. } => {
out.push(Layout::Segment(*position));
*position += 1;
}
Item::Group { name, items, .. } => {
let mut contents = Vec::new();
if !match_items(items, segments, position, &mut contents) {
return false;
}
out.push(Layout::Group {
name: name.clone(),
items: contents,
});
}
}
occurrences += 1;
}
if *position == before || !item.repeats() {
break;
}
}
if occurrences == 0 && item.required() {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Version;
fn layout(structure: &str, segments: &[&str]) -> Option<Vec<Layout>> {
let dictionary = Version::V2_5.dictionary();
group(dictionary.structure(structure).unwrap(), segments)
}
#[test]
fn groups_a_message_that_fits() {
let found = layout("ORU_R01", &["MSH", "PID", "OBR", "OBX", "OBX"]).unwrap();
assert_eq!(found[0], Layout::Segment(0));
let Layout::Group { name, items } = &found[1] else {
panic!("expected a group, got {:?}", found[1]);
};
assert_eq!(name, "PATIENT_RESULT");
assert_eq!(items.len(), 2);
let mut indices = Vec::new();
found[1].segment_indices(&mut indices);
assert_eq!(indices, [1, 2, 3, 4]);
}
#[test]
fn repeats_a_group_once_per_occurrence() {
let found = layout("ORU_R01", &["MSH", "PID", "OBR", "OBX", "OBR", "OBX"]).unwrap();
let Layout::Group { items, .. } = &found[1] else {
panic!("expected PATIENT_RESULT");
};
assert_eq!(items.len(), 3);
}
#[test]
fn refuses_a_message_that_does_not_fit() {
assert_eq!(layout("ORU_R01", &["MSH", "PID", "OBR", "ZZZ"]), None);
assert_eq!(layout("ACK", &["MSH"]), None);
assert_eq!(layout("ACK", &["MSA", "MSH"]), None);
}
#[test]
fn matches_the_flat_structures_too() {
let found = layout("ACK", &["MSH", "MSA", "ERR", "ERR"]).unwrap();
assert_eq!(
found,
[
Layout::Segment(0),
Layout::Segment(1),
Layout::Segment(2),
Layout::Segment(3)
]
);
}
}