use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Error)]
pub enum PathError {
#[error("Invalid path format: {0}")]
InvalidFormat(String),
#[error("Invalid segment ID: {0}")]
InvalidSegmentId(String),
#[error("Invalid field number: {0}")]
InvalidFieldNumber(String),
#[error("Invalid component number: {0}")]
InvalidComponentNumber(String),
#[error("Invalid repetition index: {0}")]
InvalidRepetitionIndex(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Path {
pub segment: String,
pub field: usize,
pub repetition: Option<usize>,
pub component: Option<usize>,
pub subcomponent: Option<usize>,
}
impl Path {
pub fn new(segment: &str, field: usize) -> Self {
Self {
segment: segment.to_uppercase(),
field,
repetition: None,
component: None,
subcomponent: None,
}
}
pub fn with_repetition(mut self, rep: usize) -> Self {
self.repetition = Some(rep);
self
}
pub fn with_component(mut self, comp: usize) -> Self {
self.component = Some(comp);
self
}
pub fn with_subcomponent(mut self, sub: usize) -> Self {
self.subcomponent = Some(sub);
self
}
pub fn to_path_string(&self) -> String {
let mut result = self.segment.clone();
result.push('.');
result.push_str(&self.field.to_string());
if let Some(rep) = self.repetition {
result.push('[');
result.push_str(&rep.to_string());
result.push(']');
}
if let Some(comp) = self.component {
result.push('.');
result.push_str(&comp.to_string());
}
if let Some(sub) = self.subcomponent {
result.push('.');
result.push_str(&sub.to_string());
}
result
}
pub fn is_msh(&self) -> bool {
self.segment == "MSH"
}
pub fn msh_adjusted_field(&self) -> usize {
if self.field <= 2 {
self.field.saturating_sub(1) } else {
self.field.saturating_sub(2) }
}
}
impl std::fmt::Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_path_string())
}
}
pub fn parse_path(s: &str) -> Result<Path, PathError> {
let s = s.trim();
if s.is_empty() {
return Err(PathError::InvalidFormat("Path cannot be empty".to_string()));
}
let mut parts = s.split('.');
let segment_part = parts.next().unwrap_or_default();
let field_part = parts.next().ok_or_else(|| {
PathError::InvalidFormat(format!("Path must have at least SEGMENT.FIELD, got: {s}"))
})?;
let component_part = parts.next();
let subcomponent_part = parts.next();
let segment = segment_part.to_uppercase();
if segment.len() != 3
|| !segment.starts_with(|c: char| c.is_ascii_alphabetic())
|| !segment.chars().all(|c| c.is_ascii_alphanumeric())
{
return Err(PathError::InvalidSegmentId(segment));
}
let (field, repetition) = parse_field_part(field_part)?;
let mut path = Path::new(&segment, field);
if let Some(rep) = repetition {
path = path.with_repetition(rep);
}
if let Some(component_part) = component_part {
let comp = component_part
.parse::<usize>()
.map_err(|_parse_err| PathError::InvalidComponentNumber(component_part.to_string()))?;
if comp == 0 {
return Err(PathError::InvalidComponentNumber(
"Component must be >= 1".to_string(),
));
}
path = path.with_component(comp);
}
if let Some(subcomponent_part) = subcomponent_part {
let sub = subcomponent_part.parse::<usize>().map_err(|_parse_err| {
PathError::InvalidComponentNumber(subcomponent_part.to_string())
})?;
if sub == 0 {
return Err(PathError::InvalidComponentNumber(
"Subcomponent must be >= 1".to_string(),
));
}
path = path.with_subcomponent(sub);
}
Ok(path)
}
fn parse_field_part(s: &str) -> Result<(usize, Option<usize>), PathError> {
if s.contains('[') {
let stripped = s.strip_suffix(']').ok_or_else(|| {
PathError::InvalidFormat(format!("Invalid field format, missing ']': {s}"))
})?;
let Some((field_str, rep_str)) = stripped.split_once('[') else {
return Err(PathError::InvalidFormat(format!(
"Invalid field format, missing '[': {s}"
)));
};
let field = field_str
.parse::<usize>()
.map_err(|_parse_err| PathError::InvalidFieldNumber(field_str.to_string()))?;
if field == 0 {
return Err(PathError::InvalidFieldNumber(
"Field must be >= 1".to_string(),
));
}
let rep = rep_str
.parse::<usize>()
.map_err(|_parse_err| PathError::InvalidRepetitionIndex(rep_str.to_string()))?;
if rep == 0 {
return Err(PathError::InvalidRepetitionIndex(
"Repetition must be >= 1".to_string(),
));
}
Ok((field, Some(rep)))
} else {
let field = s
.parse::<usize>()
.map_err(|_parse_err| PathError::InvalidFieldNumber(s.to_string()))?;
if field == 0 {
return Err(PathError::InvalidFieldNumber(
"Field must be >= 1".to_string(),
));
}
Ok((field, None))
}
}