use rustledger_core::{Account, NaiveDate};
use crate::LedgerState;
use crate::error::{ErrorCode, ValidationError};
pub fn push_account_not_open(
account: &Account,
date: NaiveDate,
subject: &str,
errors: &mut Vec<ValidationError>,
) {
errors.push(ValidationError::new(
ErrorCode::AccountNotOpen,
format!("{subject} {account} was never opened"),
date,
));
}
pub fn require_account_open(
state: &LedgerState,
account: &Account,
date: NaiveDate,
subject: &str,
errors: &mut Vec<ValidationError>,
) -> bool {
if state.accounts.contains_key(account) {
return true;
}
push_account_not_open(account, date, subject, errors);
false
}
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 }