pub fn validate_account_name(account: &str, account_types: &[String]) -> Option<String> {
if account.is_empty() {
return Some("account name is empty".to_string());
}
let mut components = account.split(':');
let root = components.next()?;
if root.is_empty() {
return Some("component 1 is empty".to_string());
}
if !account_types.iter().any(|t| t == root) {
return Some(format!(
"account must start with one of: {}. To use a different root name, \
rename a type via an option, e.g. `option \"name_income\" \"Revenue\"`",
account_types.join(", ")
));
}
for (i, part) in std::iter::once(root).chain(components).enumerate() {
if part.is_empty() {
return Some(format!("component {} is empty", i + 1));
}
let Some(first_char) = part.chars().next() else {
return Some(format!("component {} is empty", i + 1));
};
let is_valid_start = first_char.is_uppercase()
|| first_char.is_ascii_digit()
|| (!first_char.is_ascii() && first_char.is_alphabetic());
if !is_valid_start {
return Some(format!(
"component '{part}' must start with uppercase letter or digit"
));
}
for c in part.chars().skip(1) {
if !c.is_ascii_alphanumeric() && c != '-' && c.is_ascii() {
return Some(format!(
"component '{part}' contains invalid character '{c}'"
));
}
}
}
None }