use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;
const EMBEDDED_JSON: &str = include_str!("../../data/answer_code_lists.json");
#[derive(Debug, Deserialize)]
struct RawFile {
lists: HashMap<String, HashMap<String, Vec<String>>>,
}
#[derive(Debug)]
pub struct AnswerCodeLists {
by_fv: HashMap<String, HashMap<String, HashSet<String>>>,
}
impl AnswerCodeLists {
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
let raw: RawFile = serde_json::from_str(json)?;
let by_fv = raw
.lists
.into_iter()
.map(|(fv, lists)| {
let lists = lists
.into_iter()
.map(|(id, codes)| (id, codes.into_iter().collect()))
.collect();
(fv, lists)
})
.collect();
Ok(Self { by_fv })
}
pub fn embedded() -> &'static Self {
static CELL: OnceLock<AnswerCodeLists> = OnceLock::new();
CELL.get_or_init(|| {
AnswerCodeLists::from_json(EMBEDDED_JSON)
.expect("embedded answer_code_lists.json is malformed")
})
}
pub fn codes(&self, fv: &str, list: &str) -> Option<&HashSet<String>> {
let fv = if fv == "FV2504" { "FV2510" } else { fv };
self.by_fv.get(fv)?.get(list)
}
pub fn holds(&self, fv: &str, list: &str, answer: &str) -> Option<bool> {
self.codes(fv, list).map(|codes| codes.contains(answer))
}
}
pub fn is_answer_list_id(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 6
&& matches!(bytes[0], b'E' | b'S' | b'G')
&& bytes[1] == b'_'
&& bytes[2..].iter().all(u8::is_ascii_digit)
}
pub fn answer_path(list_path: &str) -> Option<String> {
if let Some(prefix) = list_path.strip_suffix("AJT/1082") {
return Some(format!("{prefix}AJT/4465"));
}
list_path
.strip_suffix("C556/1131")
.map(|prefix| format!("{prefix}C556/9013"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_embedded_lists_name_what_the_ebd_document_says() {
let lists = AnswerCodeLists::embedded();
assert_eq!(lists.holds("FV2610", "S_0066", "ZB5"), Some(true));
assert_eq!(lists.holds("FV2610", "S_0066", "ZB4"), Some(false));
assert_eq!(lists.holds("FV2610", "S_0065", "ZB4"), Some(true));
assert_eq!(lists.holds("FV2504", "S_0066", "ZB5"), Some(true));
assert_eq!(lists.holds("FV2610", "S_9999", "ZB5"), None);
}
#[test]
fn only_ajt_and_sts_name_a_list() {
assert_eq!(answer_path("SG2/AJT/1082").as_deref(), Some("SG2/AJT/4465"));
assert_eq!(
answer_path("SG4/STS/C556/1131").as_deref(),
Some("SG4/STS/C556/9013")
);
assert_eq!(answer_path("SG26/LIN/1082"), None);
assert!(is_answer_list_id("E_0204") && is_answer_list_id("G_0059"));
assert!(!is_answer_list_id("Z07") && !is_answer_list_id("E_02041"));
}
}