use crate::error::EngineError;
use serde::Deserialize;
use std::collections::HashMap;
pub const UNKNOWN_CODE: &str = "UNK-000";
#[derive(Debug, Deserialize)]
pub struct CatalogEntry {
pub template: String,
pub hint: Option<String>,
}
#[derive(Debug, Deserialize)]
struct RawCatalog {
#[serde(flatten)]
entries: HashMap<String, CatalogEntry>,
}
#[derive(Debug)]
pub struct Catalog {
entries: HashMap<String, CatalogEntry>,
load_error: Option<String>,
}
impl Catalog {
pub fn load(path: &str) -> Result<Self, EngineError> {
let raw = std::fs::read_to_string(path)
.map_err(|source| EngineError::CatalogRead { path: path.to_string(), source })?;
let raw: RawCatalog = toml::from_str(&raw)
.map_err(|source| EngineError::CatalogParse { path: path.to_string(), source })?;
Ok(Self { entries: raw.entries, load_error: None })
}
pub fn load_or_fallback(path: &str) -> Self {
match Self::load(path) {
Ok(catalog) => catalog,
Err(err) => Self {
entries: HashMap::new(),
load_error: Some(err.summary()),
},
}
}
pub fn render(&self, code: &str, ctx: &[(&str, String)]) -> String {
if let Some(load_error) = &self.load_error {
return format!("[{UNKNOWN_CODE}] catalog failed to load: {load_error}");
}
let Some(entry) = self.entries.get(code) else {
return format!("[{UNKNOWN_CODE}] unknown diagnostic code '{code}'");
};
let mut text = entry.template.clone();
for (key, val) in ctx {
text = text.replace(&format!("{{{key}}}"), val);
}
text
}
pub fn hint(&self, code: &str) -> Option<&str> {
if self.load_error.is_some() {
return None;
}
self.entries.get(code)?.hint.as_deref()
}
}