accounting_core/utils/
validation.rs

1//! Validation utilities
2
3use crate::traits::*;
4use crate::types::*;
5use bigdecimal::BigDecimal;
6
7/// Validate that an amount is positive
8pub fn validate_positive_amount(amount: &BigDecimal) -> LedgerResult<()> {
9    if *amount <= BigDecimal::from(0) {
10        Err(LedgerError::Validation(
11            "Amount must be positive".to_string(),
12        ))
13    } else {
14        Ok(())
15    }
16}
17
18/// Validate that an account ID is valid
19pub fn validate_account_id(account_id: &str) -> LedgerResult<()> {
20    if account_id.trim().is_empty() {
21        return Err(LedgerError::Validation(
22            "Account ID cannot be empty".to_string(),
23        ));
24    }
25
26    if account_id.len() > 50 {
27        return Err(LedgerError::Validation(
28            "Account ID cannot exceed 50 characters".to_string(),
29        ));
30    }
31
32    // Check for valid characters (alphanumeric, dashes, underscores)
33    if !account_id
34        .chars()
35        .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
36    {
37        return Err(LedgerError::Validation(
38            "Account ID can only contain alphanumeric characters, dashes, and underscores"
39                .to_string(),
40        ));
41    }
42
43    Ok(())
44}
45
46/// Validate that an account name is valid
47pub fn validate_account_name(name: &str) -> LedgerResult<()> {
48    if name.trim().is_empty() {
49        return Err(LedgerError::Validation(
50            "Account name cannot be empty".to_string(),
51        ));
52    }
53
54    if name.len() > 100 {
55        return Err(LedgerError::Validation(
56            "Account name cannot exceed 100 characters".to_string(),
57        ));
58    }
59
60    Ok(())
61}
62
63/// Validate that a transaction description is valid
64pub fn validate_transaction_description(description: &str) -> LedgerResult<()> {
65    if description.trim().is_empty() {
66        return Err(LedgerError::Validation(
67            "Transaction description cannot be empty".to_string(),
68        ));
69    }
70
71    if description.len() > 500 {
72        return Err(LedgerError::Validation(
73            "Transaction description cannot exceed 500 characters".to_string(),
74        ));
75    }
76
77    Ok(())
78}
79
80/// Enhanced transaction validator with detailed checks
81pub struct EnhancedTransactionValidator;
82
83impl TransactionValidator for EnhancedTransactionValidator {
84    fn validate_transaction(&self, transaction: &Transaction) -> LedgerResult<()> {
85        // Basic validation
86        transaction.validate()?;
87
88        // Enhanced validations
89        validate_transaction_description(&transaction.description)?;
90
91        // Validate each entry
92        for entry in &transaction.entries {
93            validate_account_id(&entry.account_id)?;
94            validate_positive_amount(&entry.amount)?;
95        }
96
97        // Check for duplicate accounts (same account cannot appear twice with same entry type)
98        let mut account_entry_combinations = std::collections::HashSet::new();
99        for entry in &transaction.entries {
100            let combination = (&entry.account_id, &entry.entry_type);
101            if !account_entry_combinations.insert(combination) {
102                return Err(LedgerError::Validation(format!(
103                    "Account '{}' appears multiple times with the same entry type in transaction",
104                    entry.account_id
105                )));
106            }
107        }
108
109        Ok(())
110    }
111
112    fn validate_account_references(&self, _transaction: &Transaction) -> LedgerResult<()> {
113        // This would typically check if accounts exist in storage
114        // For this basic implementation, we assume all accounts exist
115        Ok(())
116    }
117}
118
119/// Enhanced account validator with detailed checks
120pub struct EnhancedAccountValidator;
121
122impl AccountValidator for EnhancedAccountValidator {
123    fn validate_account(&self, account: &Account) -> LedgerResult<()> {
124        validate_account_id(&account.id)?;
125        validate_account_name(&account.name)?;
126
127        // Additional validations can be added here
128        Ok(())
129    }
130
131    fn validate_account_deletion(&self, _account_id: &str) -> LedgerResult<()> {
132        // This would typically check if account has any transactions
133        // For this basic implementation, we allow deletion
134        Ok(())
135    }
136}