use serde::de::DeserializeOwned;
use crate::reload::{FileKind, KINDS};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Refusal {
pub kind: &'static str,
pub file: String,
pub field: Option<String>,
pub reason: String,
}
impl Refusal {
pub fn sentence(&self) -> String {
match &self.field {
Some(field) => format!("{} ({field}): {}", self.file, self.reason),
None => format!("{}: {}", self.file, self.reason),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Check {
Valid,
Refused(Refusal),
Unchecked,
}
fn run<T: DeserializeOwned>(
text: &str,
parse: fn(&str) -> Result<T, String>,
) -> Result<(), (Option<String>, String)> {
match parse(text) {
Ok(_) => Ok(()),
Err(reason) => Err((field_of::<T>(text), reason)),
}
}
fn field_of<T: DeserializeOwned>(text: &str) -> Option<String> {
let mut de = ron::de::Deserializer::from_str(text).ok()?;
let err = serde_path_to_error::deserialize::<_, T>(&mut de).err()?;
let path = err.path().to_string().replace(".?", "");
let named = match err.inner() {
ron::Error::MissingStructField { field, .. } => Some(field.to_string()),
ron::Error::NoSuchStructField { found, .. } => Some(found.to_string()),
_ => None,
};
match (path.as_str(), named) {
(".", None) => None,
(".", Some(field)) => Some(field),
(_, None) => Some(path),
(_, Some(field)) => Some(format!("{path}.{field}")),
}
}
pub fn read<T: DeserializeOwned>(kind: &'static str, file: &str, text: &str) -> Result<T, Refusal> {
ron::from_str(text).map_err(|e: ron::error::SpannedError| Refusal {
kind,
file: file.to_owned(),
field: field_of::<T>(text),
reason: e.to_string(),
})
}
pub fn check(kind: &FileKind, file: &str, text: &str) -> Check {
use crate::{
composition::Compositions, data_files::DataFiles, family::Family, heightfield::Heightfield,
kits::Kits, library::Library, map::Map, note_agent::AgentSettings, notes::Notes, plan::Plan,
play::PlayConfig, policy::Policy, references::References, relations::Relations,
repetition::RepetitionRules, rigs::Rigs, rules::Rules, standards::Standards,
vocab::Vocabularies, volume::Volume, water::{WaterField, WaterPlan},
};
let result = match kind.id {
"kits" => run(text, Kits::parse),
"vocab" => run(text, Vocabularies::parse),
"library" => run(text, Library::parse),
"policy" => run(text, Policy::parse),
"rules" => run(text, Rules::parse),
"compositions" => run(text, Compositions::parse),
"standards" => run(text, Standards::parse),
"repetition" => run(text, RepetitionRules::parse),
"rigs" => run(text, Rigs::parse),
"notes" => run(text, Notes::from_ron),
"plan" => run(text, Plan::from_ron),
"references" => run(text, References::from_ron),
"relations" => run(text, Relations::parse),
"map" => run(text, Map::parse),
"terrain" => run(text, Heightfield::parse),
"water_plan" => run(text, WaterPlan::parse),
"water_field" => run(text, WaterField::parse),
"volume" => run(text, Volume::parse),
"data_registry" => run(text, DataFiles::parse),
"family_list" => run(text, Family::from_ron),
"agent" => run(text, AgentSettings::from_ron),
"play" => run(text, PlayConfig::from_ron),
_ => return Check::Unchecked,
};
match result {
Ok(()) => Check::Valid,
Err((field, reason)) => Check::Refused(Refusal { kind: kind.id, file: file.to_owned(), field, reason }),
}
}
pub fn unchecked() -> impl Iterator<Item = &'static FileKind> {
KINDS.iter().filter(|k| matches!(check(k, "", ""), Check::Unchecked))
}