1use std::collections::BTreeMap;
35
36use serde::Deserialize;
37
38use crate::category::{Category, is_absolute_uri, is_fhir_code};
39use crate::classify::Classification;
40use crate::map::CategoryMap;
41
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct Label {
46 system: String,
47 version: Option<String>,
48 code: String,
49}
50
51impl Label {
52 pub fn new(system: &str, code: &str) -> Result<Self, LabelCodeError> {
60 if !is_absolute_uri(system) {
61 return Err(LabelCodeError::System);
62 }
63 if !is_fhir_code(code) {
64 return Err(LabelCodeError::Code);
65 }
66 Ok(Self {
67 system: system.to_owned(),
68 version: None,
69 code: code.to_owned(),
70 })
71 }
72
73 #[must_use]
76 pub fn of(category: &Category) -> Self {
77 Self {
78 system: category.system().to_owned(),
79 version: category.version().map(str::to_owned),
80 code: category.code().to_owned(),
81 }
82 }
83
84 #[must_use]
86 pub fn system(&self) -> &str {
87 &self.system
88 }
89
90 #[must_use]
92 pub fn version(&self) -> Option<&str> {
93 self.version.as_deref()
94 }
95
96 #[must_use]
98 pub fn code(&self) -> &str {
99 &self.code
100 }
101
102 #[must_use]
105 pub fn token(&self) -> String {
106 format!("{}|{}", self.system, self.code)
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct DeclaredLabel {
114 pub system: String,
116 pub code: String,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
122#[non_exhaustive]
123pub enum LabelCodeError {
124 #[error("the system of a label is an absolute URI without `|`")]
126 System,
127 #[error(
130 "a label code is a FHIR code: no leading, trailing or repeated whitespace, and no character below U+0020"
131 )]
132 Code,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
137#[non_exhaustive]
138pub enum LabelError {
139 #[error("origin_system is no absolute URI without `|`")]
141 OriginSystem,
142 #[error("categories key {code:?} is neither a priority category nor a declared national one")]
145 UnknownCategory {
146 code: String,
148 },
149 #[error("categories key {code:?} names a category another key names too")]
151 CategoryTwice {
152 code: String,
154 },
155 #[error("an origins key is empty or holds a control character")]
157 OriginKey,
158 #[error("{key} declares no label; name at least one")]
160 Empty {
161 key: String,
163 },
164 #[error("{key} declares a label that is refused")]
166 Label {
167 key: String,
169 #[source]
171 source: LabelCodeError,
172 },
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct LabelPolicy {
178 origin_system: Option<String>,
179 categories: BTreeMap<Category, Vec<Label>>,
180 origins: BTreeMap<String, Vec<Label>>,
181 known: Vec<Category>,
182}
183
184impl Default for LabelPolicy {
185 fn default() -> Self {
186 Self {
187 origin_system: None,
188 categories: BTreeMap::new(),
189 origins: BTreeMap::new(),
190 known: Category::PRIORITY.to_vec(),
191 }
192 }
193}
194
195impl LabelPolicy {
196 pub fn declare(
208 origin_system: Option<&str>,
209 categories: &BTreeMap<String, Vec<DeclaredLabel>>,
210 origins: &BTreeMap<String, Vec<DeclaredLabel>>,
211 map: &CategoryMap,
212 ) -> Result<Self, LabelError> {
213 if origin_system.is_some_and(|system| !is_absolute_uri(system)) {
214 return Err(LabelError::OriginSystem);
215 }
216 let mut declared = BTreeMap::new();
217 for (code, labels) in categories {
218 let category = map
219 .category(code)
220 .ok_or_else(|| LabelError::UnknownCategory { code: code.clone() })?;
221 let labels = read(&format!("categories.{code}"), labels)?;
222 if declared.insert(category, labels).is_some() {
223 return Err(LabelError::CategoryTwice { code: code.clone() });
224 }
225 }
226 let origins = origins
227 .iter()
228 .map(|(endpoint, labels)| {
229 if endpoint.is_empty() || endpoint.chars().any(char::is_control) {
230 return Err(LabelError::OriginKey);
231 }
232 Ok((
233 endpoint.clone(),
234 read(&format!("origins.{endpoint}"), labels)?,
235 ))
236 })
237 .collect::<Result<BTreeMap<_, _>, LabelError>>()?;
238 Ok(Self {
239 origin_system: origin_system.map(str::to_owned),
240 categories: declared,
241 origins,
242 known: map.categories().collect(),
243 })
244 }
245
246 pub fn origins(&self) -> impl Iterator<Item = &str> {
248 self.origins.keys().map(String::as_str)
249 }
250
251 #[must_use]
254 pub fn label<'a>(
255 &self,
256 classified: &Classification,
257 origins: impl IntoIterator<Item = &'a str>,
258 ) -> Labels {
259 let mut categories = Vec::new();
260 let reached: Vec<&Category> = if classified.unclassified().is_some() {
261 self.known.iter().collect()
262 } else {
263 classified.categories().keys().collect()
264 };
265 for category in reached {
266 match self.categories.get(category) {
267 Some(labels) => extend(&mut categories, labels.iter().cloned()),
268 None => extend(&mut categories, [Label::of(category)]),
269 }
270 }
271 let mut labelled: Vec<(String, Vec<Label>)> = Vec::new();
272 for endpoint in origins {
273 if labelled.iter().any(|(seen, _)| seen == endpoint) {
274 continue;
275 }
276 let labels = match (self.origins.get(endpoint), &self.origin_system) {
277 (Some(labels), _) => labels.clone(),
278 (None, Some(system)) => vec![Label {
279 system: system.clone(),
280 version: None,
281 code: endpoint.to_owned(),
282 }],
283 (None, None) => Vec::new(),
284 };
285 labelled.push((endpoint.to_owned(), labels));
286 }
287 Labels {
288 categories,
289 origins: labelled,
290 }
291 }
292}
293
294#[derive(Debug, Clone, Default, PartialEq, Eq)]
297pub struct Labels {
298 categories: Vec<Label>,
299 origins: Vec<(String, Vec<Label>)>,
300}
301
302impl Labels {
303 #[must_use]
305 pub fn categories(&self) -> &[Label] {
306 &self.categories
307 }
308
309 #[must_use]
312 pub fn origin(&self, endpoint: &str) -> &[Label] {
313 self.origins
314 .iter()
315 .find(|(named, _)| named == endpoint)
316 .map_or(&[], |(_, labels)| labels.as_slice())
317 }
318
319 #[must_use]
322 pub fn all(&self) -> Vec<Label> {
323 let mut all = self.categories.clone();
324 for (_, labels) in &self.origins {
325 extend(&mut all, labels.iter().cloned());
326 }
327 all
328 }
329}
330
331fn read(key: &str, declared: &[DeclaredLabel]) -> Result<Vec<Label>, LabelError> {
333 if declared.is_empty() {
334 return Err(LabelError::Empty {
335 key: key.to_owned(),
336 });
337 }
338 let mut labels = Vec::new();
339 for label in declared {
340 let label = Label::new(&label.system, &label.code).map_err(|source| LabelError::Label {
341 key: key.to_owned(),
342 source,
343 })?;
344 extend(&mut labels, [label]);
345 }
346 Ok(labels)
347}
348
349fn extend(kept: &mut Vec<Label>, labels: impl IntoIterator<Item = Label>) {
351 for label in labels {
352 if !kept.contains(&label) {
353 kept.push(label);
354 }
355 }
356}