use std::sync::OnceLock;
use serde::Deserialize;
const TABLE_JSON: &str = include_str!("../data/codes.json");
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Code {
pub code: String,
pub severity: String,
pub domain: String,
pub summary: String,
}
#[derive(Deserialize)]
struct Table {
codes: Vec<Code>,
}
#[derive(Deserialize)]
struct Allocation {
code: String,
since: String,
revision: u32,
}
#[derive(Deserialize)]
struct Allocations {
revision: u32,
codes: Vec<Allocation>,
}
pub const DOMAINS: [&str; 9] =
["container", "geometry", "images", "labels", "annotations", "transforms", "curation", "integrity", "clinical"];
pub fn all() -> &'static [Code] {
static TABLE: OnceLock<Vec<Code>> = OnceLock::new();
TABLE.get_or_init(|| {
let table: Table = serde_json::from_str(TABLE_JSON).expect("data/codes.json is valid by construction");
table.codes
})
}
fn allocations() -> &'static Allocations {
static ALLOCATIONS: OnceLock<Allocations> = OnceLock::new();
ALLOCATIONS.get_or_init(|| serde_json::from_str(TABLE_JSON).expect("data/codes.json is valid by construction"))
}
pub fn table_revision() -> u32 {
allocations().revision
}
pub fn since(code: &str) -> Option<&'static str> {
allocations().codes.iter().find(|c| c.code == code).map(|c| c.since.as_str())
}
pub fn revision(code: &str) -> Option<u32> {
allocations().codes.iter().find(|c| c.code == code).map(|c| c.revision)
}
pub fn get(code: &str) -> Option<&'static Code> {
all().iter().find(|c| c.code == code)
}
pub fn code(name: &str) -> crate::Result<&'static Code> {
get(name).ok_or_else(|| crate::Error::Key(format!("unknown diagnostic code {}", crate::json::repr_str(name))))
}
pub fn for_domain(domain: &str) -> Vec<&'static Code> {
all().iter().filter(|c| c.domain == domain).collect()
}
pub fn table_json() -> &'static str {
TABLE_JSON
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unknown_code_is_quoted_as_python_quotes_it() {
assert_eq!(code("E101").unwrap().code, "E101");
assert_eq!(code("X999").unwrap_err().message(), "unknown diagnostic code 'X999'");
assert_eq!(code("it's").unwrap_err().message(), "unknown diagnostic code \"it's\"");
}
#[test]
fn table_is_complete_and_well_formed() {
let codes = all();
assert_eq!(codes.len(), 100);
for c in codes {
let expected = if c.code.starts_with('W') { "warning" } else { "error" };
assert_eq!(c.severity, expected, "{}", c.code);
assert!(DOMAINS.contains(&c.domain.as_str()), "{}", c.code);
}
let names: std::collections::BTreeSet<&str> = codes.iter().map(|c| c.code.as_str()).collect();
assert_eq!(names.len(), codes.len(), "a code is listed twice");
}
#[test]
fn s15_2_every_code_has_a_version_and_a_revision() {
let mut latest = 0;
for c in all() {
let version = since(&c.code).unwrap_or_else(|| panic!("{} names no version", c.code));
assert!(["1.0", "1.1"].contains(&version), "{}: {version}", c.code);
let allocated = revision(&c.code).unwrap_or_else(|| panic!("{} names no revision", c.code));
assert!((1..=table_revision()).contains(&allocated), "{}: revision {allocated}", c.code);
latest = latest.max(allocated);
}
assert_eq!(latest, table_revision());
assert_eq!((since("E999"), revision("E999")), (None, None));
}
}