use std::collections::BTreeMap;
use serde::Deserialize;
use crate::category::{Category, is_absolute_uri, is_fhir_code};
use crate::classify::Classification;
use crate::map::CategoryMap;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Label {
system: String,
version: Option<String>,
code: String,
}
impl Label {
pub fn new(system: &str, code: &str) -> Result<Self, LabelCodeError> {
if !is_absolute_uri(system) {
return Err(LabelCodeError::System);
}
if !is_fhir_code(code) {
return Err(LabelCodeError::Code);
}
Ok(Self {
system: system.to_owned(),
version: None,
code: code.to_owned(),
})
}
#[must_use]
pub fn of(category: &Category) -> Self {
Self {
system: category.system().to_owned(),
version: category.version().map(str::to_owned),
code: category.code().to_owned(),
}
}
#[must_use]
pub fn system(&self) -> &str {
&self.system
}
#[must_use]
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
#[must_use]
pub fn code(&self) -> &str {
&self.code
}
#[must_use]
pub fn token(&self) -> String {
format!("{}|{}", self.system, self.code)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DeclaredLabel {
pub system: String,
pub code: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum LabelCodeError {
#[error("the system of a label is an absolute URI without `|`")]
System,
#[error(
"a label code is a FHIR code: no leading, trailing or repeated whitespace, and no character below U+0020"
)]
Code,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum LabelError {
#[error("origin_system is no absolute URI without `|`")]
OriginSystem,
#[error("categories key {code:?} is neither a priority category nor a declared national one")]
UnknownCategory {
code: String,
},
#[error("categories key {code:?} names a category another key names too")]
CategoryTwice {
code: String,
},
#[error("an origins key is empty or holds a control character")]
OriginKey,
#[error("{key} declares no label; name at least one")]
Empty {
key: String,
},
#[error("{key} declares a label that is refused")]
Label {
key: String,
#[source]
source: LabelCodeError,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LabelPolicy {
origin_system: Option<String>,
categories: BTreeMap<Category, Vec<Label>>,
origins: BTreeMap<String, Vec<Label>>,
known: Vec<Category>,
}
impl Default for LabelPolicy {
fn default() -> Self {
Self {
origin_system: None,
categories: BTreeMap::new(),
origins: BTreeMap::new(),
known: Category::PRIORITY.to_vec(),
}
}
}
impl LabelPolicy {
pub fn declare(
origin_system: Option<&str>,
categories: &BTreeMap<String, Vec<DeclaredLabel>>,
origins: &BTreeMap<String, Vec<DeclaredLabel>>,
map: &CategoryMap,
) -> Result<Self, LabelError> {
if origin_system.is_some_and(|system| !is_absolute_uri(system)) {
return Err(LabelError::OriginSystem);
}
let mut declared = BTreeMap::new();
for (code, labels) in categories {
let category = map
.category(code)
.ok_or_else(|| LabelError::UnknownCategory { code: code.clone() })?;
let labels = read(&format!("categories.{code}"), labels)?;
if declared.insert(category, labels).is_some() {
return Err(LabelError::CategoryTwice { code: code.clone() });
}
}
let origins = origins
.iter()
.map(|(endpoint, labels)| {
if endpoint.is_empty() || endpoint.chars().any(char::is_control) {
return Err(LabelError::OriginKey);
}
Ok((
endpoint.clone(),
read(&format!("origins.{endpoint}"), labels)?,
))
})
.collect::<Result<BTreeMap<_, _>, LabelError>>()?;
Ok(Self {
origin_system: origin_system.map(str::to_owned),
categories: declared,
origins,
known: map.categories().collect(),
})
}
pub fn origins(&self) -> impl Iterator<Item = &str> {
self.origins.keys().map(String::as_str)
}
#[must_use]
pub fn label<'a>(
&self,
classified: &Classification,
origins: impl IntoIterator<Item = &'a str>,
) -> Labels {
let mut categories = Vec::new();
let reached: Vec<&Category> = if classified.unclassified().is_some() {
self.known.iter().collect()
} else {
classified.categories().keys().collect()
};
for category in reached {
match self.categories.get(category) {
Some(labels) => extend(&mut categories, labels.iter().cloned()),
None => extend(&mut categories, [Label::of(category)]),
}
}
let mut labelled: Vec<(String, Vec<Label>)> = Vec::new();
for endpoint in origins {
if labelled.iter().any(|(seen, _)| seen == endpoint) {
continue;
}
let labels = match (self.origins.get(endpoint), &self.origin_system) {
(Some(labels), _) => labels.clone(),
(None, Some(system)) => vec![Label {
system: system.clone(),
version: None,
code: endpoint.to_owned(),
}],
(None, None) => Vec::new(),
};
labelled.push((endpoint.to_owned(), labels));
}
Labels {
categories,
origins: labelled,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Labels {
categories: Vec<Label>,
origins: Vec<(String, Vec<Label>)>,
}
impl Labels {
#[must_use]
pub fn categories(&self) -> &[Label] {
&self.categories
}
#[must_use]
pub fn origin(&self, endpoint: &str) -> &[Label] {
self.origins
.iter()
.find(|(named, _)| named == endpoint)
.map_or(&[], |(_, labels)| labels.as_slice())
}
#[must_use]
pub fn all(&self) -> Vec<Label> {
let mut all = self.categories.clone();
for (_, labels) in &self.origins {
extend(&mut all, labels.iter().cloned());
}
all
}
}
fn read(key: &str, declared: &[DeclaredLabel]) -> Result<Vec<Label>, LabelError> {
if declared.is_empty() {
return Err(LabelError::Empty {
key: key.to_owned(),
});
}
let mut labels = Vec::new();
for label in declared {
let label = Label::new(&label.system, &label.code).map_err(|source| LabelError::Label {
key: key.to_owned(),
source,
})?;
extend(&mut labels, [label]);
}
Ok(labels)
}
fn extend(kept: &mut Vec<Label>, labels: impl IntoIterator<Item = Label>) {
for label in labels {
if !kept.contains(&label) {
kept.push(label);
}
}
}