use super::canonical;
pub const ERR_NAME_CONFLICT: &str = "ERR_NAME_CONFLICT:";
pub const RESERVED_KEYS: &[&str] = &[
"type",
"required",
"default",
"read",
"write",
"fields",
"strategy",
"model",
"localfield",
"foreignfield",
"fn",
"asyncfn",
"fnref",
"agg",
"depends",
];
fn norm_key(s: &str) -> String {
canonical(s).join("")
}
fn is_system_reserved(name: &str) -> bool {
name == "_id"
}
pub fn detect_conflicts<'a, I>(names: I) -> Result<(), String>
where
I: IntoIterator<Item = &'a str>,
{
let mut seen: Vec<(String, &'a str)> = Vec::new();
for name in names {
if is_system_reserved(name) {
continue;
}
let key = norm_key(name);
if key.is_empty() {
continue;
}
if let Some((_, prev)) = seen.iter().find(|(k, _)| k == &key) {
return Err(format!(
"{ERR_NAME_CONFLICT}名称 \"{name}\" 与 \"{prev}\" 归一后相同({key})"
));
}
if RESERVED_KEYS.contains(&key.as_str()) {
return Err(format!(
"{ERR_NAME_CONFLICT}名称 \"{name}\" 归一后命中契约保留键 \"{key}\""
));
}
seen.push((key, name));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn duplicate_after_normalize_errors() {
assert!(detect_conflicts(["orderTotal", "order_total"]).is_err());
assert!(detect_conflicts(["orderTotal", "orderItem"]).is_ok());
assert!(detect_conflicts(["orderTotal"]).is_ok());
}
#[test]
fn reserved_key_errors() {
let e = detect_conflicts(["fnRef"]).unwrap_err();
assert!(e.starts_with(ERR_NAME_CONFLICT));
assert!(detect_conflicts(["fn_ref"]).is_err());
}
#[test]
fn system_reserved_exempt() {
assert!(detect_conflicts(["_id", "id"]).is_ok());
}
#[test]
fn error_text_names_both() {
let e = detect_conflicts(["orderTotal", "order_total"]).unwrap_err();
assert!(e.contains("orderTotal") && e.contains("order_total"));
}
}