use std::fmt;
use std::str::FromStr;
pub const PRIORITY_SYSTEM: &str =
"http://hl7.eu/fhir/health-data-api/CodeSystem/eehrxf-document-priority-category-cs";
pub const PRIORITY_VERSION: &str = "1.0.0-ballot";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Category {
PatientSummary,
ElectronicPrescription,
ElectronicDispensation,
MedicalImaging,
MedicalTestResult,
DischargeReport,
National(NationalCategory),
}
impl Category {
pub const PRIORITY: [Self; 6] = [
Self::PatientSummary,
Self::ElectronicPrescription,
Self::ElectronicDispensation,
Self::MedicalImaging,
Self::MedicalTestResult,
Self::DischargeReport,
];
#[must_use]
pub fn system(&self) -> &str {
match self {
Self::National(national) => national.system(),
_ => PRIORITY_SYSTEM,
}
}
#[must_use]
pub fn version(&self) -> Option<&str> {
match self {
Self::National(_) => None,
_ => Some(PRIORITY_VERSION),
}
}
#[must_use]
pub fn code(&self) -> &str {
match self {
Self::PatientSummary => "Patient-Summaries",
Self::ElectronicPrescription => "Electronic-Prescriptions",
Self::ElectronicDispensation => "Electronic-Dispensations",
Self::MedicalImaging => "Medical-Imaging",
Self::MedicalTestResult => "Laboratory-Reports",
Self::DischargeReport => "Discharge-Reports",
Self::National(national) => national.code(),
}
}
#[must_use]
pub fn token(&self) -> String {
format!("{}|{}", self.system(), self.code())
}
#[must_use]
pub fn priority(code: &str) -> Option<Self> {
Self::PRIORITY
.into_iter()
.find(|category| category.code() == code)
}
}
impl fmt::Display for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}|{}", self.system(), self.code())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NationalCategory {
system: String,
code: String,
}
impl NationalCategory {
pub fn new(system: &str, code: &str) -> Result<Self, CodeError> {
if system == PRIORITY_SYSTEM {
return Err(CodeError::Priority);
}
if !is_absolute_uri(system) {
return Err(CodeError::System);
}
if !is_fhir_code(code) {
return Err(CodeError::Code);
}
Ok(Self {
system: system.to_owned(),
code: code.to_owned(),
})
}
#[must_use]
pub fn system(&self) -> &str {
&self.system
}
#[must_use]
pub fn code(&self) -> &str {
&self.code
}
}
impl FromStr for NationalCategory {
type Err = CodeError;
fn from_str(token: &str) -> Result<Self, Self::Err> {
let (system, code) = token.split_once('|').ok_or(CodeError::NoSystem)?;
Self::new(system, code)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum CodeError {
#[error("a national category is written <system>|<code>")]
NoSystem,
#[error("the system of a national category is an absolute URI without `|`")]
System,
#[error("a national category names a system of its own, never {PRIORITY_SYSTEM}")]
Priority,
#[error(
"a national category code is a FHIR code: no leading, trailing or repeated whitespace, and no character below U+0020"
)]
Code,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reference<'a> {
Bare(&'a str),
Coded {
system: &'a str,
code: &'a str,
},
}
impl<'a> Reference<'a> {
#[must_use]
pub fn read(text: &'a str) -> Self {
match text.split_once('|') {
Some((system, code)) => Self::Coded { system, code },
None => Self::Bare(text),
}
}
#[must_use]
pub fn priority(self) -> Option<Category> {
match self {
Self::Bare(code)
| Self::Coded {
system: PRIORITY_SYSTEM,
code,
} => Category::priority(code),
Self::Coded { .. } => None,
}
}
#[must_use]
pub fn names(self, national: &NationalCategory) -> bool {
matches!(self, Self::Coded { system, code } if system == national.system() && code == national.code())
}
}
pub(crate) fn is_absolute_uri(system: &str) -> bool {
let Some((scheme, rest)) = system.split_once(':') else {
return false;
};
let mut letters = scheme.chars();
let scheme_formed = letters
.next()
.is_some_and(|first| first.is_ascii_alphabetic())
&& letters.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'));
scheme_formed
&& !rest.is_empty()
&& !system
.chars()
.any(|c| c == '|' || c.is_whitespace() || c.is_control())
}
pub(crate) fn is_fhir_code(code: &str) -> bool {
!code.is_empty()
&& !code.starts_with(' ')
&& !code.ends_with(' ')
&& !code.contains(" ")
&& !code
.chars()
.any(|c| c < ' ' || (c.is_whitespace() && c != ' '))
}