#![doc(test(attr(deny(warnings))))]
pub mod api;
pub mod cache;
pub mod entity;
pub mod expression;
pub const MMS: &str = "http://id.who.int/icd/release/11/mms";
pub const ICF: &str = "http://id.who.int/icd/release/11/icf";
pub const FOUNDATION: &str = "http://id.who.int/icd/entity";
pub const SCHEMA: &str = "http://id.who.int/icd/schema/";
pub const CANONICAL: &str = "http://id.who.int";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Linearization {
Mms,
Icf,
Foundation,
}
impl Linearization {
pub const ALL: [Self; 3] = [Self::Mms, Self::Icf, Self::Foundation];
#[must_use]
pub const fn system(self) -> &'static str {
match self {
Self::Mms => MMS,
Self::Icf => ICF,
Self::Foundation => FOUNDATION,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Mms => "mms",
Self::Icf => "icf",
Self::Foundation => "entity",
}
}
#[must_use]
pub fn parse(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|l| l.name() == name)
}
#[must_use]
pub fn root_path(self, release: &str) -> String {
match self {
Self::Foundation => String::from("/icd/entity"),
other => format!("/icd/release/11/{release}/{}", other.name()),
}
}
#[must_use]
pub fn uri(self, id: &str) -> String {
format!("{}/{id}", self.system())
}
#[must_use]
pub fn versioned_uri(self, release: &str, id: &str) -> String {
match self {
Self::Foundation => self.uri(id),
other => format!(
"http://id.who.int/icd/release/11/{release}/{}/{id}",
other.name()
),
}
}
#[must_use]
pub fn id_of(self, uri: &str) -> Option<String> {
let rest = uri.strip_prefix(CANONICAL)?;
let tail = match self {
Self::Foundation => rest.strip_prefix("/icd/entity/")?,
other => {
let after = rest.strip_prefix("/icd/release/11/")?;
let (head, remainder) = after.split_once('/')?;
if head == other.name() {
remainder
} else if is_release(head) {
let (name, tail) = remainder.split_once('/')?;
if name != other.name() {
return None;
}
tail
} else {
return None;
}
}
};
let tail = tail.trim_end_matches('/');
let mut parts = tail.split('/');
let id = parts.next()?;
if id.is_empty() || !id.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
match parts.next() {
None => Some(id.to_owned()),
Some(residual @ ("other" | "unspecified")) if parts.next().is_none() => {
Some(format!("{id}/{residual}"))
}
Some(_) => None,
}
}
#[must_use]
pub fn key_of(id: &str) -> Option<u64> {
let (number, residual) = match id.split_once('/') {
None => (id, 0),
Some((n, "other")) => (n, 1),
Some((n, "unspecified")) => (n, 2),
Some(_) => return None,
};
let number: u64 = number.parse().ok()?;
number.checked_mul(4)?.checked_add(residual)
}
}
#[must_use]
pub fn is_release(text: &str) -> bool {
let bytes = text.as_bytes();
bytes.len() == 7
&& bytes
.get(..4)
.is_some_and(|y| y.iter().all(u8::is_ascii_digit))
&& bytes.get(4) == Some(&b'-')
&& bytes
.get(5..)
.is_some_and(|m| m.iter().all(u8::is_ascii_digit))
}
#[cfg(test)]
mod tests {
use super::{Linearization, is_release};
#[test]
fn uris_name_entities_in_both_forms() {
let mms = Linearization::Mms;
assert_eq!(
mms.id_of("http://id.who.int/icd/release/11/mms/257068234")
.as_deref(),
Some("257068234")
);
assert_eq!(
mms.id_of("http://id.who.int/icd/release/11/2026-01/mms/1363559646/other")
.as_deref(),
Some("1363559646/other")
);
assert_eq!(mms.id_of("http://id.who.int/icd/release/11/icf/12"), None);
assert_eq!(mms.id_of("http://id.who.int/icd/release/11/mms/abc"), None);
assert_eq!(
mms.id_of("http://id.who.int/icd/release/11/mms/1/2/3"),
None
);
assert_eq!(
Linearization::Foundation
.id_of("http://id.who.int/icd/entity/257068234")
.as_deref(),
Some("257068234")
);
assert_eq!(
Linearization::Foundation.id_of("http://id.who.int/icd/release/11/mms/257068234"),
None
);
assert_eq!(mms.uri("1A"), "http://id.who.int/icd/release/11/mms/1A");
assert_eq!(
mms.versioned_uri("2026-01", "5"),
"http://id.who.int/icd/release/11/2026-01/mms/5"
);
assert_eq!(
Linearization::key_of("1363559646/other"),
Some(1_363_559_646 * 4 + 1)
);
assert_eq!(Linearization::key_of("12/x"), None);
assert!(is_release("2026-01"));
assert!(!is_release("mms"));
}
}